From dd17652acab33cc83edb0c8a513d3d20819266bf Mon Sep 17 00:00:00 2001 From: Shoghi Cervantes Date: Tue, 1 Apr 2014 05:06:12 +0200 Subject: [PATCH] Fixed wrong paths --- src/pocketmine/Achievement.php | 134 + src/pocketmine/BanAPI.php | 411 +++ src/pocketmine/BlockAPI.php | 352 +++ src/pocketmine/LevelAPI.php | 66 + src/pocketmine/Player.php | 2736 +++++++++++++++++ src/pocketmine/PlayerAPI.php | 345 +++ src/pocketmine/PocketMine.php | 369 +++ src/pocketmine/Server.php | 1113 +++++++ src/pocketmine/ServerAPI.php | 363 +++ src/pocketmine/ServerOld.php | 735 +++++ src/pocketmine/TimeAPI.php | 144 + src/pocketmine/block/Air.php | 44 + src/pocketmine/block/Bed.php | 141 + src/pocketmine/block/Bedrock.php | 37 + src/pocketmine/block/Beetroot.php | 94 + src/pocketmine/block/BirchWoodStairs.php | 36 + src/pocketmine/block/Block.php | 543 ++++ src/pocketmine/block/Bookshelf.php | 31 + src/pocketmine/block/BrickStairs.php | 30 + src/pocketmine/block/Bricks.php | 58 + src/pocketmine/block/BrownMushroom.php | 58 + src/pocketmine/block/BurningFurnace.php | 131 + src/pocketmine/block/Cactus.php | 92 + src/pocketmine/block/Cake.php | 80 + src/pocketmine/block/Carpet.php | 80 + src/pocketmine/block/Carrot.php | 93 + src/pocketmine/block/Chest.php | 149 + src/pocketmine/block/Clay.php | 37 + src/pocketmine/block/Coal.php | 58 + src/pocketmine/block/CoalOre.php | 59 + src/pocketmine/block/Cobblestone.php | 58 + src/pocketmine/block/CobblestoneStairs.php | 30 + src/pocketmine/block/Cobweb.php | 37 + src/pocketmine/block/CyanFlower.php | 58 + src/pocketmine/block/Dandelion.php | 58 + src/pocketmine/block/DeadBush.php | 45 + src/pocketmine/block/Diamond.php | 52 + src/pocketmine/block/DiamondOre.php | 52 + src/pocketmine/block/Dirt.php | 44 + src/pocketmine/block/Door.php | 138 + src/pocketmine/block/DoubleSlab.php | 69 + src/pocketmine/block/DoubleWoodSlab.php | 62 + src/pocketmine/block/Fallable.php | 42 + src/pocketmine/block/Farmland.php | 37 + src/pocketmine/block/Fence.php | 32 + src/pocketmine/block/FenceGate.php | 75 + src/pocketmine/block/Fire.php | 62 + src/pocketmine/block/Flowable.php | 32 + src/pocketmine/block/Furnace.php | 32 + src/pocketmine/block/Generic.php | 79 + src/pocketmine/block/Glass.php | 35 + src/pocketmine/block/GlassPane.php | 32 + src/pocketmine/block/GlowingObsidian.php | 30 + src/pocketmine/block/GlowingRedstoneOre.php | 65 + src/pocketmine/block/Glowstone.php | 37 + src/pocketmine/block/Gold.php | 52 + src/pocketmine/block/GoldOre.php | 52 + src/pocketmine/block/Grass.php | 57 + src/pocketmine/block/Gravel.php | 44 + src/pocketmine/block/HayBale.php | 55 + src/pocketmine/block/Ice.php | 58 + src/pocketmine/block/Iron.php | 54 + src/pocketmine/block/IronBars.php | 32 + src/pocketmine/block/IronDoor.php | 59 + src/pocketmine/block/IronOre.php | 54 + src/pocketmine/block/JungleWoodStairs.php | 36 + src/pocketmine/block/Ladder.php | 72 + src/pocketmine/block/Lapis.php | 55 + src/pocketmine/block/LapisOre.php | 56 + src/pocketmine/block/Lava.php | 154 + src/pocketmine/block/Leaves.php | 163 + src/pocketmine/block/Liquid.php | 34 + src/pocketmine/block/LitPumpkin.php | 45 + src/pocketmine/block/Melon.php | 37 + src/pocketmine/block/MelonStem.php | 102 + src/pocketmine/block/MossStone.php | 59 + src/pocketmine/block/NetherBrick.php | 59 + src/pocketmine/block/NetherBrickStairs.php | 30 + src/pocketmine/block/NetherReactor.php | 31 + src/pocketmine/block/Netherrack.php | 59 + src/pocketmine/block/Obsidian.php | 50 + src/pocketmine/block/Planks.php | 38 + src/pocketmine/block/Potato.php | 95 + src/pocketmine/block/Pumpkin.php | 31 + src/pocketmine/block/PumpkinStem.php | 102 + src/pocketmine/block/Quartz.php | 65 + src/pocketmine/block/QuartzStairs.php | 30 + src/pocketmine/block/RedMushroom.php | 58 + src/pocketmine/block/RedstoneOre.php | 52 + src/pocketmine/block/Sand.php | 31 + src/pocketmine/block/Sandstone.php | 66 + src/pocketmine/block/SandstoneStairs.php | 30 + src/pocketmine/block/Sapling.php | 106 + src/pocketmine/block/SignPost.php | 85 + src/pocketmine/block/Slab.php | 125 + src/pocketmine/block/Snow.php | 31 + src/pocketmine/block/SnowLayer.php | 69 + src/pocketmine/block/Solid.php | 32 + src/pocketmine/block/SoulSand.php | 31 + src/pocketmine/block/Sponge.php | 31 + src/pocketmine/block/SpruceWoodStairs.php | 36 + src/pocketmine/block/Stair.php | 64 + src/pocketmine/block/StillLava.php | 31 + src/pocketmine/block/StillWater.php | 31 + src/pocketmine/block/Stone.php | 60 + src/pocketmine/block/StoneBrickStairs.php | 30 + src/pocketmine/block/StoneBricks.php | 67 + src/pocketmine/block/StoneWall.php | 37 + src/pocketmine/block/Stonecutter.php | 44 + src/pocketmine/block/Sugarcane.php | 118 + src/pocketmine/block/TNT.php | 56 + src/pocketmine/block/TallGrass.php | 77 + src/pocketmine/block/Torch.php | 87 + src/pocketmine/block/Transparent.php | 37 + src/pocketmine/block/Trapdoor.php | 71 + src/pocketmine/block/WallSign.php | 33 + src/pocketmine/block/Water.php | 160 + src/pocketmine/block/Wheat.php | 92 + src/pocketmine/block/Wood.php | 66 + src/pocketmine/block/WoodDoor.php | 38 + src/pocketmine/block/WoodSlab.php | 117 + src/pocketmine/block/WoodStairs.php | 54 + src/pocketmine/block/Wool.php | 50 + src/pocketmine/block/Workbench.php | 45 + src/pocketmine/command/Command.php | 304 ++ src/pocketmine/command/CommandExecutor.php | 37 + src/pocketmine/command/CommandMap.php | 61 + src/pocketmine/command/CommandReader.php | 92 + src/pocketmine/command/CommandSender.php | 44 + .../command/ConsoleCommandSender.php | 131 + src/pocketmine/command/PluginCommand.php | 71 + .../command/RemoteConsoleCommandSender.php | 27 + src/pocketmine/command/SimpleCommandMap.php | 180 ++ .../command/defaults/BanCommand.php | 65 + .../command/defaults/BanIpCommand.php | 81 + .../command/defaults/BanListCommand.php | 63 + .../defaults/DefaultGamemodeCommand.php | 61 + .../command/defaults/DifficultyCommand.php | 65 + .../command/defaults/HelpCommand.php | 106 + .../command/defaults/ListCommand.php | 58 + src/pocketmine/command/defaults/MeCommand.php | 62 + .../command/defaults/PardonCommand.php | 57 + .../command/defaults/PardonIpCommand.php | 60 + .../command/defaults/PluginsCommand.php | 62 + .../command/defaults/SayCommand.php | 65 + .../command/defaults/SeedCommand.php | 53 + .../command/defaults/StopCommand.php | 58 + .../command/defaults/TellCommand.php | 64 + .../command/defaults/VanillaCommand.php | 69 + .../command/defaults/VersionCommand.php | 100 + src/pocketmine/constants/GeneralConstants.php | 47 + src/pocketmine/entity/Ageable.php | 27 + src/pocketmine/entity/Animal.php | 27 + src/pocketmine/entity/Arrow.php | 27 + src/pocketmine/entity/Attachable.php | 27 + src/pocketmine/entity/Chicken.php | 27 + src/pocketmine/entity/Colorable.php | 27 + src/pocketmine/entity/Cow.php | 27 + src/pocketmine/entity/Creature.php | 27 + src/pocketmine/entity/Creeper.php | 27 + src/pocketmine/entity/Damageable.php | 27 + src/pocketmine/entity/DroppedItem.php | 27 + src/pocketmine/entity/Egg.php | 27 + src/pocketmine/entity/EnderPearl.php | 27 + src/pocketmine/entity/Enderman.php | 27 + src/pocketmine/entity/Entity.php | 552 ++++ src/pocketmine/entity/Explosive.php | 27 + src/pocketmine/entity/FallingBlock.php | 27 + src/pocketmine/entity/Hanging.php | 27 + src/pocketmine/entity/Human.php | 413 +++ src/pocketmine/entity/InventorySource.php | 58 + src/pocketmine/entity/Living.php | 27 + src/pocketmine/entity/Minecart.php | 27 + src/pocketmine/entity/Monster.php | 27 + src/pocketmine/entity/NPC.php | 27 + src/pocketmine/entity/Ocelot.php | 27 + src/pocketmine/entity/Painting.php | 27 + src/pocketmine/entity/Pig.php | 27 + src/pocketmine/entity/PigZombie.php | 27 + src/pocketmine/entity/Projectile.php | 27 + src/pocketmine/entity/ProjectileSource.php | 27 + src/pocketmine/entity/Rideable.php | 27 + src/pocketmine/entity/Sheep.php | 27 + src/pocketmine/entity/Silverfish.php | 27 + src/pocketmine/entity/Skeleton.php | 27 + src/pocketmine/entity/Slime.php | 27 + src/pocketmine/entity/Snowball.php | 27 + src/pocketmine/entity/Spider.php | 27 + src/pocketmine/entity/TNTPrimed.php | 27 + src/pocketmine/entity/Tameable.php | 27 + src/pocketmine/entity/Vehicle.php | 27 + src/pocketmine/entity/Villager.php | 40 + src/pocketmine/entity/Wolf.php | 27 + src/pocketmine/entity/Zombie.php | 27 + src/pocketmine/event/Cancellable.php | 32 + src/pocketmine/event/Event.php | 74 + src/pocketmine/event/EventPriority.php | 58 + src/pocketmine/event/HandlerList.php | 176 ++ src/pocketmine/event/Listener.php | 26 + .../event/block/BlockBreakEvent.php | 66 + src/pocketmine/event/block/BlockEvent.php | 36 + .../event/block/BlockPlaceEvent.php | 73 + .../event/entity/EntityArmorChangeEvent.php | 60 + .../event/entity/EntityDespawnEvent.php | 87 + src/pocketmine/event/entity/EntityEvent.php | 36 + .../event/entity/EntityExplodeEvent.php | 94 + .../entity/EntityInventoryChangeEvent.php | 60 + .../event/entity/EntityLevelChangeEvent.php | 48 + .../event/entity/EntityMotionEvent.php | 44 + .../event/entity/EntityMoveEvent.php | 45 + .../event/entity/EntitySpawnEvent.php | 94 + .../event/inventory/InventoryEvent.php | 47 + .../player/PlayerAchievementAwardedEvent.php | 48 + .../event/player/PlayerChatEvent.php | 90 + .../player/PlayerCommandPreprocessEvent.php | 72 + src/pocketmine/event/player/PlayerEvent.php | 36 + .../player/PlayerGameModeChangeEvent.php | 45 + .../event/player/PlayerInteractEvent.php | 64 + .../event/player/PlayerItemHeldEvent.php | 55 + .../event/player/PlayerJoinEvent.php | 54 + .../event/player/PlayerKickEvent.php | 57 + .../event/player/PlayerLoginEvent.php | 49 + .../event/player/PlayerPreLoginEvent.php | 49 + .../event/player/PlayerQuitEvent.php | 48 + .../event/player/PlayerRespawnEvent.php | 58 + .../event/plugin/PluginDisableEvent.php | 38 + .../event/plugin/PluginEnableEvent.php | 38 + src/pocketmine/event/plugin/PluginEvent.php | 42 + .../event/server/DataPacketReceiveEvent.php | 47 + .../event/server/DataPacketSendEvent.php | 47 + .../event/server/PacketReceiveEvent.php | 41 + .../event/server/PacketSendEvent.php | 41 + .../event/server/ServerCommandEvent.php | 67 + src/pocketmine/event/server/ServerEvent.php | 31 + src/pocketmine/event/tile/TileEvent.php | 35 + .../event/tile/TileInventoryChangeEvent.php | 60 + src/pocketmine/item/Apple.php | 30 + src/pocketmine/item/Bed.php | 32 + src/pocketmine/item/BeetrootSeeds.php | 31 + src/pocketmine/item/BeetrootSoup.php | 31 + src/pocketmine/item/Block.php | 44 + src/pocketmine/item/Bowl.php | 30 + src/pocketmine/item/Brick.php | 30 + src/pocketmine/item/Bucket.php | 74 + src/pocketmine/item/Cake.php | 32 + src/pocketmine/item/Carrot.php | 31 + src/pocketmine/item/Coal.php | 33 + src/pocketmine/item/Diamond.php | 30 + src/pocketmine/item/Feather.php | 30 + src/pocketmine/item/FlintSteel.php | 50 + src/pocketmine/item/GoldIngot.php | 30 + src/pocketmine/item/IronAxe.php | 30 + src/pocketmine/item/IronDoor.php | 32 + src/pocketmine/item/IronHoe.php | 30 + src/pocketmine/item/IronIngot.php | 30 + src/pocketmine/item/IronPickaxe.php | 30 + src/pocketmine/item/IronShovel.php | 30 + src/pocketmine/item/Item.php | 695 +++++ src/pocketmine/item/MelonSeeds.php | 31 + src/pocketmine/item/MushroomStew.php | 31 + src/pocketmine/item/Painting.php | 95 + src/pocketmine/item/Potato.php | 31 + src/pocketmine/item/PumpkinSeeds.php | 31 + src/pocketmine/item/Sign.php | 32 + src/pocketmine/item/SpawnEgg.php | 58 + src/pocketmine/item/Stick.php | 30 + src/pocketmine/item/Sugarcane.php | 31 + src/pocketmine/item/WheatSeeds.php | 31 + src/pocketmine/item/WoodenAxe.php | 30 + src/pocketmine/item/WoodenDoor.php | 32 + src/pocketmine/item/WoodenPickaxe.php | 30 + src/pocketmine/item/WoodenShovel.php | 30 + src/pocketmine/item/WoodenSword.php | 30 + src/pocketmine/level/Explosion.php | 154 + src/pocketmine/level/Level.php | 1384 +++++++++ src/pocketmine/level/LevelImport.php | 129 + src/pocketmine/level/PocketChunkParser.php | 234 ++ src/pocketmine/level/Position.php | 78 + src/pocketmine/level/WorldGenerator.php | 71 + src/pocketmine/level/generator/Flat.php | 159 + src/pocketmine/level/generator/Generator.php | 64 + src/pocketmine/level/generator/Normal.php | 189 ++ .../level/generator/noise/Generator.php | 104 + .../level/generator/noise/Perlin.php | 104 + .../level/generator/noise/Simplex.php | 461 +++ .../level/generator/object/BigTree.php | 85 + .../level/generator/object/Object.php | 30 + src/pocketmine/level/generator/object/Ore.php | 94 + .../level/generator/object/OreType.php | 36 + .../level/generator/object/PineTree.php | 97 + .../level/generator/object/Pond.php | 44 + .../level/generator/object/SmallTree.php | 104 + .../level/generator/object/SpruceTree.php | 88 + .../level/generator/object/TallGrass.php | 49 + .../level/generator/object/Tree.php | 69 + .../level/generator/populator/Mineshaft.php | 40 + .../level/generator/populator/Ore.php | 49 + .../level/generator/populator/Pond.php | 59 + .../level/generator/populator/Populator.php | 32 + .../level/generator/populator/TallGrass.php | 80 + .../level/generator/populator/Tree.php | 77 + src/pocketmine/math/AxisAlignedBB.php | 239 ++ src/pocketmine/math/Math.php | 30 + src/pocketmine/math/Matrix.php | 200 ++ src/pocketmine/math/Vector2.php | 130 + src/pocketmine/math/Vector3.php | 188 ++ src/pocketmine/math/VectorMath.php | 31 + src/pocketmine/nbt/NBT.php | 255 ++ src/pocketmine/nbt/tag/Byte.php | 39 + src/pocketmine/nbt/tag/Byte_Array.php | 40 + src/pocketmine/nbt/tag/Compound.php | 85 + src/pocketmine/nbt/tag/Double.php | 39 + src/pocketmine/nbt/tag/End.php | 39 + src/pocketmine/nbt/tag/Enum.php | 170 + src/pocketmine/nbt/tag/Float.php | 39 + src/pocketmine/nbt/tag/Int.php | 39 + src/pocketmine/nbt/tag/Int_Array.php | 46 + src/pocketmine/nbt/tag/Long.php | 39 + src/pocketmine/nbt/tag/NamedTag.php | 47 + src/pocketmine/nbt/tag/Short.php | 39 + src/pocketmine/nbt/tag/String.php | 40 + src/pocketmine/nbt/tag/Tag.php | 50 + src/pocketmine/network/Packet.php | 30 + src/pocketmine/network/ThreadedHandler.php | 187 ++ .../network/protocol/AddEntityPacket.php | 59 + .../network/protocol/AddItemEntityPacket.php | 55 + .../network/protocol/AddMobPacket.php | 56 + .../network/protocol/AddPaintingPacket.php | 51 + .../network/protocol/AddPlayerPacket.php | 62 + .../protocol/AdventureSettingsPacket.php | 41 + .../network/protocol/AnimatePacket.php | 44 + .../network/protocol/ChatPacket.php | 41 + .../network/protocol/ChunkDataPacket.php | 45 + .../network/protocol/ClientConnectPacket.php | 44 + .../protocol/ClientHandshakePacket.php | 54 + .../network/protocol/ContainerClosePacket.php | 41 + .../network/protocol/ContainerOpenPacket.php | 51 + .../protocol/ContainerSetContentPacket.php | 63 + .../protocol/ContainerSetDataPacket.php | 45 + .../protocol/ContainerSetSlotPacket.php | 47 + .../network/protocol/DataPacket.php | 183 ++ .../network/protocol/DisconnectPacket.php | 38 + .../network/protocol/DropItemPacket.php | 44 + .../network/protocol/EntityDataPacket.php | 50 + .../network/protocol/EntityEventPacket.php | 44 + .../network/protocol/ExplodePacket.php | 56 + .../network/protocol/HurtArmorPacket.php | 41 + src/pocketmine/network/protocol/Info.php | 106 + .../network/protocol/InteractPacket.php | 47 + .../network/protocol/LevelEventPacket.php | 49 + .../network/protocol/LoginPacket.php | 48 + .../network/protocol/LoginStatusPacket.php | 41 + .../network/protocol/MessagePacket.php | 44 + .../network/protocol/MoveEntityPacket.php | 39 + .../protocol/MoveEntityPacket_PosRot.php | 51 + .../network/protocol/MovePlayerPacket.php | 59 + .../network/protocol/PingPacket.php | 41 + .../network/protocol/PlayerActionPacket.php | 50 + .../protocol/PlayerArmorEquipmentPacket.php | 50 + .../protocol/PlayerEquipmentPacket.php | 50 + .../network/protocol/PongPacket.php | 44 + .../network/protocol/ReadyPacket.php | 40 + .../network/protocol/RemoveBlockPacket.php | 46 + .../network/protocol/RemoveEntityPacket.php | 41 + .../network/protocol/RemovePlayerPacket.php | 43 + .../network/protocol/RequestChunkPacket.php | 42 + .../network/protocol/RespawnPacket.php | 50 + .../network/protocol/RotateHeadPacket.php | 43 + .../network/protocol/SendInventoryPacket.php | 64 + .../protocol/ServerHandshakePacket.php | 60 + .../network/protocol/SetEntityDataPacket.php | 44 + .../protocol/SetEntityMotionPacket.php | 47 + .../network/protocol/SetHealthPacket.php | 41 + .../protocol/SetSpawnPositionPacket.php | 45 + .../network/protocol/SetTimePacket.php | 43 + .../network/protocol/StartGamePacket.php | 53 + .../network/protocol/TakeItemEntityPacket.php | 43 + .../network/protocol/TileEventPacket.php | 49 + .../network/protocol/UnknownPacket.php | 41 + .../network/protocol/UpdateBlockPacket.php | 49 + .../network/protocol/UseItemPacket.php | 64 + src/pocketmine/network/query/QueryHandler.php | 143 + src/pocketmine/network/query/QueryPacket.php | 46 + src/pocketmine/network/raknet/Info.php | 99 + src/pocketmine/network/raknet/Packet.php | 591 ++++ src/pocketmine/network/rcon/RCON.php | 87 + src/pocketmine/network/rcon/RCONInstance.php | 173 ++ src/pocketmine/network/upnp/UPnP.php | 72 + src/pocketmine/permission/BanEntry.php | 136 + src/pocketmine/permission/BanList.php | 166 + .../permission/DefaultPermissions.php | 114 + src/pocketmine/permission/Permissible.php | 74 + src/pocketmine/permission/PermissibleBase.php | 219 ++ src/pocketmine/permission/Permission.php | 254 ++ .../permission/PermissionAttachment.php | 113 + .../permission/PermissionAttachmentInfo.php | 84 + .../permission/PermissionRemovedExecutor.php | 33 + src/pocketmine/permission/ServerOperator.php | 41 + src/pocketmine/plugin/EventExecutor.php | 36 + src/pocketmine/plugin/FolderPluginLoader.php | 147 + src/pocketmine/plugin/MethodEventExecutor.php | 38 + src/pocketmine/plugin/PharPluginLoader.php | 145 + src/pocketmine/plugin/Plugin.php | 83 + src/pocketmine/plugin/PluginBase.php | 265 ++ src/pocketmine/plugin/PluginDescription.php | 211 ++ src/pocketmine/plugin/PluginLoadOrder.php | 35 + src/pocketmine/plugin/PluginLoader.php | 69 + src/pocketmine/plugin/PluginManager.php | 686 +++++ src/pocketmine/plugin/RegisteredListener.php | 97 + src/pocketmine/pmf/LevelFormat.php | 761 +++++ src/pocketmine/pmf/PMF.php | 143 + src/pocketmine/recipes/Crafting.php | 334 ++ src/pocketmine/recipes/Fuel.php | 53 + src/pocketmine/recipes/Smelt.php | 44 + src/pocketmine/scheduler/CallbackTask.php | 52 + src/pocketmine/scheduler/PluginTask.php | 48 + src/pocketmine/scheduler/ServerScheduler.php | 197 ++ src/pocketmine/scheduler/Task.php | 72 + src/pocketmine/scheduler/TaskHandler.php | 138 + src/pocketmine/scheduler/TaskQueue.php | 29 + src/pocketmine/scheduler/TickScheduler.php | 79 + src/pocketmine/tile/Chest.php | 129 + src/pocketmine/tile/Container.php | 195 ++ src/pocketmine/tile/Furnace.php | 118 + src/pocketmine/tile/Sign.php | 85 + src/pocketmine/tile/Spawnable.php | 36 + src/pocketmine/tile/Tile.php | 122 + src/pocketmine/utils/Cache.php | 84 + src/pocketmine/utils/Config.php | 385 +++ src/pocketmine/utils/Random.php | 122 + src/pocketmine/utils/TextFormat.php | 281 ++ src/pocketmine/utils/Utils.php | 829 +++++ src/pocketmine/utils/VersionString.php | 113 + src/pocketmine/wizard/Installer.php | 205 ++ src/pocketmine/wizard/InstallerLang.php | 114 + src/spl/SplAutoloader.php | 64 + src/spl/SplClassLoader.php | 218 ++ 437 files changed, 41109 insertions(+) create mode 100644 src/pocketmine/Achievement.php create mode 100644 src/pocketmine/BanAPI.php create mode 100644 src/pocketmine/BlockAPI.php create mode 100644 src/pocketmine/LevelAPI.php create mode 100644 src/pocketmine/Player.php create mode 100644 src/pocketmine/PlayerAPI.php create mode 100644 src/pocketmine/PocketMine.php create mode 100644 src/pocketmine/Server.php create mode 100644 src/pocketmine/ServerAPI.php create mode 100644 src/pocketmine/ServerOld.php create mode 100644 src/pocketmine/TimeAPI.php create mode 100644 src/pocketmine/block/Air.php create mode 100644 src/pocketmine/block/Bed.php create mode 100644 src/pocketmine/block/Bedrock.php create mode 100644 src/pocketmine/block/Beetroot.php create mode 100644 src/pocketmine/block/BirchWoodStairs.php create mode 100644 src/pocketmine/block/Block.php create mode 100644 src/pocketmine/block/Bookshelf.php create mode 100644 src/pocketmine/block/BrickStairs.php create mode 100644 src/pocketmine/block/Bricks.php create mode 100644 src/pocketmine/block/BrownMushroom.php create mode 100644 src/pocketmine/block/BurningFurnace.php create mode 100644 src/pocketmine/block/Cactus.php create mode 100644 src/pocketmine/block/Cake.php create mode 100644 src/pocketmine/block/Carpet.php create mode 100644 src/pocketmine/block/Carrot.php create mode 100644 src/pocketmine/block/Chest.php create mode 100644 src/pocketmine/block/Clay.php create mode 100644 src/pocketmine/block/Coal.php create mode 100644 src/pocketmine/block/CoalOre.php create mode 100644 src/pocketmine/block/Cobblestone.php create mode 100644 src/pocketmine/block/CobblestoneStairs.php create mode 100644 src/pocketmine/block/Cobweb.php create mode 100644 src/pocketmine/block/CyanFlower.php create mode 100644 src/pocketmine/block/Dandelion.php create mode 100644 src/pocketmine/block/DeadBush.php create mode 100644 src/pocketmine/block/Diamond.php create mode 100644 src/pocketmine/block/DiamondOre.php create mode 100644 src/pocketmine/block/Dirt.php create mode 100644 src/pocketmine/block/Door.php create mode 100644 src/pocketmine/block/DoubleSlab.php create mode 100644 src/pocketmine/block/DoubleWoodSlab.php create mode 100644 src/pocketmine/block/Fallable.php create mode 100644 src/pocketmine/block/Farmland.php create mode 100644 src/pocketmine/block/Fence.php create mode 100644 src/pocketmine/block/FenceGate.php create mode 100644 src/pocketmine/block/Fire.php create mode 100644 src/pocketmine/block/Flowable.php create mode 100644 src/pocketmine/block/Furnace.php create mode 100644 src/pocketmine/block/Generic.php create mode 100644 src/pocketmine/block/Glass.php create mode 100644 src/pocketmine/block/GlassPane.php create mode 100644 src/pocketmine/block/GlowingObsidian.php create mode 100644 src/pocketmine/block/GlowingRedstoneOre.php create mode 100644 src/pocketmine/block/Glowstone.php create mode 100644 src/pocketmine/block/Gold.php create mode 100644 src/pocketmine/block/GoldOre.php create mode 100644 src/pocketmine/block/Grass.php create mode 100644 src/pocketmine/block/Gravel.php create mode 100644 src/pocketmine/block/HayBale.php create mode 100644 src/pocketmine/block/Ice.php create mode 100644 src/pocketmine/block/Iron.php create mode 100644 src/pocketmine/block/IronBars.php create mode 100644 src/pocketmine/block/IronDoor.php create mode 100644 src/pocketmine/block/IronOre.php create mode 100644 src/pocketmine/block/JungleWoodStairs.php create mode 100644 src/pocketmine/block/Ladder.php create mode 100644 src/pocketmine/block/Lapis.php create mode 100644 src/pocketmine/block/LapisOre.php create mode 100644 src/pocketmine/block/Lava.php create mode 100644 src/pocketmine/block/Leaves.php create mode 100644 src/pocketmine/block/Liquid.php create mode 100644 src/pocketmine/block/LitPumpkin.php create mode 100644 src/pocketmine/block/Melon.php create mode 100644 src/pocketmine/block/MelonStem.php create mode 100644 src/pocketmine/block/MossStone.php create mode 100644 src/pocketmine/block/NetherBrick.php create mode 100644 src/pocketmine/block/NetherBrickStairs.php create mode 100644 src/pocketmine/block/NetherReactor.php create mode 100644 src/pocketmine/block/Netherrack.php create mode 100644 src/pocketmine/block/Obsidian.php create mode 100644 src/pocketmine/block/Planks.php create mode 100644 src/pocketmine/block/Potato.php create mode 100644 src/pocketmine/block/Pumpkin.php create mode 100644 src/pocketmine/block/PumpkinStem.php create mode 100644 src/pocketmine/block/Quartz.php create mode 100644 src/pocketmine/block/QuartzStairs.php create mode 100644 src/pocketmine/block/RedMushroom.php create mode 100644 src/pocketmine/block/RedstoneOre.php create mode 100644 src/pocketmine/block/Sand.php create mode 100644 src/pocketmine/block/Sandstone.php create mode 100644 src/pocketmine/block/SandstoneStairs.php create mode 100644 src/pocketmine/block/Sapling.php create mode 100644 src/pocketmine/block/SignPost.php create mode 100644 src/pocketmine/block/Slab.php create mode 100644 src/pocketmine/block/Snow.php create mode 100644 src/pocketmine/block/SnowLayer.php create mode 100644 src/pocketmine/block/Solid.php create mode 100644 src/pocketmine/block/SoulSand.php create mode 100644 src/pocketmine/block/Sponge.php create mode 100644 src/pocketmine/block/SpruceWoodStairs.php create mode 100644 src/pocketmine/block/Stair.php create mode 100644 src/pocketmine/block/StillLava.php create mode 100644 src/pocketmine/block/StillWater.php create mode 100644 src/pocketmine/block/Stone.php create mode 100644 src/pocketmine/block/StoneBrickStairs.php create mode 100644 src/pocketmine/block/StoneBricks.php create mode 100644 src/pocketmine/block/StoneWall.php create mode 100644 src/pocketmine/block/Stonecutter.php create mode 100644 src/pocketmine/block/Sugarcane.php create mode 100644 src/pocketmine/block/TNT.php create mode 100644 src/pocketmine/block/TallGrass.php create mode 100644 src/pocketmine/block/Torch.php create mode 100644 src/pocketmine/block/Transparent.php create mode 100644 src/pocketmine/block/Trapdoor.php create mode 100644 src/pocketmine/block/WallSign.php create mode 100644 src/pocketmine/block/Water.php create mode 100644 src/pocketmine/block/Wheat.php create mode 100644 src/pocketmine/block/Wood.php create mode 100644 src/pocketmine/block/WoodDoor.php create mode 100644 src/pocketmine/block/WoodSlab.php create mode 100644 src/pocketmine/block/WoodStairs.php create mode 100644 src/pocketmine/block/Wool.php create mode 100644 src/pocketmine/block/Workbench.php create mode 100644 src/pocketmine/command/Command.php create mode 100644 src/pocketmine/command/CommandExecutor.php create mode 100644 src/pocketmine/command/CommandMap.php create mode 100644 src/pocketmine/command/CommandReader.php create mode 100644 src/pocketmine/command/CommandSender.php create mode 100644 src/pocketmine/command/ConsoleCommandSender.php create mode 100644 src/pocketmine/command/PluginCommand.php create mode 100644 src/pocketmine/command/RemoteConsoleCommandSender.php create mode 100644 src/pocketmine/command/SimpleCommandMap.php create mode 100644 src/pocketmine/command/defaults/BanCommand.php create mode 100644 src/pocketmine/command/defaults/BanIpCommand.php create mode 100644 src/pocketmine/command/defaults/BanListCommand.php create mode 100644 src/pocketmine/command/defaults/DefaultGamemodeCommand.php create mode 100644 src/pocketmine/command/defaults/DifficultyCommand.php create mode 100644 src/pocketmine/command/defaults/HelpCommand.php create mode 100644 src/pocketmine/command/defaults/ListCommand.php create mode 100644 src/pocketmine/command/defaults/MeCommand.php create mode 100644 src/pocketmine/command/defaults/PardonCommand.php create mode 100644 src/pocketmine/command/defaults/PardonIpCommand.php create mode 100644 src/pocketmine/command/defaults/PluginsCommand.php create mode 100644 src/pocketmine/command/defaults/SayCommand.php create mode 100644 src/pocketmine/command/defaults/SeedCommand.php create mode 100644 src/pocketmine/command/defaults/StopCommand.php create mode 100644 src/pocketmine/command/defaults/TellCommand.php create mode 100644 src/pocketmine/command/defaults/VanillaCommand.php create mode 100644 src/pocketmine/command/defaults/VersionCommand.php create mode 100644 src/pocketmine/constants/GeneralConstants.php create mode 100644 src/pocketmine/entity/Ageable.php create mode 100644 src/pocketmine/entity/Animal.php create mode 100644 src/pocketmine/entity/Arrow.php create mode 100644 src/pocketmine/entity/Attachable.php create mode 100644 src/pocketmine/entity/Chicken.php create mode 100644 src/pocketmine/entity/Colorable.php create mode 100644 src/pocketmine/entity/Cow.php create mode 100644 src/pocketmine/entity/Creature.php create mode 100644 src/pocketmine/entity/Creeper.php create mode 100644 src/pocketmine/entity/Damageable.php create mode 100644 src/pocketmine/entity/DroppedItem.php create mode 100644 src/pocketmine/entity/Egg.php create mode 100644 src/pocketmine/entity/EnderPearl.php create mode 100644 src/pocketmine/entity/Enderman.php create mode 100644 src/pocketmine/entity/Entity.php create mode 100644 src/pocketmine/entity/Explosive.php create mode 100644 src/pocketmine/entity/FallingBlock.php create mode 100644 src/pocketmine/entity/Hanging.php create mode 100644 src/pocketmine/entity/Human.php create mode 100644 src/pocketmine/entity/InventorySource.php create mode 100644 src/pocketmine/entity/Living.php create mode 100644 src/pocketmine/entity/Minecart.php create mode 100644 src/pocketmine/entity/Monster.php create mode 100644 src/pocketmine/entity/NPC.php create mode 100644 src/pocketmine/entity/Ocelot.php create mode 100644 src/pocketmine/entity/Painting.php create mode 100644 src/pocketmine/entity/Pig.php create mode 100644 src/pocketmine/entity/PigZombie.php create mode 100644 src/pocketmine/entity/Projectile.php create mode 100644 src/pocketmine/entity/ProjectileSource.php create mode 100644 src/pocketmine/entity/Rideable.php create mode 100644 src/pocketmine/entity/Sheep.php create mode 100644 src/pocketmine/entity/Silverfish.php create mode 100644 src/pocketmine/entity/Skeleton.php create mode 100644 src/pocketmine/entity/Slime.php create mode 100644 src/pocketmine/entity/Snowball.php create mode 100644 src/pocketmine/entity/Spider.php create mode 100644 src/pocketmine/entity/TNTPrimed.php create mode 100644 src/pocketmine/entity/Tameable.php create mode 100644 src/pocketmine/entity/Vehicle.php create mode 100644 src/pocketmine/entity/Villager.php create mode 100644 src/pocketmine/entity/Wolf.php create mode 100644 src/pocketmine/entity/Zombie.php create mode 100644 src/pocketmine/event/Cancellable.php create mode 100644 src/pocketmine/event/Event.php create mode 100644 src/pocketmine/event/EventPriority.php create mode 100644 src/pocketmine/event/HandlerList.php create mode 100644 src/pocketmine/event/Listener.php create mode 100644 src/pocketmine/event/block/BlockBreakEvent.php create mode 100644 src/pocketmine/event/block/BlockEvent.php create mode 100644 src/pocketmine/event/block/BlockPlaceEvent.php create mode 100644 src/pocketmine/event/entity/EntityArmorChangeEvent.php create mode 100644 src/pocketmine/event/entity/EntityDespawnEvent.php create mode 100644 src/pocketmine/event/entity/EntityEvent.php create mode 100644 src/pocketmine/event/entity/EntityExplodeEvent.php create mode 100644 src/pocketmine/event/entity/EntityInventoryChangeEvent.php create mode 100644 src/pocketmine/event/entity/EntityLevelChangeEvent.php create mode 100644 src/pocketmine/event/entity/EntityMotionEvent.php create mode 100644 src/pocketmine/event/entity/EntityMoveEvent.php create mode 100644 src/pocketmine/event/entity/EntitySpawnEvent.php create mode 100644 src/pocketmine/event/inventory/InventoryEvent.php create mode 100644 src/pocketmine/event/player/PlayerAchievementAwardedEvent.php create mode 100644 src/pocketmine/event/player/PlayerChatEvent.php create mode 100644 src/pocketmine/event/player/PlayerCommandPreprocessEvent.php create mode 100644 src/pocketmine/event/player/PlayerEvent.php create mode 100644 src/pocketmine/event/player/PlayerGameModeChangeEvent.php create mode 100644 src/pocketmine/event/player/PlayerInteractEvent.php create mode 100644 src/pocketmine/event/player/PlayerItemHeldEvent.php create mode 100644 src/pocketmine/event/player/PlayerJoinEvent.php create mode 100644 src/pocketmine/event/player/PlayerKickEvent.php create mode 100644 src/pocketmine/event/player/PlayerLoginEvent.php create mode 100644 src/pocketmine/event/player/PlayerPreLoginEvent.php create mode 100644 src/pocketmine/event/player/PlayerQuitEvent.php create mode 100644 src/pocketmine/event/player/PlayerRespawnEvent.php create mode 100644 src/pocketmine/event/plugin/PluginDisableEvent.php create mode 100644 src/pocketmine/event/plugin/PluginEnableEvent.php create mode 100644 src/pocketmine/event/plugin/PluginEvent.php create mode 100644 src/pocketmine/event/server/DataPacketReceiveEvent.php create mode 100644 src/pocketmine/event/server/DataPacketSendEvent.php create mode 100644 src/pocketmine/event/server/PacketReceiveEvent.php create mode 100644 src/pocketmine/event/server/PacketSendEvent.php create mode 100644 src/pocketmine/event/server/ServerCommandEvent.php create mode 100644 src/pocketmine/event/server/ServerEvent.php create mode 100644 src/pocketmine/event/tile/TileEvent.php create mode 100644 src/pocketmine/event/tile/TileInventoryChangeEvent.php create mode 100644 src/pocketmine/item/Apple.php create mode 100644 src/pocketmine/item/Bed.php create mode 100644 src/pocketmine/item/BeetrootSeeds.php create mode 100644 src/pocketmine/item/BeetrootSoup.php create mode 100644 src/pocketmine/item/Block.php create mode 100644 src/pocketmine/item/Bowl.php create mode 100644 src/pocketmine/item/Brick.php create mode 100644 src/pocketmine/item/Bucket.php create mode 100644 src/pocketmine/item/Cake.php create mode 100644 src/pocketmine/item/Carrot.php create mode 100644 src/pocketmine/item/Coal.php create mode 100644 src/pocketmine/item/Diamond.php create mode 100644 src/pocketmine/item/Feather.php create mode 100644 src/pocketmine/item/FlintSteel.php create mode 100644 src/pocketmine/item/GoldIngot.php create mode 100644 src/pocketmine/item/IronAxe.php create mode 100644 src/pocketmine/item/IronDoor.php create mode 100644 src/pocketmine/item/IronHoe.php create mode 100644 src/pocketmine/item/IronIngot.php create mode 100644 src/pocketmine/item/IronPickaxe.php create mode 100644 src/pocketmine/item/IronShovel.php create mode 100644 src/pocketmine/item/Item.php create mode 100644 src/pocketmine/item/MelonSeeds.php create mode 100644 src/pocketmine/item/MushroomStew.php create mode 100644 src/pocketmine/item/Painting.php create mode 100644 src/pocketmine/item/Potato.php create mode 100644 src/pocketmine/item/PumpkinSeeds.php create mode 100644 src/pocketmine/item/Sign.php create mode 100644 src/pocketmine/item/SpawnEgg.php create mode 100644 src/pocketmine/item/Stick.php create mode 100644 src/pocketmine/item/Sugarcane.php create mode 100644 src/pocketmine/item/WheatSeeds.php create mode 100644 src/pocketmine/item/WoodenAxe.php create mode 100644 src/pocketmine/item/WoodenDoor.php create mode 100644 src/pocketmine/item/WoodenPickaxe.php create mode 100644 src/pocketmine/item/WoodenShovel.php create mode 100644 src/pocketmine/item/WoodenSword.php create mode 100644 src/pocketmine/level/Explosion.php create mode 100644 src/pocketmine/level/Level.php create mode 100644 src/pocketmine/level/LevelImport.php create mode 100644 src/pocketmine/level/PocketChunkParser.php create mode 100644 src/pocketmine/level/Position.php create mode 100644 src/pocketmine/level/WorldGenerator.php create mode 100644 src/pocketmine/level/generator/Flat.php create mode 100644 src/pocketmine/level/generator/Generator.php create mode 100644 src/pocketmine/level/generator/Normal.php create mode 100644 src/pocketmine/level/generator/noise/Generator.php create mode 100644 src/pocketmine/level/generator/noise/Perlin.php create mode 100644 src/pocketmine/level/generator/noise/Simplex.php create mode 100644 src/pocketmine/level/generator/object/BigTree.php create mode 100644 src/pocketmine/level/generator/object/Object.php create mode 100644 src/pocketmine/level/generator/object/Ore.php create mode 100644 src/pocketmine/level/generator/object/OreType.php create mode 100644 src/pocketmine/level/generator/object/PineTree.php create mode 100644 src/pocketmine/level/generator/object/Pond.php create mode 100644 src/pocketmine/level/generator/object/SmallTree.php create mode 100644 src/pocketmine/level/generator/object/SpruceTree.php create mode 100644 src/pocketmine/level/generator/object/TallGrass.php create mode 100644 src/pocketmine/level/generator/object/Tree.php create mode 100644 src/pocketmine/level/generator/populator/Mineshaft.php create mode 100644 src/pocketmine/level/generator/populator/Ore.php create mode 100644 src/pocketmine/level/generator/populator/Pond.php create mode 100644 src/pocketmine/level/generator/populator/Populator.php create mode 100644 src/pocketmine/level/generator/populator/TallGrass.php create mode 100644 src/pocketmine/level/generator/populator/Tree.php create mode 100644 src/pocketmine/math/AxisAlignedBB.php create mode 100644 src/pocketmine/math/Math.php create mode 100644 src/pocketmine/math/Matrix.php create mode 100644 src/pocketmine/math/Vector2.php create mode 100644 src/pocketmine/math/Vector3.php create mode 100644 src/pocketmine/math/VectorMath.php create mode 100644 src/pocketmine/nbt/NBT.php create mode 100644 src/pocketmine/nbt/tag/Byte.php create mode 100644 src/pocketmine/nbt/tag/Byte_Array.php create mode 100644 src/pocketmine/nbt/tag/Compound.php create mode 100644 src/pocketmine/nbt/tag/Double.php create mode 100644 src/pocketmine/nbt/tag/End.php create mode 100644 src/pocketmine/nbt/tag/Enum.php create mode 100644 src/pocketmine/nbt/tag/Float.php create mode 100644 src/pocketmine/nbt/tag/Int.php create mode 100644 src/pocketmine/nbt/tag/Int_Array.php create mode 100644 src/pocketmine/nbt/tag/Long.php create mode 100644 src/pocketmine/nbt/tag/NamedTag.php create mode 100644 src/pocketmine/nbt/tag/Short.php create mode 100644 src/pocketmine/nbt/tag/String.php create mode 100644 src/pocketmine/nbt/tag/Tag.php create mode 100644 src/pocketmine/network/Packet.php create mode 100644 src/pocketmine/network/ThreadedHandler.php create mode 100644 src/pocketmine/network/protocol/AddEntityPacket.php create mode 100644 src/pocketmine/network/protocol/AddItemEntityPacket.php create mode 100644 src/pocketmine/network/protocol/AddMobPacket.php create mode 100644 src/pocketmine/network/protocol/AddPaintingPacket.php create mode 100644 src/pocketmine/network/protocol/AddPlayerPacket.php create mode 100644 src/pocketmine/network/protocol/AdventureSettingsPacket.php create mode 100644 src/pocketmine/network/protocol/AnimatePacket.php create mode 100644 src/pocketmine/network/protocol/ChatPacket.php create mode 100644 src/pocketmine/network/protocol/ChunkDataPacket.php create mode 100644 src/pocketmine/network/protocol/ClientConnectPacket.php create mode 100644 src/pocketmine/network/protocol/ClientHandshakePacket.php create mode 100644 src/pocketmine/network/protocol/ContainerClosePacket.php create mode 100644 src/pocketmine/network/protocol/ContainerOpenPacket.php create mode 100644 src/pocketmine/network/protocol/ContainerSetContentPacket.php create mode 100644 src/pocketmine/network/protocol/ContainerSetDataPacket.php create mode 100644 src/pocketmine/network/protocol/ContainerSetSlotPacket.php create mode 100644 src/pocketmine/network/protocol/DataPacket.php create mode 100644 src/pocketmine/network/protocol/DisconnectPacket.php create mode 100644 src/pocketmine/network/protocol/DropItemPacket.php create mode 100644 src/pocketmine/network/protocol/EntityDataPacket.php create mode 100644 src/pocketmine/network/protocol/EntityEventPacket.php create mode 100644 src/pocketmine/network/protocol/ExplodePacket.php create mode 100644 src/pocketmine/network/protocol/HurtArmorPacket.php create mode 100644 src/pocketmine/network/protocol/Info.php create mode 100644 src/pocketmine/network/protocol/InteractPacket.php create mode 100644 src/pocketmine/network/protocol/LevelEventPacket.php create mode 100644 src/pocketmine/network/protocol/LoginPacket.php create mode 100644 src/pocketmine/network/protocol/LoginStatusPacket.php create mode 100644 src/pocketmine/network/protocol/MessagePacket.php create mode 100644 src/pocketmine/network/protocol/MoveEntityPacket.php create mode 100644 src/pocketmine/network/protocol/MoveEntityPacket_PosRot.php create mode 100644 src/pocketmine/network/protocol/MovePlayerPacket.php create mode 100644 src/pocketmine/network/protocol/PingPacket.php create mode 100644 src/pocketmine/network/protocol/PlayerActionPacket.php create mode 100644 src/pocketmine/network/protocol/PlayerArmorEquipmentPacket.php create mode 100644 src/pocketmine/network/protocol/PlayerEquipmentPacket.php create mode 100644 src/pocketmine/network/protocol/PongPacket.php create mode 100644 src/pocketmine/network/protocol/ReadyPacket.php create mode 100644 src/pocketmine/network/protocol/RemoveBlockPacket.php create mode 100644 src/pocketmine/network/protocol/RemoveEntityPacket.php create mode 100644 src/pocketmine/network/protocol/RemovePlayerPacket.php create mode 100644 src/pocketmine/network/protocol/RequestChunkPacket.php create mode 100644 src/pocketmine/network/protocol/RespawnPacket.php create mode 100644 src/pocketmine/network/protocol/RotateHeadPacket.php create mode 100644 src/pocketmine/network/protocol/SendInventoryPacket.php create mode 100644 src/pocketmine/network/protocol/ServerHandshakePacket.php create mode 100644 src/pocketmine/network/protocol/SetEntityDataPacket.php create mode 100644 src/pocketmine/network/protocol/SetEntityMotionPacket.php create mode 100644 src/pocketmine/network/protocol/SetHealthPacket.php create mode 100644 src/pocketmine/network/protocol/SetSpawnPositionPacket.php create mode 100644 src/pocketmine/network/protocol/SetTimePacket.php create mode 100644 src/pocketmine/network/protocol/StartGamePacket.php create mode 100644 src/pocketmine/network/protocol/TakeItemEntityPacket.php create mode 100644 src/pocketmine/network/protocol/TileEventPacket.php create mode 100644 src/pocketmine/network/protocol/UnknownPacket.php create mode 100644 src/pocketmine/network/protocol/UpdateBlockPacket.php create mode 100644 src/pocketmine/network/protocol/UseItemPacket.php create mode 100644 src/pocketmine/network/query/QueryHandler.php create mode 100644 src/pocketmine/network/query/QueryPacket.php create mode 100644 src/pocketmine/network/raknet/Info.php create mode 100644 src/pocketmine/network/raknet/Packet.php create mode 100644 src/pocketmine/network/rcon/RCON.php create mode 100644 src/pocketmine/network/rcon/RCONInstance.php create mode 100644 src/pocketmine/network/upnp/UPnP.php create mode 100644 src/pocketmine/permission/BanEntry.php create mode 100644 src/pocketmine/permission/BanList.php create mode 100644 src/pocketmine/permission/DefaultPermissions.php create mode 100644 src/pocketmine/permission/Permissible.php create mode 100644 src/pocketmine/permission/PermissibleBase.php create mode 100644 src/pocketmine/permission/Permission.php create mode 100644 src/pocketmine/permission/PermissionAttachment.php create mode 100644 src/pocketmine/permission/PermissionAttachmentInfo.php create mode 100644 src/pocketmine/permission/PermissionRemovedExecutor.php create mode 100644 src/pocketmine/permission/ServerOperator.php create mode 100644 src/pocketmine/plugin/EventExecutor.php create mode 100644 src/pocketmine/plugin/FolderPluginLoader.php create mode 100644 src/pocketmine/plugin/MethodEventExecutor.php create mode 100644 src/pocketmine/plugin/PharPluginLoader.php create mode 100644 src/pocketmine/plugin/Plugin.php create mode 100644 src/pocketmine/plugin/PluginBase.php create mode 100644 src/pocketmine/plugin/PluginDescription.php create mode 100644 src/pocketmine/plugin/PluginLoadOrder.php create mode 100644 src/pocketmine/plugin/PluginLoader.php create mode 100644 src/pocketmine/plugin/PluginManager.php create mode 100644 src/pocketmine/plugin/RegisteredListener.php create mode 100644 src/pocketmine/pmf/LevelFormat.php create mode 100644 src/pocketmine/pmf/PMF.php create mode 100644 src/pocketmine/recipes/Crafting.php create mode 100644 src/pocketmine/recipes/Fuel.php create mode 100644 src/pocketmine/recipes/Smelt.php create mode 100644 src/pocketmine/scheduler/CallbackTask.php create mode 100644 src/pocketmine/scheduler/PluginTask.php create mode 100644 src/pocketmine/scheduler/ServerScheduler.php create mode 100644 src/pocketmine/scheduler/Task.php create mode 100644 src/pocketmine/scheduler/TaskHandler.php create mode 100644 src/pocketmine/scheduler/TaskQueue.php create mode 100644 src/pocketmine/scheduler/TickScheduler.php create mode 100644 src/pocketmine/tile/Chest.php create mode 100644 src/pocketmine/tile/Container.php create mode 100644 src/pocketmine/tile/Furnace.php create mode 100644 src/pocketmine/tile/Sign.php create mode 100644 src/pocketmine/tile/Spawnable.php create mode 100644 src/pocketmine/tile/Tile.php create mode 100644 src/pocketmine/utils/Cache.php create mode 100644 src/pocketmine/utils/Config.php create mode 100644 src/pocketmine/utils/Random.php create mode 100644 src/pocketmine/utils/TextFormat.php create mode 100644 src/pocketmine/utils/Utils.php create mode 100644 src/pocketmine/utils/VersionString.php create mode 100644 src/pocketmine/wizard/Installer.php create mode 100644 src/pocketmine/wizard/InstallerLang.php create mode 100644 src/spl/SplAutoloader.php create mode 100644 src/spl/SplClassLoader.php diff --git a/src/pocketmine/Achievement.php b/src/pocketmine/Achievement.php new file mode 100644 index 000000000..232a0a162 --- /dev/null +++ b/src/pocketmine/Achievement.php @@ -0,0 +1,134 @@ + array( + "name" => "Taking Inventory", + "requires" => array(), + ),*/ + "mineWood" => array( + "name" => "Getting Wood", + "requires" => array( //"openInventory", + ), + ), + "buildWorkBench" => array( + "name" => "Benchmarking", + "requires" => array( + "mineWood", + ), + ), + "buildPickaxe" => array( + "name" => "Time to Mine!", + "requires" => array( + "buildWorkBench", + ), + ), + "buildFurnace" => array( + "name" => "Hot Topic", + "requires" => array( + "buildPickaxe", + ), + ), + "acquireIron" => array( + "name" => "Acquire hardware", + "requires" => array( + "buildFurnace", + ), + ), + "buildHoe" => array( + "name" => "Time to Farm!", + "requires" => array( + "buildWorkBench", + ), + ), + "makeBread" => array( + "name" => "Bake Bread", + "requires" => array( + "buildHoe", + ), + ), + "bakeCake" => array( + "name" => "The Lie", + "requires" => array( + "buildHoe", + ), + ), + "buildBetterPickaxe" => array( + "name" => "Getting an Upgrade", + "requires" => array( + "buildPickaxe", + ), + ), + "buildSword" => array( + "name" => "Time to Strike!", + "requires" => array( + "buildWorkBench", + ), + ), + "diamonds" => array( + "name" => "DIAMONDS!", + "requires" => array( + "acquireIron", + ), + ), + + ); + + + public static function broadcast(Player $player, $achievementId){ + if(isset(Achievement::$list[$achievementId])){ + if(Server::getInstance()->getConfigString("announce-player-achievements", true) === true){ + Server::getInstance()->broadcastMessage($player->getDisplayName() . " has just earned the achievement " . TextFormat::GREEN . Achievement::$list[$achievementId]["name"]); + }else{ + $player->sendMessage("You have just earned the achievement " . TextFormat::GREEN . Achievement::$list[$achievementId]["name"]); + } + + return true; + } + + return false; + } + + public static function add($achievementId, $achievementName, array $requires = array()){ + if(!isset(Achievement::$list[$achievementId])){ + Achievement::$list[$achievementId] = array( + "name" => $achievementName, + "requires" => $requires, + ); + + return true; + } + + return false; + } + + +} diff --git a/src/pocketmine/BanAPI.php b/src/pocketmine/BanAPI.php new file mode 100644 index 000000000..05db4f2d3 --- /dev/null +++ b/src/pocketmine/BanAPI.php @@ -0,0 +1,411 @@ +server = Server::getInstance(); + } + + public function init(){ + $this->whitelist = new Config(\pocketmine\DATA . "white-list.txt", Config::ENUM); //Open whitelist list file + $this->bannedIPs = new Config(\pocketmine\DATA . "banned-ips.txt", Config::ENUM); //Open Banned IPs list file + $this->banned = new Config(\pocketmine\DATA . "banned.txt", Config::ENUM); //Open Banned Usernames list file + $this->ops = new Config(\pocketmine\DATA . "ops.txt", Config::ENUM); //Open list of OPs + $this->server->api->console->register("banip", " [IP|player]", array($this, "commandHandler")); + $this->server->api->console->register("ban", " [username]", array($this, "commandHandler")); + $this->server->api->console->register("kick", " [reason ...]", array($this, "commandHandler")); + $this->server->api->console->register("whitelist", " [username]", array($this, "commandHandler")); + $this->server->api->console->register("op", "", array($this, "commandHandler")); + $this->server->api->console->register("deop", "", array($this, "commandHandler")); + $this->server->api->console->register("sudo", " ", array($this, "commandHandler")); + $this->server->api->console->alias("ban-ip", "banip add"); + $this->server->api->console->alias("banlist", "ban list"); + $this->server->api->console->alias("pardon", "ban remove"); + $this->server->api->console->alias("pardon-ip", "banip remove"); + $this->server->addHandler("console.command", array($this, "permissionsCheck"), 1); //Event handler when commands are issued. Used to check permissions of commands that go through the server. + $this->server->addHandler("player.block.break", array($this, "permissionsCheck"), 1); //Event handler for blocks + $this->server->addHandler("player.block.place", array($this, "permissionsCheck"), 1); //Event handler for blocks + $this->server->addHandler("player.flying", array($this, "permissionsCheck"), 1); //Flying Event + } + + /** + * @param string $cmd Command to Whitelist + */ + public function cmdWhitelist($cmd){ //Whitelists a CMD so everyone can issue it - Even non OPs. + $this->cmdWhitelist[strtolower(trim($cmd))] = true; + } + + /** + * @param string $username + * + * @return boolean + */ + public function isOp($username){ //Is a player op? + $username = strtolower($username); + if($this->server->api->dhandle("op.check", $username) === true){ + return true; + }elseif($this->ops->exists($username)){ + return true; + } + + return false; + } + + /** + * @param mixed $data + * @param string $event + * + * @return boolean + */ + public function permissionsCheck($data, $event){ + switch($event){ + case "player.flying": //OPs can fly around the server. + if($this->isOp($data->getName())){ + return true; + } + break; + case "player.block.break": + case "player.block.place": //Spawn protection detection. Allows OPs to place/break blocks in the spawn area. + if(!$this->isOp($data["player"]->getName())){ + $t = new Vector2($data["target"]->x, $data["target"]->z); + $s = new Vector2(Level::getDefault()->getSpawn()->x, Level::getDefault()->getSpawn()->z); + if($t->distance($s) <= $this->server->api->getProperty("spawn-protection") and $this->server->api->dhandle($event . ".spawn", $data) !== true){ + return false; + } + } + + return; + case "console.command": //Checks if a command is allowed with the current user permissions. + if(isset($this->cmdWhitelist[$data["cmd"]])){ + return; + } + + if($data["issuer"] instanceof Player){ + if($this->server->api->handle("console.check", $data) === true or $this->isOp($data["issuer"]->getName())){ + return; + } + }elseif($data["issuer"] === "console" or $data["issuer"] === "rcon"){ + return; + } + + return false; + } + } + + /** + * @param string $cmd + * @param array $params + * @param string $issuer + * @param string $alias + * + * @return string + */ + public function commandHandler($cmd, $params, $issuer, $alias){ + $output = ""; + switch($cmd){ + case "sudo": + $target = strtolower(array_shift($params)); + $player = Player::get($target); + if(!($player instanceof Player)){ + $output .= "Player not connected.\n"; + break; + } + $this->server->api->console->run(implode(" ", $params), $player); + $output .= "Command ran as " . $player->getName() . ".\n"; + break; + case "op": + $user = strtolower($params[0]); + if($user == null){ + $output .= "Usage: /op \n"; + break; + } + $player = Player::get($user); + if(!($player instanceof Player)){ + $this->ops->set($user); + $this->ops->save(); + $output .= $user . " is now op\n"; + break; + } + $this->ops->set(strtolower($player->getName())); + $this->ops->save(); + $output .= $player->getName() . " is now op\n"; + $player->sendMessage("You are now op."); + break; + case "deop": + $user = strtolower($params[0]); + $player = Player::get($user); + if(!($player instanceof Player)){ + $this->ops->remove($user); + $this->ops->save(); + $output .= $user . " is no longer op\n"; + break; + } + $this->ops->remove(strtolower($player->getName())); + $this->ops->save(); + $output .= $player->getName() . " is no longer op\n"; + $player->sendMessage("You are no longer op."); + break; + case "kick": + if(!isset($params[0])){ + $output .= "Usage: /kick [reason ...]\n"; + }else{ + $name = strtolower(array_shift($params)); + $player = Player::get($name); + if($player === false){ + $output .= "Player \"" . $name . "\" does not exist\n"; + }else{ + $reason = implode(" ", $params); + $reason = $reason == "" ? "No reason" : $reason; + + $this->server->schedule(60, array($player, "close"), "You have been kicked: " . $reason); //Forces a kick + $player->blocked = true; + if($issuer instanceof Player){ + Player::broadcastMessage($player->getName() . " has been kicked by " . $issuer->getName() . ": $reason\n"); + }else{ + Player::broadcastMessage($player->getName() . " has been kicked: $reason\n"); + } + } + } + break; + case "whitelist": + $p = strtolower(array_shift($params)); + switch($p){ + case "remove": + $user = strtolower($params[0]); + $this->whitelist->remove($user); + $this->whitelist->save(); + $output .= "Player \"$user\" removed from white-list\n"; + break; + case "add": + $user = strtolower($params[0]); + $this->whitelist->set($user); + $this->whitelist->save(); + $output .= "Player \"$user\" added to white-list\n"; + break; + case "reload": + $this->whitelist = new Config(\pocketmine\DATA . "white-list.txt", Config::ENUM); + break; + case "list": + $output .= "White-list: " . implode(", ", $this->whitelist->getAll(true)) . "\n"; + break; + case "on": + case "true": + case "1": + $output .= "White-list turned on\n"; + $this->server->api->setProperty("white-list", true); + break; + case "off": + case "false": + case "0": + $output .= "White-list turned off\n"; + $this->server->api->setProperty("white-list", false); + break; + default: + $output .= "Usage: /whitelist [username]\n"; + break; + } + break; + case "banip": + $p = strtolower(array_shift($params)); + switch($p){ + case "pardon": + case "remove": + $ip = strtolower($params[0]); + $this->bannedIPs->remove($ip); + $this->bannedIPs->save(); + $output .= "IP \"$ip\" removed from ban list\n"; + break; + case "add": + case "ban": + $ip = strtolower($params[0]); + $player = Player::get($ip); + if($player instanceof Player){ + $ip = $player->getIP(); + $player->kick("You are banned"); + } + $this->bannedIPs->set($ip); + $this->bannedIPs->save(); + $output .= "IP \"$ip\" added to ban list\n"; + break; + case "reload": + $this->bannedIPs = new Config(\pocketmine\DATA . "banned-ips.txt", Config::ENUM); + break; + case "list": + $output .= "IP ban list: " . implode(", ", $this->bannedIPs->getAll(true)) . "\n"; + break; + default: + $output .= "Usage: /banip [IP|player]\n"; + break; + } + break; + case "ban": + $p = strtolower(array_shift($params)); + switch($p){ + case "pardon": + case "remove": + $user = strtolower($params[0]); + $this->banned->remove($user); + $this->banned->save(); + $output .= "Player \"$user\" removed from ban list\n"; + break; + case "add": + case "ban": + $user = strtolower($params[0]); + $this->banned->set($user); + $this->banned->save(); + $player = Player::get($user); + if($player !== false){ + $player->kick("You are banned"); + } + if($issuer instanceof Player){ + Player::broadcastMessage($user . " has been banned by " . $issuer->getName() . "\n"); + }else{ + Player::broadcastMessage($user . " has been banned\n"); + } + $this->kick($user, "Banned"); + $output .= "Player \"$user\" added to ban list\n"; + break; + case "reload": + $this->banned = new Config(\pocketmine\DATA . "banned.txt", Config::ENUM); + break; + case "list": + $output .= "Ban list: " . implode(", ", $this->banned->getAll(true)) . "\n"; + break; + default: + $output .= "Usage: /ban [username]\n"; + break; + } + break; + } + + return $output; + } + + /** + * @param string $username + */ + public function ban($username){ + $this->commandHandler("ban", array("add", $username), "console", ""); + } + + /** + * @param string $username + */ + public function pardon($username){ + $this->commandHandler("ban", array("pardon", $username), "console", ""); + } + + /** + * @param string $ip + */ + public function banIP($ip){ + $this->commandHandler("banip", array("add", $ip), "console", ""); + } + + /** + * @param string $ip + */ + public function pardonIP($ip){ + $this->commandHandler("banip", array("pardon", $ip), "console", ""); + } + + /** + * @param string $username + * @param string $reason + */ + public function kick($username, $reason = "No Reason"){ + $this->commandHandler("kick", array($username, $reason), "console", ""); + } + + public function reload(){ + $this->commandHandler("ban", array("reload"), "console", ""); + $this->commandHandler("banip", array("reload"), "console", ""); + $this->commandHandler("whitelist", array("reload"), "console", ""); + } + + /** + * @param string $ip + * + * @return boolean + */ + public function isIPBanned($ip){ + if($this->server->api->dhandle("api.ban.ip.check", $ip) === false){ + return true; + }elseif($this->bannedIPs->exists($ip, true)){ + return true; + }else{ + return false; + } + } + + /** + * @param string $username + * + * @return boolean + */ + public function isBanned($username){ + $username = strtolower($username); + if($this->server->api->dhandle("api.ban.check", $username) === false){ + return true; + }elseif($this->banned->exists($username, true)){ + return true; + }else{ + return false; + } + } + + /** + * @param string $username + * + * @return boolean + */ + public function inWhitelist($username){ + $username = strtolower($username); + if($this->isOp($username)){ + return true; + }elseif($this->server->api->dhandle("api.ban.whitelist.check", $username) === false){ + return true; + }elseif($this->whitelist->exists($username, true)){ + return true; + } + + return false; + } +} diff --git a/src/pocketmine/BlockAPI.php b/src/pocketmine/BlockAPI.php new file mode 100644 index 000000000..3077666fe --- /dev/null +++ b/src/pocketmine/BlockAPI.php @@ -0,0 +1,352 @@ +server = Server::getInstance(); + } + + public function init(){ + $this->server->schedule(1, array($this, "blockUpdateTick"), array(), true); + $this->server->api->console->register("give", " [amount]", array($this, "commandHandler")); + } + + public function commandHandler($cmd, $params, $issuer, $alias){ + $output = ""; + switch($cmd){ + case "give": + if(!isset($params[0]) or !isset($params[1])){ + $output .= "Usage: /give [amount]\n"; + break; + } + $player = Player::get($params[0]); + $item = Item::fromString($params[1]); + + if(!isset($params[2])){ + $item->setCount($item->getMaxStackSize()); + }else{ + $item->setCount((int) $params[2]); + } + + if($player instanceof Player){ + if(($player->gamemode & 0x01) === 0x01){ + $output .= "Player is in creative mode.\n"; + break; + } + if($item->getID() == 0){ + $output .= "You cannot give an air block to a player.\n"; + break; + } + $player->addItem(clone $item); + $output .= "Giving " . $item->getCount() . " of " . $item->getName() . " (" . $item->getID() . ":" . $item->getMetadata() . ") to " . $player->getName() . "\n"; + }else{ + $output .= "Unknown player.\n"; + } + + break; + } + + return $output; + } + + public function blockUpdateAround(Position $pos, $type = Level::BLOCK_UPDATE_NORMAL, $delay = false){ + if($delay !== false){ + $this->scheduleBlockUpdate($pos->getSide(0), $delay, $type); + $this->scheduleBlockUpdate($pos->getSide(1), $delay, $type); + $this->scheduleBlockUpdate($pos->getSide(2), $delay, $type); + $this->scheduleBlockUpdate($pos->getSide(3), $delay, $type); + $this->scheduleBlockUpdate($pos->getSide(4), $delay, $type); + $this->scheduleBlockUpdate($pos->getSide(5), $delay, $type); + }else{ + $this->blockUpdate($pos->getSide(0), $type); + $this->blockUpdate($pos->getSide(1), $type); + $this->blockUpdate($pos->getSide(2), $type); + $this->blockUpdate($pos->getSide(3), $type); + $this->blockUpdate($pos->getSide(4), $type); + $this->blockUpdate($pos->getSide(5), $type); + } + } + + public function blockUpdate(Position $pos, $type = Level::BLOCK_UPDATE_NORMAL){ + if(!($pos instanceof block\Block)){ + $block = $pos->level->getBlock($pos); + }else{ + $pos = new Position($pos->x, $pos->y, $pos->z, $pos->level); + $block = $pos->level->getBlock($pos); + } + if($block === false){ + return false; + } + + $level = $block->onUpdate($type); + if($level === Level::BLOCK_UPDATE_NORMAL){ + $this->blockUpdateAround($block, $level); + } + + return $level; + } + + public function scheduleBlockUpdate(Position $pos, $delay, $type = Level::BLOCK_UPDATE_SCHEDULED){ + $type = (int) $type; + if($delay < 0){ + return false; + } + + $index = $pos->x . "." . $pos->y . "." . $pos->z . "." . $pos->level->getName() . "." . $type; + $delay = microtime(true) + $delay * 0.05; + if(!isset($this->scheduledUpdates[$index])){ + $this->scheduledUpdates[$index] = $pos; + $this->server->query("INSERT INTO blockUpdates (x, y, z, level, type, delay) VALUES (" . $pos->x . ", " . $pos->y . ", " . $pos->z . ", '" . $pos->level->getName() . "', " . $type . ", " . $delay . ");"); + + return true; + } + + return false; + } + + public function blockUpdateTick(){ + $time = microtime(true); + if(count($this->scheduledUpdates) > 0){ + $update = $this->server->query("SELECT x,y,z,level,type FROM blockUpdates WHERE delay <= " . $time . ";"); + if($update instanceof \SQLite3Result){ + $upp = array(); + while(($up = $update->fetchArray(SQLITE3_ASSOC)) !== false){ + $index = $up["x"] . "." . $up["y"] . "." . $up["z"] . "." . $up["level"] . "." . $up["type"]; + if(isset($this->scheduledUpdates[$index])){ + $upp[] = array((int) $up["type"], $this->scheduledUpdates[$index]); + unset($this->scheduledUpdates[$index]); + } + } + $this->server->query("DELETE FROM blockUpdates WHERE delay <= " . $time . ";"); + foreach($upp as $b){ + $this->blockUpdate($b[1], $b[0]); + } + } + } + } + +} diff --git a/src/pocketmine/LevelAPI.php b/src/pocketmine/LevelAPI.php new file mode 100644 index 000000000..b3f97ee8a --- /dev/null +++ b/src/pocketmine/LevelAPI.php @@ -0,0 +1,66 @@ +server = Server::getInstance(); + } + + public function init(){ + $this->server->api->console->register("save-all", "", array($this, "commandHandler")); + $this->server->api->console->register("save-on", "", array($this, "commandHandler")); + $this->server->api->console->register("save-off", "", array($this, "commandHandler")); + } + + public function commandHandler($cmd, $params, $issuer, $alias){ + $output = ""; + switch($cmd){ + case "save-all": + $save = $this->server->saveEnabled; + $this->server->saveEnabled = true; + Level::saveAll(); + $this->server->saveEnabled = $save; + break; + case "save-on": + $this->server->saveEnabled = true; + break; + case "save-off": + $this->server->saveEnabled = false; + break; + } + + return $output; + } + + public function __destruct(){ + Level::saveAll(); + foreach(Level::getAll() as $level){ + $level->unload(true); + } + } + +} diff --git a/src/pocketmine/Player.php b/src/pocketmine/Player.php new file mode 100644 index 000000000..6fb0475f4 --- /dev/null +++ b/src/pocketmine/Player.php @@ -0,0 +1,2736 @@ +level->players[$this->CID] = $this; + parent::initEntity(); + } + + /** + * @param Player $player + */ + public function spawnTo(Player $player){ + if($this->spawned === true and $player->getLevel() === $this->getLevel() and $player->canSee($this)){ + parent::spawnTo($player); + } + } + + /** + * @param Player $player + */ + public function despawnFrom(Player $player){ + if($this->spawned === true){ + parent::despawnFrom($player); + } + } + + /** + * @return Server + */ + public function getServer(){ + return $this->server; + } + + /** + * @param Player $player + * + * @return bool + */ + public function canSee(Player $player){ + return !isset($this->hiddenPlayers[$player->getName()]); + } + + /** + * @param Player $player + */ + public function hidePlayer(Player $player){ + if($player === $this){ + return; + } + $this->hiddenPlayers[$player->getName()] = $player; + $player->despawnFrom($this); + } + + /** + * @param Player $player + */ + public function showPlayer(Player $player){ + if($player === $this){ + return; + } + unset($this->hiddenPlayers[$player->getName()]); + $player->spawnTo($this); + } + + /** + * @return bool + */ + public function isOnline(){ + return $this->connected === true and $this->loggedIn === true; + } + + /** + * @return bool + */ + public function isOp(){ + return $this->server->isOp($this->getName()); + } + + /** + * @param bool $value + */ + public function setOp($value){ + if($value === $this->isOp()){ + return; + } + + if($value === true){ + $this->server->addOp($this->getName()); + }else{ + $this->server->removeOp($this->getName()); + } + + $this->perm->recalculatePermissions(); + } + + /** + * @param permission\Permission|string $name + * + * @return bool + */ + public function isPermissionSet($name){ + return $this->perm->isPermissionSet($name); + } + + /** + * @param permission\Permission|string $name + * + * @return bool + */ + public function hasPermission($name){ + return $this->perm->hasPermission($name); + } + + /** + * @param Plugin $plugin + * @param string $name + * @param bool $value + * + * @return permission\PermissionAttachment + */ + public function addAttachment(Plugin $plugin, $name = null, $value = null){ + return $this->perm->addAttachment($plugin, $name, $value); + } + + /** + * @param PermissionAttachment $attachment + */ + public function removeAttachment(PermissionAttachment $attachment){ + $this->perm->removeAttachment($attachment); + } + + public function recalculatePermissions(){ + $this->perm->recalculatePermissions(); + } + + /** + * @return permission\PermissionAttachmentInfo[] + */ + public function getEffectivePermissions(){ + return $this->perm->getEffectivePermissions(); + } + + + /** + * @param integer $clientID + * @param string $ip + * @param integer $port + * @param integer $MTU + */ + public function __construct($clientID, $ip, $port, $MTU){ + $this->perm = new PermissibleBase($this); + $this->namedtag = new Compound(); + $this->bigCnt = 0; + $this->MTU = $MTU; + $this->server = Server::getInstance(); + $this->lastBreak = microtime(true); + $this->clientID = $clientID; + $this->CID = $ip . ":" . $port; + Player::$list[$this->CID] = $this; + $this->ip = $ip; + $this->port = $port; + $this->spawnPosition = Level::getDefault()->getSafeSpawn(); + $this->timeout = microtime(true) + 20; + $this->gamemode = $this->server->getGamemode(); + $this->level = Level::getDefault(); + $this->viewDistance = $this->server->getViewDistance(); + $this->slot = 0; + $this->hotbar = array(0, -1, -1, -1, -1, -1, -1, -1, -1); + $this->packetStats = array(0, 0); + $this->buffer = new Packet(Info::DATA_PACKET_0); + $this->buffer->data = array(); + $this->tasks[] = $this->server->getScheduler()->scheduleRepeatingTask(new CallbackTask(array($this, "handlePacketQueues")), 1); + $this->tasks[] = $this->server->getScheduler()->scheduleRepeatingTask(new CallbackTask(array($this, "clearQueue")), 20 * 60); + console("[DEBUG] New Session started with " . $ip . ":" . $port . ". MTU " . $this->MTU . ", Client ID " . $this->clientID, true, true, 2); + } + + /** + * @param string $achievementId + */ + public function removeAchievement($achievementId){ + if($this->hasAchievement($achievementId)){ + $this->achievements[$achievementId] = false; + } + } + + /** + * @param string $achievementId + * + * @return bool + */ + public function hasAchievement($achievementId){ + if(!isset(Achievement::$list[$achievementId]) or !isset($this->achievements)){ + $this->achievements = array(); + + return false; + } + + if(!isset($this->achievements[$achievementId]) or $this->achievements[$achievementId] == false){ + return false; + } + + return true; + } + + /** + * @return bool + */ + public function isConnected(){ + return $this->connected === true; + } + + /** + * Gets the "friendly" name to display of this player to use in the chat. + * + * @return string + */ + public function getDisplayName(){ + return $this->displayName; + } + + /** + * @param string $name + */ + public function setDisplayName($name){ + $this->displayName = $name; + } + + /** + * Gets the player IP address + * + * @return string + */ + public function getAddress(){ + return $this->ip; + } + + /** + * @return int + */ + public function getPort(){ + return $this->port; + } + + /** + * @return bool + */ + public function isSleeping(){ + return $this->sleeping instanceof Vector3; + } + + /** + * Sets the chunk send flags for a specific index + * + * WARNING: Do not use this, it's only for internal use. + * Changes to this function won't be recorded on the version. + * + * @param int $index + * @param int $flags + */ + public function setChunkIndex($index, $flags){ + if(isset($this->chunksLoaded[$index])){ + $this->chunksLoaded[$index] |= $flags; + } + } + + /** + * @return Position + */ + public function getSpawn(){ + return $this->spawnPosition; + } + + /** + * Sends, if available, the next ordered chunk to the client + * + * WARNING: Do not use this, it's only for internal use. + * Changes to this function won't be recorded on the version. + * + * @param bool $force + * @param bool $ev + * + * @return bool|void + */ + public function getNextChunk($force = false, $ev = null){ + if($this->connected === false){ + return; + } + + if($ev === true){ + --$this->chunkScheduled; + if($this->chunkScheduled < 0){ + $this->chunkScheduled = 0; + } + } + + foreach($this->chunkCount as $count => $t){ + if(isset($this->recoveryQueue[$count]) or isset($this->resendQueue[$count])){ + if($this->chunkScheduled === 0){ + $this->server->getScheduler()->scheduleDelayedTask(new CallbackTask(array($this, "getNextChunk"), array(false, true)), MAX_CHUNK_RATE); + ++$this->chunkScheduled; + } + + return; + }else{ + unset($this->chunkCount[$count]); + } + } + + if(is_array($this->lastChunk)){ + foreach($this->level->getChunkEntities($this->lastChunk[0], $this->lastChunk[1]) as $entity){ + if($entity !== $this){ + $entity->spawnTo($this); + } + } + foreach($this->level->getChunkTiles($this->lastChunk[0], $this->lastChunk[1]) as $tile){ + if($tile instanceof Spawnable){ + $tile->spawnTo($this); + } + } + $this->lastChunk = false; + } + + $index = key($this->chunksOrder); + $distance = @$this->chunksOrder[$index]; + if($index === null or $distance === null){ + if($this->chunkScheduled === 0){ + $this->server->getScheduler()->scheduleDelayedTask(new CallbackTask(array($this, "getNextChunk"), array(false, true)), 60); + } + + return false; + } + $X = null; + $Z = null; + LevelFormat::getXZ($index, $X, $Z); + if(!$this->level->isChunkPopulated($X, $Z)){ + $this->orderChunks(); + if($this->chunkScheduled === 0 or $force === true){ + $this->server->getScheduler()->scheduleDelayedTask(new CallbackTask(array($this, "getNextChunk"), array(false, true)), MAX_CHUNK_RATE); + ++$this->chunkScheduled; + } + + return false; + } + unset($this->chunksOrder[$index]); + if(!isset($this->chunksLoaded[$index])){ + $this->chunksLoaded[$index] = 0xff; + } + $Yndex = $this->chunksLoaded[$index]; + $this->chunksLoaded[$index] = 0; //Load them all + $this->level->useChunk($X, $Z, $this); + $pk = new ChunkDataPacket; + $pk->chunkX = $X; + $pk->chunkZ = $Z; + $pk->data = $this->level->getOrderedChunk($X, $Z, $Yndex); + $cnt = $this->dataPacket($pk); + if($cnt === false){ + return false; + } + $this->chunkCount = array(); + foreach($cnt as $i => $count){ + $this->chunkCount[$count] = true; + } + + $this->lastChunk = array($X, $Z); + + if($this->chunkScheduled === 0 or $force === true){ + $this->server->getScheduler()->scheduleDelayedTask(new CallbackTask(array($this, "getNextChunk"), array(false, true)), MAX_CHUNK_RATE); + ++$this->chunkScheduled; + } + } + + /** + * + * WARNING: Do not use this, it's only for internal use. + * Changes to this function won't be recorded on the version. + * + * @return bool + */ + public function orderChunks(){ + if($this->connected === false){ + return false; + } + + $newOrder = array(); + $lastChunk = $this->chunksLoaded; + $centerX = $this->x >> 4; + $centerZ = $this->z >> 4; + $startX = $centerX - $this->viewDistance; + $startZ = $centerZ - $this->viewDistance; + $finalX = $centerX + $this->viewDistance; + $finalZ = $centerZ + $this->viewDistance; + for($X = $startX; $X <= $finalX; ++$X){ + for($Z = $startZ; $Z <= $finalZ; ++$Z){ + $distance = abs($X - $centerX) + abs($Z - $centerZ); + $index = LevelFormat::getIndex($X, $Z); + if(!isset($this->chunksLoaded[$index]) or $this->chunksLoaded[$index] !== 0){ + $newOrder[$index] = $distance; + } + unset($lastChunk[$index]); + } + } + asort($newOrder); + $this->chunksOrder = $newOrder; + + $index = key($this->chunksOrder); + LevelFormat::getXZ($index, $X, $Z); + $this->level->loadChunk($X, $Z); + if(!$this->level->isChunkPopulated($X, $Z)){ + $this->level->loadChunk($X - 1, $Z); + $this->level->loadChunk($X + 1, $Z); + $this->level->loadChunk($X, $Z - 1); + $this->level->loadChunk($X, $Z + 1); + $this->level->loadChunk($X + 1, $Z + 1); + $this->level->loadChunk($X + 1, $Z - 1); + $this->level->loadChunk($X - 1, $Z - 1); + $this->level->loadChunk($X - 1, $Z + 1); + } + + foreach($lastChunk as $index => $Yndex){ + if($Yndex !== 0xff){ + $X = null; + $Z = null; + LevelFormat::getXZ($index, $X, $Z); + foreach($this->level->getChunkEntities($X, $Z) as $entity){ + if($entity !== $this){ + $entity->despawnFrom($this); + } + } + } + unset($this->chunksLoaded[$index]); + } + } + + /** + * Sends an ordered DataPacket to the send buffer + * + * @param DataPacket $packet + * + * @return array|bool + */ + public function dataPacket(DataPacket $packet){ + if($this->connected === false){ + return false; + } + $this->server->getPluginManager()->callEvent($ev = new event\server\DataPacketSendEvent($this, $packet)); + if($ev->isCancelled()){ + return false; + } + + $packet->encode(); + $len = strlen($packet->buffer) + 1; + $MTU = $this->MTU - 24; + if($len > $MTU){ + return $this->directBigRawPacket($packet); + } + + if(($this->bufferLen + $len) >= $MTU){ + $this->sendBuffer(); + } + + $packet->messageIndex = $this->counter[3]++; + $packet->reliability = 2; + @$this->buffer->data[] = $packet; + $this->bufferLen += 6 + $len; + + return array(); + } + + private function directBigRawPacket(DataPacket $packet){ + if($this->connected === false){ + return false; + } + + $sendtime = microtime(true); + + $size = $this->MTU - 34; + $buffer = str_split($packet->buffer, $size); + $bigCnt = $this->bigCnt; + $this->bigCnt = ($this->bigCnt + 1) % 0x10000; + $cnts = array(); + $bufCount = count($buffer); + foreach($buffer as $i => $buf){ + $cnts[] = $count = $this->counter[0]++; + + $pk = new UnknownPacket; + $pk->packetID = $packet->pid(); + $pk->reliability = 2; + $pk->hasSplit = true; + $pk->splitCount = $bufCount; + $pk->splitID = $bigCnt; + $pk->splitIndex = $i; + $pk->buffer = $buf; + $pk->messageIndex = $this->counter[3]++; + + $rk = new Packet(Info::DATA_PACKET_0); + $rk->data[] = $pk; + $rk->seqNumber = $count; + $rk->sendtime = $sendtime; + $this->recoveryQueue[$count] = $rk; + $this->send($rk); + } + + return $cnts; + } + + /** + * Sends a raw Packet to the conection + * + * WARNING: Do not use this, it's only for internal use. + * Changes to this function won't be recorded on the version. + * + * @param Packet $packet + */ + public function send(Packet $packet){ + if($this->connected === true){ + $packet->ip = $this->ip; + $packet->port = $this->port; + $this->bandwidthRaw += $this->server->sendPacket($packet); + } + } + + /** + * Forces sending the buffer + * + * WARNING: Do not use this, it's only for internal use. + * Changes to this function won't be recorded on the version. + */ + public function sendBuffer(){ + if($this->connected === true){ + if($this->bufferLen > 0 and $this->buffer instanceof Packet){ + $this->buffer->seqNumber = $this->counter[0]++; + $this->send($this->buffer); + } + $this->bufferLen = 0; + $this->buffer = new Packet(Info::DATA_PACKET_0); + $this->buffer->data = array(); + $this->nextBuffer = microtime(true) + 0.1; + } + } + + /** + * @param Vector3 $pos + * + * @return boolean + */ + public function sleepOn(Vector3 $pos){ + foreach($this->level->getPlayers() as $p){ + if($p->sleeping instanceof Vector3){ + if($pos->distance($p->sleeping) <= 0.1){ + return false; + } + } + } + $this->sleeping = $pos; + $this->teleport(new Position($pos->x + 0.5, $pos->y + 1, $pos->z + 0.5, $this->level)); + /*if($this->entity instanceof Entity){ + $this->updateMetadata(); + }*/ + $this->setSpawn($pos); + $this->server->getScheduler()->scheduleDelayedTask(new CallbackTask(array($this, "checkSleep")), 60); + + return true; + } + + /** + * Sets the spawnpoint of the player (and the compass direction) to a Vector3, or set it on another world with a Position object + * + * @param Vector3|Position $pos + */ + public function setSpawn(Vector3 $pos){ + if(!($pos instanceof Position)){ + $level = $this->level; + }else{ + $level = $pos->level; + } + $this->spawnPosition = new Position($pos->x, $pos->y, $pos->z, $level); + $pk = new SetSpawnPositionPacket; + $pk->x = (int) $this->spawnPosition->x; + $pk->y = (int) $this->spawnPosition->y; + $pk->z = (int) $this->spawnPosition->z; + $this->dataPacket($pk); + } + + public function stopSleep(){ + $this->sleeping = false; + //if($this->entity instanceof Entity){ + //$this->entity->updateMetadata(); + //} + } + + /** + * WARNING: Do not use this, it's only for internal use. + * Changes to this function won't be recorded on the version. + */ + public function checkSleep(){ + if($this->sleeping !== false){ + //TODO + if($this->server->api->time->getPhase($this->level) === "night"){ + foreach($this->level->getPlayers() as $p){ + if($p->sleeping === false){ + return; + } + } + $this->server->api->time->set("day", $this->level); + foreach($this->level->getPlayers() as $p){ + $p->stopSleep(); + } + } + } + + return; + } + + /*public function eventHandler($data, $event){ + switch($event){ + //TODO, obsolete + case "tile.update": + if($data->level === $this->level){ + if($data instanceof Furnace){ + foreach($this->windows as $id => $w){ + if($w === $data){ + $pk = new ContainerSetDataPacket; + $pk->windowid = $id; + $pk->property = 0; //Smelting + $pk->value = floor($data->namedtag->CookTime); + $this->dataPacket($pk); + + $pk = new ContainerSetDataPacket; + $pk->windowid = $id; + $pk->property = 1; //Fire icon + $pk->value = $data->namedtag->BurnTicks; + $this->dataPacket($pk); + } + } + } + } + break; + case "tile.container.slot": + if($data["tile"]->level === $this->level){ + foreach($this->windows as $id => $w){ + if($w === $data["tile"]){ + $pk = new ContainerSetSlotPacket; + $pk->windowid = $id; + $pk->slot = $data["slot"] + (isset($data["offset"]) ? $data["offset"] : 0); + $pk->item = $data["slotdata"]; + $this->dataPacket($pk); + } + } + } + break; + case "player.pickup": + if($data["eid"] === $this->id){ + $data["eid"] = 0; + $pk = new TakeItemEntityPacket; + $pk->eid = 0; + $pk->target = $data["entity"]->getID(); + $this->dataPacket($pk); + if(($this->gamemode & 0x01) === 0x00){ + //$this->addItem($data["entity"]->type, $data["entity"]->meta, $data["entity"]->stack); + } + switch($data["entity"]->type){ + case Item::WOOD: + $this->awardAchievement("mineWood"); + break; + case Item::DIAMOND: + $this->awardAchievement("diamond"); + break; + } + }elseif($data["entity"]->level === $this->level){ + $pk = new TakeItemEntityPacket; + $pk->eid = $data["eid"]; + $pk->target = $data["entity"]->getID(); + $this->dataPacket($pk); + } + break; + case "entity.animate": + if($data["eid"] === $this->id or $data["entity"]->level !== $this->level){ + break; + } + $pk = new AnimatePacket; + $pk->eid = $data["eid"]; + $pk->action = $data["action"]; //1 swing arm, + $this->dataPacket($pk); + break; + case "entity.metadata": + if($data->getID() === $this->id){ + $eid = 0; + }else{ + $eid = $data->getID(); + } + if($data->level === $this->level){ + $pk = new SetEntityDataPacket; + $pk->eid = $eid; + $pk->metadata = $data->getMetadata(); + $this->dataPacket($pk); + } + break; + case "entity.event": + if($data["entity"]->getID() === $this->id){ + $eid = 0; + }else{ + $eid = $data["entity"]->getID(); + } + if($data["entity"]->level === $this->level){ + $pk = new EntityEventPacket; + $pk->eid = $eid; + $pk->event = $data["event"]; + $this->dataPacket($pk); + } + break; + } + }*/ + + /** + * @param string $achievementId + * + * @return bool + */ + public function awardAchievement($achievementId){ + if(isset(Achievement::$list[$achievementId]) and !$this->hasAchievement($achievementId)){ + foreach(Achievement::$list[$achievementId]["requires"] as $requerimentId){ + if(!$this->hasAchievement($requerimentId)){ + return false; + } + } + $this->server->getPluginManager()->callEvent($ev = new event\player\PlayerAchievementAwardedEvent($this, $achievementId)); + if(!$ev->isCancelled()){ + $this->achievements[$achievementId] = true; + Achievement::broadcast($this, $achievementId); + + return true; + }else{ + return false; + } + } + + return false; + } + + /** + * @return int + */ + public function getGamemode(){ + return $this->gamemode; + } + + /** + * Sets the gamemode, and if needed, kicks the player + * TODO: Check if Mojang adds the ability to change gamemode without kicking players + * + * @param int $gm + * + * @return bool + */ + public function setGamemode($gm){ + if($gm < 0 or $gm > 3 or $this->gamemode === $gm){ + return false; + } + + $this->server->getPluginManager()->callEvent($ev = new event\player\PlayerGameModeChangeEvent($this, (int) $gm)); + if($ev->isCancelled()){ + return false; + } + + if(($this->gamemode & 0x01) === ($gm & 0x01)){ + $this->gamemode = $gm; + $this->sendMessage("Your gamemode has been changed to " . Server::getGamemodeString($this->getGamemode()) . ".\n"); + }else{ + $this->blocked = true; + $this->gamemode = $gm; + $this->sendMessage("Your gamemode has been changed to " . Server::getGamemodeString($this->getGamemode()) . ", you've to do a forced reconnect.\n"); + $this->server->getScheduler()->scheduleDelayedTask(new CallbackTask(array($this, "close"), array($this->username . " has left the game", "gamemode change")), 30); + + } + $this->sendSettings(); + + return true; + } + + /** + * Sends all the option flags + * + * WARNING: Do not use this, it's only for internal use. + * Changes to this function won't be recorded on the version. + * + * @param bool $nametags + */ + public function sendSettings($nametags = true){ + /* + bit mask | flag name + 0x00000001 world_inmutable + 0x00000002 - + 0x00000004 - + 0x00000008 - (autojump) + 0x00000010 - + 0x00000020 nametags_visible + 0x00000040 ? + 0x00000080 ? + 0x00000100 ? + 0x00000200 ? + 0x00000400 ? + 0x00000800 ? + 0x00001000 ? + 0x00002000 ? + 0x00004000 ? + 0x00008000 ? + 0x00010000 ? + 0x00020000 ? + 0x00040000 ? + 0x00080000 ? + 0x00100000 ? + 0x00200000 ? + 0x00400000 ? + 0x00800000 ? + 0x01000000 ? + 0x02000000 ? + 0x04000000 ? + 0x08000000 ? + 0x10000000 ? + 0x20000000 ? + 0x40000000 ? + 0x80000000 ? + */ + $flags = 0; + if(($this->gamemode & 0x02) === 0x02){ + $flags |= 0x01; //Do not allow placing/breaking blocks, adventure mode + } + + if($nametags !== false){ + $flags |= 0x20; //Show Nametags + } + + $pk = new AdventureSettingsPacket; + $pk->flags = $flags; + $this->dataPacket($pk); + } + + /** + * WARNING: Do not use this, it's only for internal use. + * Changes to this function won't be recorded on the version. + * + * @return bool + */ + public function measureLag(){ + if($this->connected === false){ + return false; + } + if($this->packetStats[1] > 2){ + $this->packetLoss = $this->packetStats[1] / max(1, $this->packetStats[0] + $this->packetStats[1]); + }else{ + $this->packetLoss = 0; + } + $this->packetStats = array(0, 0); + array_shift($this->bandwidthStats); + $this->bandwidthStats[] = $this->bandwidthRaw / max(0.00001, microtime(true) - $this->lastMeasure); + $this->bandwidthRaw = 0; + $this->lagStat = array_sum($this->lag) / max(1, count($this->lag)); + $this->lag = array(); + $this->sendBuffer(); + $this->lastMeasure = microtime(true); + } + + /** + * WARNING: Experimental method + * + * @return int + */ + public function getLag(){ + return $this->lagStat * 1000; + } + + /** + * WARNING: Experimental method + * + * @return int + */ + public function getPacketLoss(){ + return $this->packetLoss; + } + + /** + * WARNING: Experimental method + * + * @return float + */ + public function getBandwidth(){ + return array_sum($this->bandwidthStats) / max(1, count($this->bandwidthStats)); + } + + /** + * WARNING: Do not use this, it's only for internal use. + * Changes to this function won't be recorded on the version. + * + * @return bool + */ + public function clearQueue(){ + if($this->connected === false){ + return false; + } + ksort($this->received); + if(($cnt = count($this->received)) > self::MAX_QUEUE){ + foreach($this->received as $c => $t){ + unset($this->received[$c]); + --$cnt; + if($cnt <= self::MAX_QUEUE){ + break; + } + } + } + } + + /** + * WARNING: Do not use this, it's only for internal use. + * Changes to this function won't be recorded on the version. + * + * @return bool + */ + public function handlePacketQueues(){ + if($this->connected === false){ + return false; + } + $time = microtime(true); + if($time > $this->timeout){ + $this->close($this->username . " has left the game", "timeout"); + + return false; + } + + if(($ackCnt = count($this->ackQueue)) > 0){ + rsort($this->ackQueue); + $safeCount = (int) (($this->MTU - 1) / 4); + $packetCnt = (int) ($ackCnt / $safeCount + 1); + for($p = 0; $p < $packetCnt; ++$p){ + $pk = new Packet(Info::ACK); + $pk->packets = array(); + for($c = 0; $c < $safeCount; ++$c){ + if(($k = array_pop($this->ackQueue)) === null){ + break; + } + $pk->packets[] = $k; + } + $this->send($pk); + } + $this->ackQueue = array(); + } + + if(($receiveCnt = count($this->receiveQueue)) > 0){ + ksort($this->receiveQueue); + foreach($this->receiveQueue as $count => $packets){ + unset($this->receiveQueue[$count]); + foreach($packets as $p){ + if($p instanceof DataPacket and $p->hasSplit === false){ + if(isset($p->messageIndex) and $p->messageIndex !== false){ + if($p->messageIndex > $this->receiveCount){ + $this->receiveCount = $p->messageIndex; + }elseif($p->messageIndex !== 0){ + if(isset($this->received[$p->messageIndex])){ + continue; + } + switch($p->pid()){ + case 0x01: + case ProtocolInfo::PING_PACKET: + case ProtocolInfo::PONG_PACKET: + case ProtocolInfo::MOVE_PLAYER_PACKET: + case ProtocolInfo::REQUEST_CHUNK_PACKET: + case ProtocolInfo::ANIMATE_PACKET: + case ProtocolInfo::SET_HEALTH_PACKET: + continue; + } + } + $this->received[$p->messageIndex] = true; + } + $p->decode(); + $this->handleDataPacket($p); + } + } + } + } + + if($this->nextBuffer <= $time and $this->bufferLen > 0){ + $this->sendBuffer(); + } + + $limit = $time - 5; //max lag + foreach($this->recoveryQueue as $count => $data){ + if($data->sendtime > $limit){ + break; + } + unset($this->recoveryQueue[$count]); + $this->resendQueue[$count] = $data; + } + + if(($resendCnt = count($this->resendQueue)) > 0){ + foreach($this->resendQueue as $count => $data){ + unset($this->resendQueue[$count]); + $this->packetStats[1]++; + $this->lag[] = microtime(true) - $data->sendtime; + $data->sendtime = microtime(true); + $this->send($data); + $this->recoveryQueue[$count] = $data; + } + } + } + + /** + * Handles a Minecraft packet + * TODO: Separate all of this in handlers + * + * WARNING: Do not use this, it's only for internal use. + * Changes to this function won't be recorded on the version. + * + * @param DataPacket $packet + */ + public function handleDataPacket(DataPacket $packet){ + if($this->connected === false){ + return; + } + + $this->server->getPluginManager()->callEvent($ev = new event\server\DataPacketReceiveEvent($this, $packet)); + if($ev->isCancelled()){ + return; + } + + switch($packet->pid()){ + case 0x01: + break; + case ProtocolInfo::PONG_PACKET: + break; + case ProtocolInfo::PING_PACKET: + $pk = new PongPacket; + $pk->ptime = $packet->time; + $pk->time = abs(microtime(true) * 1000); + $this->directDataPacket($pk); + break; + case ProtocolInfo::DISCONNECT_PACKET: + $this->close($this->username . " has left the game", "client disconnect"); + break; + case ProtocolInfo::CLIENT_CONNECT_PACKET: + if($this->loggedIn === true){ + break; + } + $pk = new ServerHandshakePacket; + $pk->port = $this->port; + $pk->session = $packet->session; + $pk->session2 = Utils::readLong("\x00\x00\x00\x00\x04\x44\x0b\xa9"); + $this->dataPacket($pk); + break; + case ProtocolInfo::CLIENT_HANDSHAKE_PACKET: + if($this->loggedIn === true){ + break; + } + break; + case ProtocolInfo::LOGIN_PACKET: + if($this->loggedIn === true){ + break; + } + $this->username = $packet->username; + $this->displayName = $this->username; + $this->iusername = strtolower($this->username); + $this->loginData = array("clientId" => $packet->clientId, "loginData" => $packet->loginData); + + //TODO: op things + if(count(Player::$list) > $this->server->getMaxPlayers()){ + if($this->kick("server full") === true){ + return; + } + } + if($packet->protocol1 !== ProtocolInfo::CURRENT_PROTOCOL){ + if($packet->protocol1 < ProtocolInfo::CURRENT_PROTOCOL){ + $pk = new LoginStatusPacket; + $pk->status = 1; + $this->directDataPacket($pk); + }else{ + $pk = new LoginStatusPacket; + $pk->status = 2; + $this->directDataPacket($pk); + } + $this->close("", "Incorrect protocol #" . $packet->protocol1, false); + + return; + } + if(preg_match('#^[a-zA-Z0-9_]{3,16}$#', $this->username) == 0 or $this->username === "" or $this->iusername === "rcon" or $this->iusername === "console"){ + $this->close("", "Bad username"); + + return; + } + + $this->server->getPluginManager()->callEvent($ev = new event\player\PlayerPreLoginEvent($this, "Plugin reason")); + if($ev->isCancelled()){ + $this->close($ev->getKickMessage(), "Plugin reason"); + + return; + } + + if(!$this->server->isWhitelisted(strtolower($this->getName()))){ + $this->close($this->username . " has left the game", "Server is white-listed"); + + return; + }elseif($this->server->getNameBans()->isBanned(strtolower($this->getName())) or $this->server->getIPBans()->isBanned($this->getAddress())){ + $this->close($this->username . " has left the game", "You are banned"); + + return; + } + $this->server->getPluginManager()->subscribeToPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this); + $this->server->getPluginManager()->subscribeToPermission(Server::BROADCAST_CHANNEL_USERS, $this); + + $u = Player::get($this->iusername, false, true); + if(count($u) > 0){ + foreach($u as $p){ + if($p !== $this){ + $p->close($p->getDisplayName() . " has left the game", "logged in from another location"); + } + } + } + + $nbt = Player::getOffline($this->username); + if(!isset($nbt->NameTag)){ + $nbt->NameTag = new String("NameTag", $this->username); + }else{ + $nbt["NameTag"] = $this->username; + } + $this->gamemode = $nbt["playerGameType"] & 0x03; + if(($this->level = Level::get($nbt["Level"])) === false){ + $this->level = Level::getDefault(); + $nbt["Level"] = $this->level->getName(); + $nbt["Pos"][0] = $this->level->getSpawn()->x; + $nbt["Pos"][1] = $this->level->getSpawn()->y; + $nbt["Pos"][2] = $this->level->getSpawn()->z; + } + + if(!($nbt instanceof Compound)){ + $this->close($this->username . " has left the game", "invalid data"); + + return; + } + + $this->achievements = array(); + foreach($nbt->Achievements as $achievement){ + $this->achievements[$achievement->getName()] = $achievement->getValue() > 0 ? true : false; + } + + Player::saveOffline($this->username, $nbt); + parent::__construct($this->level, $nbt); + $this->loggedIn = true; + + if(($this->gamemode & 0x01) === 0x01){ + $this->slot = 0; + $this->hotbar[0] = 0; + }else{ + $this->slot = $this->hotbar[0]; + } + + $this->server->getPluginManager()->callEvent($ev = new event\player\PlayerLoginEvent($this, "Plugin reason")); + if($ev->isCancelled()){ + $this->close($ev->getKickMessage(), "Plugin reason"); + + return; + } + + $pk = new LoginStatusPacket; + $pk->status = 0; + $this->dataPacket($pk); + + $pk = new StartGamePacket; + $pk->seed = $this->level->getSeed(); + $pk->x = $this->x; + $pk->y = $this->y; + $pk->z = $this->z; + $pk->generator = 0; + $pk->gamemode = $this->gamemode & 0x01; + $pk->eid = 0; //Always use EntityID as zero for the actual player + $this->dataPacket($pk); + + + if(($level = Level::get($this->namedtag["SpawnLevel"])) !== false){ + $this->spawnPosition = new Position($this->namedtag["SpawnX"], $this->namedtag["SpawnY"], $this->namedtag["SpawnZ"], $level); + + $pk = new SetSpawnPositionPacket; + $pk->x = (int) $this->spawnPosition->x; + $pk->y = (int) $this->spawnPosition->y; + $pk->z = (int) $this->spawnPosition->z; + $this->dataPacket($pk); + } + + //TODO: new events, or remove them! + //$this->evid[] = $this->server->event("entity.animate", array($this, "eventHandler")); + //$this->evid[] = $this->server->event("entity.event", array($this, "eventHandler")); + //$this->evid[] = $this->server->event("entity.metadata", array($this, "eventHandler")); + //$this->evid[] = $this->server->event("player.pickup", array($this, "eventHandler")); + //$this->evid[] = $this->server->event("tile.container.slot", array($this, "eventHandler")); + //$this->evid[] = $this->server->event("tile.update", array($this, "eventHandler")); + $this->lastMeasure = microtime(true); + $this->tasks[] = $this->server->getScheduler()->scheduleRepeatingTask(new CallbackTask(array($this, "measureLag")), 50); + + console("[INFO] " . TextFormat::AQUA . $this->username . TextFormat::RESET . "[/" . $this->ip . ":" . $this->port . "] logged in with entity id " . $this->id . " at (" . $this->level->getName() . ", " . round($this->x, 4) . ", " . round($this->y, 4) . ", " . round($this->z, 4) . ")"); + + $this->server->getPluginManager()->callEvent(new event\player\PlayerJoinEvent($this, $this->username . " joined the game")); + + break; + case ProtocolInfo::READY_PACKET: + if($this->loggedIn === false){ + break; + } + switch($packet->status){ + case 1: //Spawn!! + if($this->spawned !== false){ + break; + } + //TODO + //$this->heal($this->data->get("health"), "spawn", true); + $this->spawned = true; + $this->spawnToAll(); + + $this->sendInventory(); + $this->sendSettings(); + $this->sendArmor(); + $this->tasks[] = $this->server->getScheduler()->scheduleDelayedTask(new CallbackTask(array($this, "orderChunks")), 30); + + $this->blocked = false; + + $pk = new SetTimePacket; + $pk->time = $this->level->getTime(); + $pk->started = $this->level->stopTime == false; + $this->dataPacket($pk); + + $pos = new Position($this->x, $this->y, $this->z, $this->level); + $pos = $this->level->getSafeSpawn($pos); + $this->server->getPluginManager()->callEvent($ev = new event\player\PlayerRespawnEvent($this, $pos)); + + $this->teleport($ev->getRespawnPosition()); + $this->sendBuffer(); + + break; + case 2: //Chunk loaded? + break; + } + break; + case ProtocolInfo::ROTATE_HEAD_PACKET: + if($this->spawned === false){ + break; + } + $this->setRotation($packet->yaw, $this->pitch); + break; + case ProtocolInfo::MOVE_PLAYER_PACKET: + if($this->spawned === false){ + break; + } + if($packet->messageIndex > $this->lastMovement){ + $this->lastMovement = $packet->messageIndex; + $newPos = new Vector3($packet->x, $packet->y, $packet->z); + if($this->forceMovement instanceof Vector3){ + if($this->forceMovement->distance($newPos) <= 0.7){ + $this->forceMovement = false; + }else{ + $this->setPosition($this->forceMovement); + } + } + /*$speed = $this->entity->getSpeedMeasure(); + if($this->blocked === true or ($this->server->api->getProperty("allow-flight") !== true and (($speed > 9 and ($this->gamemode & 0x01) === 0x00) or $speed > 20 or $this->entity->distance($newPos) > 7)) or $this->server->api->handle("player.move", $this->entity) === false){ + if($this->lastCorrect instanceof Vector3){ + $this->teleport($this->lastCorrect, $this->entity->yaw, $this->entity->pitch, false); + } + if($this->blocked !== true){ + console("[WARNING] ".$this->username." moved too quickly!"); + } + }else{*/ + $this->setPositionAndRotation($newPos, $packet->yaw, $packet->pitch); + //} + } + break; + case ProtocolInfo::PLAYER_EQUIPMENT_PACKET: + if($this->spawned === false){ + break; + } + + if($packet->slot === 0x28 or $packet->slot === 0){ //0 for 0.8.0 compatibility + $packet->slot = -1; //Air + }else{ + $packet->slot -= 9; //Get real block slot + } + + if(($this->gamemode & 0x01) === 1){ //Creative mode match + $packet->slot = false; + foreach(BlockAPI::$creative as $i => $d){ + if($d[0] === $packet->item and $d[1] === $packet->meta){ + $packet->slot = $i; + $item = Item::get($d[0], $d[1], 1); + break; + } + } + }else{ + $item = $this->getSlot($packet->slot); + } + + + if($packet->slot === false){ + $this->sendInventorySlot($packet->slot); + }else{ + $this->server->getPluginManager()->callEvent($ev = new event\player\PlayerItemHeldEvent($this, $item, $packet->slot, 0)); + if($ev->isCancelled()){ + $this->sendInventorySlot($packet->slot); + }elseif($item instanceof Item){ + $this->setEquipmentSlot(0, $packet->slot); + $this->setCurrentEquipmentSlot(0); + if(($this->gamemode & 0x01) === 0){ + if(!in_array($this->slot, $this->hotbar)){ + array_pop($this->hotbar); + array_unshift($this->hotbar, $this->slot); + } + } + } + } + + if($this->inAction === true){ + $this->inAction = false; + //$this->entity->updateMetadata(); + } + break; + case ProtocolInfo::REQUEST_CHUNK_PACKET: + break; + case ProtocolInfo::USE_ITEM_PACKET: + $blockVector = new Vector3($packet->x, $packet->y, $packet->z); + + if(($this->spawned === false or $this->blocked === true) and $packet->face >= 0 and $packet->face <= 5){ + $target = $this->level->getBlock($blockVector); + $block = $target->getSide($packet->face); + + $pk = new UpdateBlockPacket; + $pk->x = $target->x; + $pk->y = $target->y; + $pk->z = $target->z; + $pk->block = $target->getID(); + $pk->meta = $target->getMetadata(); + $this->dataPacket($pk); + + $pk = new UpdateBlockPacket; + $pk->x = $block->x; + $pk->y = $block->y; + $pk->z = $block->z; + $pk->block = $block->getID(); + $pk->meta = $block->getMetadata(); + $this->dataPacket($pk); + break; + } + $this->craftingItems = array(); + $this->toCraft = array(); + $packet->eid = $this->id; + + if($packet->face >= 0 and $packet->face <= 5){ //Use Block, place + if($this->inAction === true){ + $this->inAction = false; + //$this->entity->updateMetadata(); + } + + if($blockVector->distance($this) > 10){ + + }elseif(($this->gamemode & 0x01) === 1 and isset(BlockAPI::$creative[$this->slot]) and $packet->item === BlockAPI::$creative[$this->slot][0] and $packet->meta === BlockAPI::$creative[$this->slot][1]){ + $item = Item::get(BlockAPI::$creative[$this->slot][0], BlockAPI::$creative[$this->slot][1], 1); + if($this->level->useItemOn($blockVector, $item, $packet->face, $packet->fx, $packet->fy, $packet->fz, $this) === true){ + break; + } + }elseif($this->getSlot($this->slot)->getID() !== $packet->item or ($this->getSlot($this->slot)->isTool() === false and $this->getSlot($this->slot)->getMetadata() !== $packet->meta)){ + $this->sendInventorySlot($this->slot); + }else{ + $item = clone $this->getSlot($this->slot); + //TODO: Implement adventure mode checks + if($this->level->useItemOn($blockVector, $item, $packet->face, $packet->fx, $packet->fy, $packet->fz, $this) === true){ + $this->setSlot($this->slot, $item); + $this->sendInventorySlot($this->slot); + break; + } + } + $target = $this->level->getBlock($blockVector); + $block = $target->getSide($packet->face); + + $pk = new UpdateBlockPacket; + $pk->x = $target->x; + $pk->y = $target->y; + $pk->z = $target->z; + $pk->block = $target->getID(); + $pk->meta = $target->getMetadata(); + $this->dataPacket($pk); + + $pk = new UpdateBlockPacket; + $pk->x = $block->x; + $pk->y = $block->y; + $pk->z = $block->z; + $pk->block = $block->getID(); + $pk->meta = $block->getMetadata(); + $this->dataPacket($pk); + break; + }elseif($packet->face === 0xff){ + //TODO: add event + $this->inAction = true; + $this->startAction = microtime(true); + //$this->updateMetadata(); + } + break; + /*case ProtocolInfo::PLAYER_ACTION_PACKET: + if($this->spawned === false or $this->blocked === true){ + break; + } + $packet->eid = $this->id; + $this->craftingItems = array(); + $this->toCraft = array(); + + switch($packet->action){ + case 5: //Shot arrow + if($this->entity->inAction === true){ + if($this->getSlot($this->slot)->getID() === BOW){ + if($this->startAction !== false){ + $time = microtime(true) - $this->startAction; + $d = array( + "x" => $this->entity->x, + "y" => $this->entity->y + 1.6, + "z" => $this->entity->z, + ); + $e = $this->server->api->entity->add($this->level, ENTITY_OBJECT, OBJECT_ARROW, $d); + $e->yaw = $this->entity->yaw; + $e->pitch = $this->entity->pitch; + $rotation = ($this->entity->yaw - 90) % 360; + if($rotation < 0){ + $rotation = (360 + $rotation); + } + $rotation = ($rotation + 180); + if($rotation >= 360){ + $rotation = ($rotation - 360); + } + $X = 1; + $Z = 1; + $overturn = false; + if(0 <= $rotation and $rotation < 90){ + + }elseif(90 <= $rotation and $rotation < 180){ + $rotation -= 90; + $X = (-1); + $overturn = true; + }elseif(180 <= $rotation and $rotation < 270){ + $rotation -= 180; + $X = (-1); + $Z = (-1); + }elseif(270 <= $rotation and $rotation < 360){ + $rotation -= 270; + $Z = (-1); + $overturn = true; + } + $rad = deg2rad($rotation); + $pitch = (-($this->entity->pitch)); + $speed = 80; + $speedY = (sin(deg2rad($pitch)) * $speed); + $speedXZ = (cos(deg2rad($pitch)) * $speed); + if($overturn){ + $speedX = (sin($rad) * $speedXZ * $X); + $speedZ = (cos($rad) * $speedXZ * $Z); + } + else{ + $speedX = (cos($rad) * $speedXZ * $X); + $speedZ = (sin($rad) * $speedXZ * $Z); + } + $e->speedX = $speedX; + $e->speedZ = $speedZ; + $e->speedY = $speedY; + $e->spawnToAll(); + } + } + } + $this->startAction = false; + $this->entity->inAction = false; + $this->entity->updateMetadata(); + break; + case 6: //get out of the bed + $this->stopSleep(); + } + break;*/ + case ProtocolInfo::REMOVE_BLOCK_PACKET: + if($this->spawned === false or $this->blocked === true){ + break; + } + $this->craftingItems = array(); + $this->toCraft = array(); + + $vector = new Vector3($packet->x, $packet->y, $packet->z); + + + if(($this->gamemode & 0x01) === 1){ + $item = Item::get(BlockAPI::$creative[$this->slot][0], BlockAPI::$creative[$this->slot][1], 1); + }else{ + $item = clone $this->getSlot($this->slot); + } + + if(($drops = $this->level->useBreakOn($vector, $item)) !== true){ + if(($this->gamemode & 0x01) === 0){ + //TODO: drop items + $this->setSlot($this->slot, $item); + } + break; + } + $target = $this->level->getBlock($vector); + $pk = new UpdateBlockPacket; + $pk->x = $target->x; + $pk->y = $target->y; + $pk->z = $target->z; + $pk->block = $target->getID(); + $pk->meta = $target->getMetadata(); + $this->directDataPacket($pk); + break; + case ProtocolInfo::PLAYER_ARMOR_EQUIPMENT_PACKET: + if($this->spawned === false or $this->blocked === true){ + break; + } + $this->craftingItems = array(); + $this->toCraft = array(); + + for($i = 0; $i < 4; ++$i){ + $s = $packet->slots[$i]; + if($s === 0 or $s === 255){ + $s = Item::get(Item::AIR, 0, 0); + }else{ + $s = Item::get($s + 256, 0, 1); + } + $slot = $this->getArmorSlot($i); + if($slot->getID() !== Item::AIR and $s->getID() === Item::AIR){ + if($this->setArmorSlot($i, Item::get(Item::AIR, 0, 0)) === false){ + $this->sendArmor(); + $this->sendInventory(); + }else{ + $this->addItem($slot); + $packet->slots[$i] = 255; + } + }elseif($s->getID() !== Item::AIR and $slot->getID() === Item::AIR and ($sl = $this->hasItem($s, false)) !== false){ + if($this->setArmorSlot($i, $this->getSlot($sl)) === false){ + $this->sendArmor(); + $this->sendInventory(); + }else{ + $this->setSlot($sl, Item::get(Item::AIR, 0, 0)); + } + }elseif($s->getID() !== Item::AIR and $slot->getID() !== Item::AIR and ($slot->getID() !== $s->getID() or $slot->getMetadata() !== $s->getMetadata()) and ($sl = $this->hasItem($s, false)) !== false){ + if($this->setArmorSlot($i, $this->getSlot($sl)) === false){ + $this->sendArmor(); + $this->sendInventory(); + }else{ + $this->setSlot($sl, $slot); + } + }else{ + $packet->slots[$i] = 255; + } + + } + + if($this->inAction === true){ + $this->inAction = false; + //$this->entity->updateMetadata(); + } + break; + /*case ProtocolInfo::INTERACT_PACKET: + if($this->spawned === false){ + break; + } + $packet->eid = $this->id; + $data = array(); + $data["target"] = $packet->target; + $data["eid"] = $packet->eid; + $data["action"] = $packet->action; + $this->craftingItems = array(); + $this->toCraft = array(); + $target = Entity::get($packet->target); + if($target instanceof Entity and $this->gamemode !== VIEW and $this->blocked === false and ($target instanceof Entity) and $this->entity->distance($target) <= 8){ + $data["targetentity"] = $target; + $data["entity"] = $this->entity; + if($target instanceof Player and ($this->server->api->getProperty("pvp") == false or $this->server->difficulty <= 0 or ($target->player->gamemode & 0x01) === 0x01)){ + break; + }elseif($this->server->handle("player.interact", $data) !== false){ + $slot = $this->getSlot($this->slot); + switch($slot->getID()){ + case WOODEN_SWORD: + case GOLD_SWORD: + $damage = 4; + break; + case STONE_SWORD: + $damage = 5; + break; + case IRON_SWORD: + $damage = 6; + break; + case DIAMOND_SWORD: + $damage = 7; + break; + + case WOODEN_AXE: + case GOLD_AXE: + $damage = 3; + break; + case STONE_AXE: + $damage = 4; + break; + case IRON_AXE: + $damage = 5; + break; + case DIAMOND_AXE: + $damage = 6; + break; + + case WOODEN_PICKAXE: + case GOLD_PICKAXE: + $damage = 2; + break; + case STONE_PICKAXE: + $damage = 3; + break; + case IRON_PICKAXE: + $damage = 4; + break; + case DIAMOND_PICKAXE: + $damage = 5; + break; + + case WOODEN_SHOVEL: + case GOLD_SHOVEL: + $damage = 1; + break; + case STONE_SHOVEL: + $damage = 2; + break; + case IRON_SHOVEL: + $damage = 3; + break; + case DIAMOND_SHOVEL: + $damage = 4; + break; + + default: + $damage = 1;//$this->server->difficulty; + } + $target->harm($damage, $this->id); + if($slot->isTool() === true and ($this->gamemode & 0x01) === 0){ + if($slot->useOn($target) and $slot->getMetadata() >= $slot->getMaxDurability()){ + $this->setSlot($this->slot, new Item(AIR, 0, 0)); + } + } + } + } + + break;*/ + /*case ProtocolInfo::ANIMATE_PACKET: + if($this->spawned === false){ + break; + } + $packet->eid = $this->id; + $this->server->api->dhandle("entity.animate", array("eid" => $packet->eid, "entity" => $this->entity, "action" => $packet->action)); + break;*/ + case ProtocolInfo::RESPAWN_PACKET: + if($this->spawned === false or $this->dead === false){ + break; + } + $this->craftingItems = array(); + $this->toCraft = array(); + + $this->server->getPluginManager()->callEvent($ev = new event\player\PlayerRespawnEvent($this, $this->spawnPosition)); + + $this->teleport($ev->getRespawnPosition()); + //$this->entity->fire = 0; + //$this->entity->air = 300; + //$this->entity->setHealth(20, "respawn", true); + //$this->entity->updateMetadata(); + + $this->sendInventory(); + $this->blocked = false; + break; + case ProtocolInfo::SET_HEALTH_PACKET: //Not used + break; + /*case ProtocolInfo::ENTITY_EVENT_PACKET: + if($this->spawned === false or $this->blocked === true){ + break; + } + $this->craftingItems = array(); + $this->toCraft = array(); + $packet->eid = $this->id; + if($this->entity->inAction === true){ + $this->entity->inAction = false; + $this->entity->updateMetadata(); + } + switch($packet->event){ + case 9: //Eating + $items = array( + Item::APPLE => 4, + Item::MUSHROOM_STEW => 10, + Item::BEETROOT_SOUP => 10, + Item::BREAD => 5, + Item::RAW_PORKCHOP => 3, + Item::COOKED_PORKCHOP => 8, + Item::RAW_BEEF => 3, + Item::STEAK => 8, + Item::COOKED_CHICKEN => 6, + Item::RAW_CHICKEN => 2, + Item::MELON_SLICE => 2, + Item::GOLDEN_APPLE => 10, + Item::PUMPKIN_PIE => 8, + Item::CARROT => 4, + Item::POTATO => 1, + Item::BAKED_POTATO => 6, + //Item::COOKIE => 2, + //Item::COOKED_FISH => 5, + //Item::RAW_FISH => 2, + ); + $slot = $this->getSlot($this->slot); + if($this->entity->getHealth() < 20 and isset($items[$slot->getID()])){ + + $pk = new EntityEventPacket; + $pk->eid = 0; + $pk->event = 9; + $this->dataPacket($pk); + + $this->entity->heal($items[$slot->getID()], "eating"); + //--$slot->count; + if($slot->getCount() <= 0){ + $this->setSlot($this->slot, Item::get(AIR, 0, 0)); + } + if($slot->getID() === Item::MUSHROOM_STEW or $slot->getID() === Item::BEETROOT_SOUP){ + $this->addItem(Item::get(BOWL, 0, 1)); + } + } + break; + } + break;*/ + /*case ProtocolInfo::DROP_ITEM_PACKET: + if($this->spawned === false or $this->blocked === true){ + break; + } + $packet->eid = $this->id; + $packet->item = $this->getSlot($this->slot); + $this->craftingItems = array(); + $this->toCraft = array(); + $data = array(); + $data["eid"] = $packet->eid; + $data["unknown"] = $packet->unknown; + $data["item"] = $packet->item; + $data["player"] = $this; + if($this->blocked === false and $this->server->handle("player.drop", $data) !== false){ + $this->server->api->entity->drop(new Position($this->entity->x - 0.5, $this->entity->y, $this->entity->z - 0.5, $this->level), $packet->item); + $this->setSlot($this->slot, Item::get(AIR, 0, 0), false); + } + if($this->entity->inAction === true){ + $this->entity->inAction = false; + $this->entity->updateMetadata(); + } + break;*/ + case ProtocolInfo::MESSAGE_PACKET: + if($this->spawned === false){ + break; + } + $this->craftingItems = array(); + $this->toCraft = array(); + $packet->message = TextFormat::clean($packet->message); + if(trim($packet->message) != "" and strlen($packet->message) <= 255){ + $message = $packet->message; + $this->server->getPluginManager()->callEvent($ev = new event\player\PlayerCommandPreprocessEvent($this, $message)); + if($ev->isCancelled()){ + break; + } + if(substr($ev->getMessage(), 0, 1) === "/"){ //Command + $this->server->dispatchCommand($ev->getPlayer(), substr($ev->getMessage(), 1)); + }else{ + $this->server->getPluginManager()->callEvent($ev = new event\player\PlayerChatEvent($this, $ev->getMessage())); + if(!$ev->isCancelled()){ + $this->server->broadcastMessage(sprintf($ev->getFormat(), $ev->getPlayer()->getDisplayName(), $ev->getMessage()), $ev->getRecipients()); + } + } + } + break; + case ProtocolInfo::CONTAINER_CLOSE_PACKET: + if($this->spawned === false){ + break; + } + $this->craftingItems = array(); + $this->toCraft = array(); + if(isset($this->windows[$packet->windowid])){ + if(is_array($this->windows[$packet->windowid])){ + foreach($this->windows[$packet->windowid] as $ob){ + $pk = new TileEventPacket; + $pk->x = $ob->x; + $pk->y = $ob->y; + $pk->z = $ob->z; + $pk->case1 = 1; + $pk->case2 = 0; + Player::broadcastPacket($this->level->players, $pk); + } + }elseif($this->windows[$packet->windowid] instanceof Chest){ + $pk = new TileEventPacket; + $pk->x = $this->windows[$packet->windowid]->x; + $pk->y = $this->windows[$packet->windowid]->y; + $pk->z = $this->windows[$packet->windowid]->z; + $pk->case1 = 1; + $pk->case2 = 0; + Player::broadcastPacket($this->level->players, $pk); + } + } + unset($this->windows[$packet->windowid]); + + $pk = new ContainerClosePacket; + $pk->windowid = $packet->windowid; + $this->dataPacket($pk); + break; + case ProtocolInfo::CONTAINER_SET_SLOT_PACKET: + if($this->spawned === false or $this->blocked === true){ + break; + } + + if($this->lastCraft <= (microtime(true) - 1)){ + if(isset($this->toCraft[-1])){ + $this->toCraft = array(-1 => $this->toCraft[-1]); + }else{ + $this->toCraft = array(); + } + $this->craftingItems = array(); + } + + if($packet->windowid === 0){ + $craft = false; + $slot = $this->getSlot($packet->slot); + if($slot->getCount() >= $packet->item->getCount() and (($slot->getID() === $packet->item->getID() and $slot->getMetadata() === $packet->item->getMetadata()) or ($packet->item->getID() === Item::AIR and $packet->item->getCount() === 0)) and !isset($this->craftingItems[$packet->slot])){ //Crafting recipe + $use = Item::get($slot->getID(), $slot->getMetadata(), $slot->getCount() - $packet->item->getCount()); + $this->craftingItems[$packet->slot] = $use; + $craft = true; + }elseif($slot->getCount() <= $packet->item->getCount() and ($slot->getID() === Item::AIR or ($slot->getID() === $packet->item->getID() and $slot->getMetadata() === $packet->item->getMetadata()))){ //Crafting final + $craftItem = Item::get($packet->item->getID(), $packet->item->getMetadata(), $packet->item->getCount() - $slot->getCount()); + if(count($this->toCraft) === 0){ + $this->toCraft[-1] = 0; + } + $this->toCraft[$packet->slot] = $craftItem; + $craft = true; + }elseif(((count($this->toCraft) === 1 and isset($this->toCraft[-1])) or count($this->toCraft) === 0) and $slot->getCount() > 0 and $slot->getID() > Item::AIR and ($slot->getID() !== $packet->item->getID() or $slot->getMetadata() !== $packet->item->getMetadata())){ //Crafting final + $craftItem = Item::get($packet->item->getID(), $packet->item->getMetadata(), $packet->item->getCount()); + if(count($this->toCraft) === 0){ + $this->toCraft[-1] = 0; + } + $use = Item::get($slot->getID(), $slot->getMetadata(), $slot->getCount()); + $this->craftingItems[$packet->slot] = $use; + $this->toCraft[$packet->slot] = $craftItem; + $craft = true; + } + + if($craft === true){ + $this->lastCraft = microtime(true); + } + + if($craft === true and count($this->craftingItems) > 0 and count($this->toCraft) > 0 and ($recipe = $this->craftItems($this->toCraft, $this->craftingItems, $this->toCraft[-1])) !== true){ + if($recipe === false){ + $this->sendInventory(); + $this->toCraft = array(); + }else{ + $this->toCraft = array(-1 => $this->toCraft[-1]); + } + $this->craftingItems = array(); + } + }else{ + $this->toCraft = array(); + $this->craftingItems = array(); + } + if(!isset($this->windows[$packet->windowid])){ + break; + } + + if(is_array($this->windows[$packet->windowid])){ + $tiles = $this->windows[$packet->windowid]; + if($packet->slot >= 0 and $packet->slot < Chest::SLOTS){ + $tile = $tiles[0]; + $slotn = $packet->slot; + $offset = 0; + }elseif($packet->slot >= Chest::SLOTS and $packet->slot <= (Chest::SLOTS << 1)){ + $tile = $tiles[1]; + $slotn = $packet->slot - Chest::SLOTS; + $offset = Chest::SLOTS; + }else{ + break; + } + + $item = Item::get($packet->item->getID(), $packet->item->getMetadata(), $packet->item->getCount()); + + $slot = $tile->getSlot($slotn); + //TODO: container access events? + /*if($this->server->api->dhandle("player.container.slot", array( + "tile" => $tile, + "slot" => $packet->slot, + "offset" => $offset, + "slotdata" => $slot, + "itemdata" => $item, + "player" => $this, + )) === false + ){ + $pk = new ContainerSetSlotPacket; + $pk->windowid = $packet->windowid; + $pk->slot = $packet->slot; + $pk->item = $slot; + $this->dataPacket($pk); + break; + }*/ + if($item->getID() !== Item::AIR and $slot->getID() == $item->getID()){ + if($slot->getCount() < $item->getCount()){ + $it = clone $item; + $it->setCount($item->getCount() - $slot->getCount()); + if($this->removeItem($it) === false){ + $this->sendInventory(); + break; + } + }elseif($slot->getCount() > $item->getCount()){ + $it = clone $item; + $it->setCount($slot->getCount() - $item->getCount()); + $this->addItem($it); + } + }else{ + if($this->removeItem($item) === false){ + $this->sendInventory(); + break; + } + $this->addItem($slot); + } + $tile->setSlot($slotn, $item, true, $offset); + }else{ + $tile = $this->windows[$packet->windowid]; + if( + !($tile instanceof Chest or $tile instanceof Furnace) + or $packet->slot < 0 + or ( + $tile instanceof Chest + and $packet->slot >= Chest::SLOTS + ) or ( + $tile instanceof Furnace and $packet->slot >= Furnace::SLOTS + ) + ){ + break; + } + $item = Item::get($packet->item->getID(), $packet->item->getMetadata(), $packet->item->getCount()); + + $slot = $tile->getSlot($packet->slot); + //TODO: container access events? + /*if($this->server->api->dhandle("player.container.slot", array( + "tile" => $tile, + "slot" => $packet->slot, + "slotdata" => $slot, + "itemdata" => $item, + "player" => $this, + )) === false + ){ + $pk = new ContainerSetSlotPacket; + $pk->windowid = $packet->windowid; + $pk->slot = $packet->slot; + $pk->item = $slot; + $this->dataPacket($pk); + break; + }*/ + + if($tile instanceof Furnace and $packet->slot == 2){ + switch($slot->getID()){ + case Item::IRON_INGOT: + $this->awardAchievement("acquireIron"); + break; + } + } + + if($item->getID() !== Item::AIR and $slot->getID() == $item->getID()){ + if($slot->getCount() < $item->getCount()){ + $it = clone $item; + $it->setCount($item->getCount() - $slot->getCount()); + if($this->removeItem($it) === false){ + $this->sendInventory(); + break; + } + }elseif($slot->getCount() > $item->getCount()){ + $it = clone $item; + $it->setCount($slot->count - $item->count); + $this->addItem($it); + } + }else{ + if($this->removeItem($item) === false){ + $this->sendInventory(); + break; + } + $this->addItem($slot); + } + $tile->setSlot($packet->slot, $item); + } + break; + case ProtocolInfo::SEND_INVENTORY_PACKET: //TODO, Mojang, enable this ´^_^` + if($this->spawned === false){ + break; + } + break; + case ProtocolInfo::ENTITY_DATA_PACKET: + if($this->spawned === false or $this->blocked === true){ + break; + } + $this->craftingItems = array(); + $this->toCraft = array(); + $t = $this->level->getTile(new Vector3($packet->x, $packet->y, $packet->z)); + if($t instanceof Sign){ + if($t->namedtag->creator !== $this->username){ + $t->spawnTo($this); + }else{ + $nbt = new NBT(NBT::LITTLE_ENDIAN); + $nbt->read($packet->namedtag); + if($nbt->id !== Tile::SIGN){ + $t->spawnTo($this); + }else{ + $t->setText($nbt->Text1, $nbt->Text2, $nbt->Text3, $nbt->Text4); + } + } + } + break; + default: + console("[DEBUG] Unhandled " . $packet->pid() . " data packet for " . $this->username . " (" . $this->clientID . "): " . print_r($packet, true), true, true, 2); + break; + } + } + + /** + * Kicks a player from the server + * + * @param string $reason + * + * @return bool + */ + public function kick($reason = ""){ + $this->server->getPluginManager()->callEvent($ev = new event\player\PlayerKickEvent($this, $reason, "Kicked player " . $this->username . "." . ($reason !== "" ? " With reason: $reason" : ""))); + if(!$ev->isCancelled()){ + $this->sendMessage("You have been kicked. " . ($reason !== "" ? " Reason: $reason" : "") . "\n"); + $this->close($ev->getQuitMessage(), $reason); + + return true; + } + + return false; + } + + /** + * Sends a direct chat message to a player + * + * @param string $message + */ + public function sendMessage($message){ + $mes = explode("\n", $message); + foreach($mes as $m){ + if(preg_match_all('#@([@A-Za-z_]{1,})#', $m, $matches, PREG_OFFSET_CAPTURE) > 0){ + $offsetshift = 0; + foreach($matches[1] as $selector){ + if($selector[0]{0} === "@"){ //Escape! + $m = substr_replace($m, $selector[0], $selector[1] + $offsetshift - 1, strlen($selector[0]) + 1); + --$offsetshift; + continue; + } + switch(strtolower($selector[0])){ + case "player": + case "username": + $m = substr_replace($m, $this->username, $selector[1] + $offsetshift - 1, strlen($selector[0]) + 1); + $offsetshift += strlen($selector[0]) - strlen($this->username) + 1; + break; + } + } + } + + if($m !== ""){ + $pk = new MessagePacket; + $pk->author = ""; //Do not use this ;) + $pk->message = TextFormat::clean($m); //Colors not implemented :( + $this->dataPacket($pk); + } + } + } + + /** + * @param string $message Message to be broadcasted + * @param string $reason Reason showed in console + */ + public function close($message = "", $reason = "generic reason"){ + if($this->connected === true){ + unset($this->level->players[$this->CID]); + if($this->username != ""){ + $this->server->getPluginManager()->callEvent($ev = new event\player\PlayerQuitEvent($this, $message)); + if($this->loggedIn === true){ + parent::close(); + $this->save(); + } + } + + $this->sendBuffer(); + $this->directDataPacket(new DisconnectPacket); + unset(Player::$list[$this->CID]); + $this->connected = false; + $this->level->freeAllChunks($this); + $this->loggedIn = false; + foreach($this->tasks as $task){ + $task->cancel(); + } + $this->tasks = array(); + $this->recoveryQueue = array(); + $this->receiveQueue = array(); + $this->resendQueue = array(); + $this->ackQueue = array(); + if($this->username != "" and ($this->namedtag instanceof Compound)){ + Player::saveOffline($this->username, $this->namedtag); + } + if(isset($ev) and $this->username != "" and $this->spawned !== false and $ev->getQuitMessage() != ""){ + $this->server->broadcastMessage($ev->getQuitMessage()); + } + $this->server->getPluginManager()->unsubscribeFromPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this); + $this->server->getPluginManager()->unsubscribeFromPermission(Server::BROADCAST_CHANNEL_USERS, $this); + $this->spawned = false; + console("[INFO] " . TextFormat::AQUA . $this->username . TextFormat::RESET . "[/" . $this->ip . ":" . $this->port . "] logged out due to " . $reason); + $this->windows = array(); + $this->armor = array(); + $this->inventory = array(); + $this->chunksLoaded = array(); + $this->chunksOrder = array(); + $this->chunkCount = array(); + $this->craftingItems = array(); + $this->received = array(); + $this->buffer = null; + unset($this->buffer); + } + } + + /** + * Handles player data saving + */ + public function save(){ + parent::saveNBT(); + $this->namedtag["Level"] = $this->level->getName(); + $this->namedtag["SpawnLevel"] = $this->level->getName(); + $this->namedtag["SpawnX"] = (int) $this->spawnPosition->x; + $this->namedtag["SpawnY"] = (int) $this->spawnPosition->y; + $this->namedtag["SpawnZ"] = (int) $this->spawnPosition->z; + + foreach($this->achievements as $achievement => $status){ + $this->namedtag->Achievements[$achievement] = new Byte($achievement, $status === true ? 1 : 0); + } + + $this->namedtag["playerGameType"] = $this->gamemode; + + //$this->data->set("health", $this->getHealth()); + } + + /** + * Sends a Minecraft packet directly, bypassing the send buffers + * + * @param DataPacket $packet + * @param bool $recover + * + * @return array|bool + */ + public function directDataPacket(DataPacket $packet, $recover = true){ + if($this->connected === false){ + return false; + } + + $this->server->getPluginManager()->callEvent($ev = new event\server\DataPacketSendEvent($this, $packet)); + if($ev->isCancelled()){ + return array(); + } + $packet->encode(); + $pk = new Packet(Info::DATA_PACKET_0); + $pk->data[] = $packet; + $pk->seqNumber = $this->counter[0]++; + $pk->sendtime = microtime(true); + if($recover !== false){ + $this->recoveryQueue[$pk->seqNumber] = $pk; + } + + $this->send($pk); + + return array($pk->seqNumber); + } + + /** + * @return Player[] + */ + public static function getAll(){ + return Player::$list; + } + + /** + * Gets a player, or multiple + * + * @param string $name name/partial name to search + * @param bool $alike = true, if false, will only return exact matches + * @param bool $multiple = false, if true, will return an array with all the players that match + * + * @return Player[]|bool|Player + */ + public static function get($name, $alike = true, $multiple = false){ + $name = trim(strtolower($name)); + if($name === ""){ + return false; + } + $players = array(); + foreach(Player::$list as $player){ + if($multiple === false and $player->iusername === $name){ + return $player; + }elseif(strpos($player->iusername, $name) !== false){ + $players[$player->CID] = $player; + } + } + + if($multiple === false){ + if(count($players) > 0){ + return array_shift($players); + }else{ + return false; + } + }else{ + return $players; + } + } + + /** + * Gets or generates the NBT data for a player + * + * @param string $name + * + * @return Compound + */ + public static function getOffline($name){ + $server = Server::getInstance(); + $iname = strtolower($name); + if(!file_exists(Server::getInstance()->getDataPath() . "players/" . $iname . ".dat")){ + $spawn = Level::getDefault()->getSafeSpawn(); + $nbt = new Compound(false, array( + new Enum("Pos", array( + new Double(0, $spawn->x), + new Double(1, $spawn->y), + new Double(2, $spawn->z) + )), + new String("Level", Level::getDefault()->getName()), + new String("SpawnLevel", Level::getDefault()->getName()), + new Int("SpawnX", (int) $spawn->x), + new Int("SpawnY", (int) $spawn->y), + new Int("SpawnZ", (int) $spawn->z), + new Byte("SpawnForced", 1), //TODO + new Enum("Inventory", array()), + new Compound("Achievements", array()), + new Int("playerGameType", $server->getGamemode()), + new Enum("Motion", array( + new Double(0, 0.0), + new Double(1, 0.0), + new Double(2, 0.0) + )), + new Enum("Rotation", array( + new Float(0, 0.0), + new Float(1, 0.0) + )), + new Float("FallDistance", 0.0), + new Short("Fire", 0), + new Short("Air", 0), + new Byte("OnGround", 1), + new Byte("Invulnerable", 0), + new String("NameTag", $name), + )); + $nbt->Pos->setTagType(NBT::TAG_Double); + $nbt->Inventory->setTagType(NBT::TAG_Compound); + $nbt->Motion->setTagType(NBT::TAG_Double); + $nbt->Rotation->setTagType(NBT::TAG_Float); + if(file_exists(Server::getInstance()->getDataPath() . "players/" . $iname . ".yml")){ + $data = new Config(Server::getInstance()->getDataPath() . "players/" . $iname . ".yml", Config::YAML, array()); + $nbt["playerGameType"] = (int) $data->get("gamemode"); + $nbt["Level"] = $data->get("position")["level"]; + $nbt["Pos"][0] = $data->get("position")["x"]; + $nbt["Pos"][1] = $data->get("position")["y"]; + $nbt["Pos"][2] = $data->get("position")["z"]; + $nbt["SpawnLevel"] = $data->get("spawn")["level"]; + $nbt["SpawnX"] = (int) $data->get("spawn")["x"]; + $nbt["SpawnY"] = (int) $data->get("spawn")["y"]; + $nbt["SpawnZ"] = (int) $data->get("spawn")["z"]; + console("[NOTICE] Old Player data found for \"" . $iname . "\", upgrading profile"); + foreach($data->get("inventory") as $slot => $item){ + if(count($item) === 3){ + $nbt->Inventory[$slot + 9] = new Compound(false, array( + new Short("id", $item[0]), + new Short("Damage", $item[1]), + new Byte("Count", $item[2]), + new Byte("Slot", $slot + 9), + new Byte("TrueSlot", $slot + 9) + )); + } + } + foreach($data->get("hotbar") as $slot => $itemSlot){ + if(isset($nbt->Inventory[$itemSlot + 9])){ + $item = $nbt->Inventory[$itemSlot + 9]; + $nbt->Inventory[$slot] = new Compound(false, array( + new Short("id", $item->id), + new Short("Damage", $item->Damage), + new Byte("Count", $item->Count), + new Byte("Slot", $slot), + new Byte("TrueSlot", $item->TrueSlot) + )); + } + } + foreach($data->get("armor") as $slot => $item){ + if(count($item) === 2){ + $nbt->Inventory[$slot + 100] = new Compound(false, array( + new Short("id", $item[0]), + new Short("Damage", $item[1]), + new Byte("Count", 1), + new Byte("Slot", $slot + 100) + )); + } + } + foreach($data->get("achievements") as $achievement => $status){ + $nbt->Achievements[$achievement] = new Byte($achievement, $status == true ? 1 : 0); + } + unlink(Server::getInstance()->getDataPath() . "players/" . $iname . ".yml"); + }else{ + console("[NOTICE] Player data not found for \"" . $iname . "\", creating new profile"); + Player::saveOffline($name, $nbt); + } + + }else{ + $nbt = new NBT(NBT::BIG_ENDIAN); + $nbt->readCompressed(file_get_contents(Server::getInstance()->getDataPath() . "players/" . $iname . ".dat")); + $nbt = $nbt->getData(); + } + + return $nbt; + } + + /** + * Saves a compressed NBT Coumpound tag as a player data + * + * @param string $name + * @param Compound $nbtTag + */ + public static function saveOffline($name, Compound $nbtTag){ + $nbt = new NBT(NBT::BIG_ENDIAN); + $nbt->setData($nbtTag); + file_put_contents(Server::getInstance()->getDataPath() . "players/" . strtolower($name) . ".dat", $nbt->writeCompressed()); + } + + /** + * Gets the username + * + * @return string + */ + public function getName(){ + return $this->username; + } + + /** + * Broadcasts a Minecraft packet to a list of players + * + * @param Player[] $players + * @param DataPacket $packet + */ + public static function broadcastPacket(array $players, DataPacket $packet){ + foreach($players as $player){ + $player->dataPacket(clone $packet); + } + } + + /** + * @param Item[] $craft + * @param Item[] $recipe + * @param int $type + * + * @return array|bool + */ + public function craftItems(array $craft, array $recipe, $type){ + $craftItem = array(0, true, 0); + unset($craft[-1]); + foreach($craft as $item){ + if($item instanceof Item){ + $craftItem[0] = $item->getID(); + if($item->getMetadata() !== $craftItem[1] and $craftItem[1] !== true){ + $craftItem[1] = false; + }else{ + $craftItem[1] = $item->getMetadata(); + } + $craftItem[2] += $item->getCount(); + } + + } + + $recipeItems = array(); + foreach($recipe as $item){ + if(!isset($recipeItems[$item->getID()])){ + $recipeItems[$item->getID()] = array($item->getID(), $item->getMetadata(), $item->getCount()); + }else{ + if($item->getMetadata() !== $recipeItems[$item->getID()][1]){ + $recipeItems[$item->getID()][1] = false; + } + $recipeItems[$item->getID()][2] += $item->getCount(); + } + } + + $res = Crafting::canCraft($craftItem, $recipeItems, $type); + + if(!is_array($res) and $type === 1){ + $res2 = Crafting::canCraft($craftItem, $recipeItems, 0); + if(is_array($res2)){ + $res = $res2; + } + } + + if(is_array($res)){ + //TODO: CraftItemEvent + //$this->server->getPluginManager($ev = new CraftItemEvent($this, $recipe, $craft, $type)); + //if($ev->isCancelled()){ + // return false; + //} + + foreach($recipe as $slot => $item){ + $s = $this->getSlot($slot); + $s->setCount($s->getCount() - $item->getCount()); + if($s->getCount() <= 0){ + $this->setSlot($slot, Item::get(Item::AIR, 0, 0)); + } + } + foreach($craft as $slot => $item){ + $s = $this->getSlot($slot); + if($s->getCount() <= 0 or $s->getID() === Item::AIR){ + $this->setSlot($slot, Item::get($item->getID(), $item->getMetadata(), $item->getCount())); + }else{ + $this->setSlot($slot, Item::get($item->getID(), $item->getMetadata(), $s->getCount() + $item->getCount())); + } + + switch($item->getID()){ + case Item::WORKBENCH: + $this->awardAchievement("buildWorkBench"); + break; + case Item::WOODEN_PICKAXE: + $this->awardAchievement("buildPickaxe"); + break; + case Item::FURNACE: + $this->awardAchievement("buildFurnace"); + break; + case Item::WOODEN_HOE: + $this->awardAchievement("buildHoe"); + break; + case Item::BREAD: + $this->awardAchievement("makeBread"); + break; + case Item::CAKE: + $this->awardAchievement("bakeCake"); + $this->addItem(Item::get(Item::BUCKET, 0, 3)); + break; + case Item::STONE_PICKAXE: + case Item::GOLD_PICKAXE: + case Item::IRON_PICKAXE: + case Item::DIAMOND_PICKAXE: + $this->awardAchievement("buildBetterPickaxe"); + break; + case Item::WOODEN_SWORD: + $this->awardAchievement("buildSword"); + break; + case Item::DIAMOND: + $this->awardAchievement("diamond"); + break; + + } + } + } + + return $res; + } + + public function setSlot($slot, Item $item){ + parent::setSlot($slot, $item); + $this->sendInventorySlot($slot); + } + + /** + * Sends a single slot + * TODO: Check if Mojang has implemented this on Minecraft: PE 0.9.0 + * + * @param int $s + * + * @return bool + */ + public function sendInventorySlot($s){ + $this->sendInventory(); + + return; //TODO: Check if Mojang adds this + $s = (int) $s; + if(!isset($this->inventory[$s])){ + $pk = new ContainerSetSlotPacket; + $pk->windowid = 0; + $pk->slot = (int) $s; + $pk->item = Item::get(Item::AIR, 0, 0); + $this->dataPacket($pk); + } + + $slot = $this->inventory[$s]; + $pk = new ContainerSetSlotPacket; + $pk->windowid = 0; + $pk->slot = (int) $s; + $pk->item = $slot; + $this->dataPacket($pk); + + return true; + } + + /** + * Sends the full inventory + */ + public function sendInventory(){ + if(($this->gamemode & 0x01) === 1){ + return; + } + $hotbar = array(); + foreach($this->hotbar as $slot){ + $hotbar[] = $slot <= -1 ? -1 : $slot + 9; + } + + $pk = new ContainerSetContentPacket; + $pk->windowid = 0; + $pk->slots = $this->inventory; + $pk->hotbar = $hotbar; + $this->dataPacket($pk); + } + + /** + * Handles a RakNet Packet + * + * @param Packet $packet + */ + public function handlePacket(Packet $packet){ + if($this->connected === true){ + $this->timeout = microtime(true) + 20; + switch($packet->pid()){ + case Info::NACK: + foreach($packet->packets as $count){ + if(isset($this->recoveryQueue[$count])){ + $this->resendQueue[$count] =& $this->recoveryQueue[$count]; + $this->lag[] = microtime(true) - $this->recoveryQueue[$count]->sendtime; + unset($this->recoveryQueue[$count]); + } + ++$this->packetStats[1]; + } + break; + + case Info::ACK: + foreach($packet->packets as $count){ + if(isset($this->recoveryQueue[$count])){ + $this->lag[] = microtime(true) - $this->recoveryQueue[$count]->sendtime; + unset($this->recoveryQueue[$count]); + unset($this->resendQueue[$count]); + } + ++$this->packetStats[0]; + } + break; + + case Info::DATA_PACKET_0: + case Info::DATA_PACKET_1: + case Info::DATA_PACKET_2: + case Info::DATA_PACKET_3: + case Info::DATA_PACKET_4: + case Info::DATA_PACKET_5: + case Info::DATA_PACKET_6: + case Info::DATA_PACKET_7: + case Info::DATA_PACKET_8: + case Info::DATA_PACKET_9: + case Info::DATA_PACKET_A: + case Info::DATA_PACKET_B: + case Info::DATA_PACKET_C: + case Info::DATA_PACKET_D: + case Info::DATA_PACKET_E: + case Info::DATA_PACKET_F: + $this->ackQueue[] = $packet->seqNumber; + $this->receiveQueue[$packet->seqNumber] = array(); + foreach($packet->data as $pk){ + $this->receiveQueue[$packet->seqNumber][] = $pk; + } + break; + } + } + } + + +} diff --git a/src/pocketmine/PlayerAPI.php b/src/pocketmine/PlayerAPI.php new file mode 100644 index 000000000..0b5094b5f --- /dev/null +++ b/src/pocketmine/PlayerAPI.php @@ -0,0 +1,345 @@ +server = Server::getInstance(); + } + + public function init(){ + $this->server->schedule(20 * 15, array($this, "handle"), 1, true, "server.regeneration"); + $this->server->addHandler("player.death", array($this, "handle"), 1); + $this->server->api->console->register("list", "", array($this, "commandHandler")); + $this->server->api->console->register("kill", "", array($this, "commandHandler")); + $this->server->api->console->register("gamemode", " [player]", array($this, "commandHandler")); + $this->server->api->console->register("tp", "[target player] OR /tp [target player] ", array($this, "commandHandler")); + $this->server->api->console->register("spawnpoint", "[player | w:world] [x] [y] [z]", array($this, "commandHandler")); + $this->server->api->console->register("spawn", "", array($this, "commandHandler")); + $this->server->api->console->register("ping", "", array($this, "commandHandler")); + $this->server->api->console->alias("lag", "ping"); + $this->server->api->console->alias("suicide", "kill"); + $this->server->api->console->alias("tppos", "tp"); + $this->server->api->ban->cmdWhitelist("list"); + $this->server->api->ban->cmdWhitelist("ping"); + $this->server->api->ban->cmdWhitelist("spawn"); + } + + public function handle($data, $event){ + switch($event){ + case "server.regeneration": + /*if($this->server->difficulty === 0){ + $result = $this->server->preparedSQL->selectPlayersToHeal->execute(); + if($result !== false){ + while(($player = $result->fetchArray()) !== false){ + if(($player = Entity::get($player["EID"])) !== false){ + if($player->getHealth() <= 0){ + continue; + } + $player->setHealth(min(20, $player->getHealth() + $data), "regeneration"); + } + } + return true; + } + }*/ + break; + case "player.death": + if(is_numeric($data["cause"])){ + $e = Entity::get($data["cause"]); + if($e instanceof Entity){ + switch($e->class){ + case ENTITY_PLAYER: + $message = " was killed by " . $e->name; + break; + default: + $message = " was killed"; + break; + } + } + }else{ + switch($data["cause"]){ + case "cactus": + $message = " was pricked to death"; + break; + case "lava": + $message = " tried to swim in lava"; + break; + case "fire": + $message = " went up in flames"; + break; + case "burning": + $message = " burned to death"; + break; + case "suffocation": + $message = " suffocated in a wall"; + break; + case "water": + $message = " drowned"; + break; + case "void": + $message = " fell out of the world"; + break; + case "fall": + $message = " hit the ground too hard"; + break; + case "explosion": + $message = " blew up"; + break; + default: + $message = " died"; + break; + } + } + Player::broadcastMessage($data["player"]->getName() . $message); + + return true; + } + return; + } + + public function commandHandler($cmd, $params, $issuer, $alias){ + $output = ""; + switch($cmd){ + case "spawnpoint": + if(count($params) === 0){ + $output .= "Usage: /$cmd [player | w:world] [x] [y] [z]\n"; + break; + } + if(!($issuer instanceof Player) and count($params) < 4){ + $output .= "Please run this command in-game.\n"; + break; + } + + if(count($params) === 1 or count($params) === 4){ + $tg = array_shift($params); + if(count($params) === 3 and substr($tg, 0, 2) === "w:"){ + $target = Level::get(substr($tg, 2)); + }else{ + $target = Player::get($tg); + } + }else{ + $target = $issuer; + } + + if(!($target instanceof Player) and !($target instanceof Level)){ + $output .= "That player cannot be found.\n"; + break; + } + + if(count($params) === 3){ + if($target instanceof Level){ + $spawn = new Vector3(floatval(array_shift($params)), floatval(array_shift($params)), floatval(array_shift($params))); + }else{ + $spawn = new Position(floatval(array_shift($params)), floatval(array_shift($params)), floatval(array_shift($params)), $issuer->level); + } + }else{ + $spawn = new Position($issuer->entity->x, $issuer->entity->y, $issuer->entity->z, $issuer->entity->level); + } + + $target->setSpawn($spawn); + if($target instanceof Level){ + $output .= "Spawnpoint of world " . $target->getName() . " set correctly!\n"; + }elseif($target !== $issuer){ + $output .= "Spawnpoint of " . $target->getName() . " set correctly!\n"; + }else{ + $output .= "Spawnpoint set correctly!\n"; + } + break; + case "spawn": + if(!($issuer instanceof Player)){ + $output .= "Please run this command in-game.\n"; + break; + } + $issuer->teleport(Level::getDefault()->getSafeSpawn()); + break; + case "ping": + if(!($issuer instanceof Player)){ + $output .= "Please run this command in-game.\n"; + break; + } + $output .= "ping " . round($issuer->getLag(), 2) . "ms, packet loss " . round($issuer->getPacketLoss() * 100, 2) . "%, " . round($issuer->getBandwidth() / 1024, 2) . " KB/s\n"; + break; + case "gamemode": + $player = false; + $setgm = false; + $gms = array( + "0" => 0, + "survival" => 0, + "s" => 0, + "1" => 1, + "creative" => 1, + "c" => 1, + "2" => 2, + "adventure" => 2, + "a" => 2, + "3" => 3, + "view" => 3, + "viewer" => 3, + "spectator" => 3, + "v" => 3, + ); + if(isset($params[1])){ + if(Player::get($params[1]) instanceof Player){ + $player = Player::get($params[1]); + $setgm = $params[0]; + }elseif(Player::get($params[0]) instanceof Player){ + $player = Player::get($params[0]); + $setgm = $params[1]; + }else{ + $output .= "Usage: /$cmd [player] or /$cmd [player] \n"; + break; + } + }elseif(isset($params[0])){ + if(!(Player::get($params[0]) instanceof Player)){ + if($issuer instanceof Player){ + $setgm = $params[0]; + $player = $issuer; + } + } + } + + if(!($player instanceof Player) or !isset($gms[strtolower($setgm)])){ + $output .= "Usage: /$cmd [player] or /$cmd [player] \n"; + break; + } + if($player->setGamemode($gms[strtolower($setgm)])){ + $output .= "Gamemode of " . $player->getName() . " changed to " . $player->getGamemode() . "\n"; + } + break; + case "tp": + if(count($params) <= 2 or substr($params[0], 0, 2) === "w:" or substr($params[1], 0, 2) === "w:"){ + if((!isset($params[1]) or substr($params[0], 0, 2) === "w:") and isset($params[0]) and ($issuer instanceof Player)){ + $name = $issuer->getName(); + $target = implode(" ", $params); + }elseif(isset($params[1]) and isset($params[0])){ + $name = array_shift($params); + $target = implode(" ", $params); + }else{ + $output .= "Usage: /$cmd [target player] \n"; + break; + } + if($this->teleport($name, $target) !== false){ + $output .= "\"$name\" teleported to \"$target\"\n"; + }else{ + $output .= "Couldn't teleport.\n"; + } + }else{ + if(!isset($params[3]) and isset($params[2]) and isset($params[1]) and isset($params[0]) and ($issuer instanceof Player)){ + $name = $issuer->getName(); + $x = $params[0]; + $y = $params[1]; + $z = $params[2]; + }elseif(isset($params[3]) and isset($params[2]) and isset($params[1]) and isset($params[0])){ + $name = $params[0]; + $x = $params[1]; + $y = $params[2]; + $z = $params[3]; + }else{ + $output .= "Usage: /$cmd [player] \n"; + break; + } + if($this->tppos($name, $x, $y, $z)){ + $output .= "\"$name\" teleported to ($x, $y, $z)\n"; + }else{ + $output .= "Couldn't teleport.\n"; + } + } + break; + case "kill": + case "suicide": + if(!isset($params[0]) and ($issuer instanceof Player)){ + $player = $issuer; + }else{ + $player = Player::get($params[0]); + } + if($player instanceof Player){ + $player->harm(1000, "console", true); + $player->sendMessage("Ouch. That looks like it hurt.\n"); + }else{ + $output .= "Usage: /$cmd [player]\n"; + } + break; + case "list": + $output .= "There are " . count(Player::$list) . "/" . $this->server->maxClients . " players online:\n"; + if(count(Player::$list) == 0){ + break; + } + foreach(Player::$list as $c){ + $output .= $c->getName() . ", "; + } + $output = substr($output, 0, -2) . "\n"; + break; + } + + return $output; + } + + public function teleport(&$name, &$target){ + if(substr($target, 0, 2) === "w:"){ + $lv = Level::get(substr($target, 2)); + if($lv instanceof Level){ + $origin = Player::get($name); + if($origin instanceof Player){ + $name = $origin->getName(); + + return $origin->teleport($lv->getSafeSpawn()); + } + }else{ + return false; + } + } + $player = Player::get($target); + if($player instanceof Player and $player->spawned === true){ + $target = $player->getName(); + $origin = Player::get($name); + if($origin instanceof Player){ + $name = $origin->getName(); + + return $origin->teleport($player->entity); + } + } + + return false; + } + + public function tppos(&$name, &$x, &$y, &$z){ + $player = Player::get($name); + if($player instanceof Player and $player->spawned === true){ + $name = $player->getName(); + $x = $x{0} === "~" ? $player->x + floatval(substr($x, 1)) : floatval($x); + $y = $y{0} === "~" ? $player->y + floatval(substr($y, 1)) : floatval($y); + $z = $z{0} === "~" ? $player->z + floatval(substr($z, 1)) : floatval($z); + $player->teleport(new Vector3($x, $y, $z)); + + return true; + } + + return false; + } +} diff --git a/src/pocketmine/PocketMine.php b/src/pocketmine/PocketMine.php new file mode 100644 index 000000000..3b62ba6fd --- /dev/null +++ b/src/pocketmine/PocketMine.php @@ -0,0 +1,369 @@ + $value){ + echo str_repeat(" ", $cnt + 1) . "[" . (is_integer($key) ? $key : '"' . $key . '"') . "]=>" . PHP_EOL; + ++$cnt; + safe_var_dump($value); + --$cnt; + } + echo str_repeat(" ", $cnt) . "}" . PHP_EOL; + break; + case is_integer($var): + echo str_repeat(" ", $cnt) . "int(" . $var . ")" . PHP_EOL; + break; + case is_float($var): + echo str_repeat(" ", $cnt) . "float(" . $var . ")" . PHP_EOL; + break; + case is_bool($var): + echo str_repeat(" ", $cnt) . "bool(" . ($var === true ? "true" : "false") . ")" . PHP_EOL; + break; + case is_string($var): + echo str_repeat(" ", $cnt) . "string(" . strlen($var) . ") \"$var\"" . PHP_EOL; + break; + case is_resource($var): + echo str_repeat(" ", $cnt) . "resource() of type (" . get_resource_type($var) . ")" . PHP_EOL; + break; + case is_object($var): + echo str_repeat(" ", $cnt) . "object(" . get_class($var) . ")" . PHP_EOL; + break; + case is_null($var): + echo str_repeat(" ", $cnt) . "NULL" . PHP_EOL; + break; + } + } + } + + function dummy(){ + + } +} + +namespace pocketmine { + use pocketmine\utils\TextFormat; + use pocketmine\utils\Utils; + use pocketmine\wizard\Installer; + + const VERSION = "Alpha_1.4dev"; + const API_VERSION = "1.0.0"; + const CODENAME = "絶好(Zekkou)ケーキ(Cake)"; + const MINECRAFT_VERSION = "v0.8.1 alpha"; + const PHP_VERSION = "5.5"; + + @define("pocketmine\\PATH", \getcwd() . DIRECTORY_SEPARATOR); + + if(!class_exists("SplClassLoader", false)){ + require_once(\pocketmine\PATH . "src/spl/SplClassLoader.php"); + } + + + $autoloader = new \SplClassLoader(); + $autoloader->add("pocketmine", array( + \pocketmine\PATH . "src" + )); + $autoloader->register(true); + + //Startup code. Do not look at it, it can harm you. Most of them are hacks to fix date-related bugs, or basic functions used after this + + set_time_limit(0); //Who set it to 30 seconds?!?! + + if(ini_get("date.timezone") == ""){ //No Timezone set + date_default_timezone_set("GMT"); + if(strpos(" " . strtoupper(php_uname("s")), " WIN") !== false){ + $time = time(); + $time -= $time % 60; + //TODO: Parse different time & date formats by region. ¬¬ world + //Example: USA + exec("time.exe /T", $hour); + $i = array_map("intval", explode(":", trim($hour[0]))); + exec("date.exe /T", $date); + $j = array_map("intval", explode(substr($date[0], 2, 1), trim($date[0]))); + $offset = round((mktime($i[0], $i[1], 0, $j[1], $j[0], $j[2]) - $time) / 60) * 60; + }else{ + exec("date +%s", $t); + $offset = round((intval(trim($t[0])) - time()) / 60) * 60; + } + + $daylight = (int) date("I"); + $d = timezone_name_from_abbr("", $offset, $daylight); + @ini_set("date.timezone", $d); + date_default_timezone_set($d); + }else{ + $d = @date_default_timezone_get(); + if(strpos($d, "/") === false){ + $d = timezone_name_from_abbr($d); + @ini_set("date.timezone", $d); + date_default_timezone_set($d); + } + } + + gc_enable(); + error_reporting(E_ALL | E_STRICT); + ini_set("allow_url_fopen", 1); + ini_set("display_errors", 1); + ini_set("display_startup_errors", 1); + ini_set("default_charset", "utf-8"); + + ini_set("memory_limit", "128M"); //Default + define("pocketmine\\START_TIME", microtime(true)); + + $opts = getopt("", array("enable-ansi", "disable-ansi", "data:", "plugins:", "no-wizard")); + + define("pocketmine\\DATA", isset($opts["data"]) ? realpath($opts["data"]) . DIRECTORY_SEPARATOR : \getcwd() . DIRECTORY_SEPARATOR); + define("pocketmine\\PLUGIN_PATH", isset($opts["plugins"]) ? realpath($opts["plugins"]) . DIRECTORY_SEPARATOR : \getcwd() . DIRECTORY_SEPARATOR . "plugins" . DIRECTORY_SEPARATOR); + + if((strpos(strtoupper(php_uname("s")), "WIN") === false or isset($opts["enable-ansi"])) and !isset($opts["disable-ansi"])){ + define("pocketmine\\ANSI", true); + }else{ + define("pocketmine\\ANSI", false); + } + + function kill($pid){ + switch(Utils::getOS()){ + case "win": + exec("taskkill.exe /F /PID " . ((int) $pid) . " > NUL"); + break; + case "mac": + case "linux": + default: + exec("kill -9 " . ((int) $pid) . " > /dev/null 2>&1"); + } + } + + /** + * Output text to the console, can contain Minecraft-formatted text. + * + * @param $message + * @param bool $EOL + * @param bool $log + * @param int $level + */ + function console($message, $EOL = true, $log = true, $level = 1){ + if(!defined("pocketmine\\DEBUG") or \pocketmine\DEBUG >= $level){ + $message .= $EOL === true ? PHP_EOL : ""; + if($message{0} !== "["){ + $message = "[INFO] $message"; + } + $time = (\pocketmine\ANSI === true ? TextFormat::AQUA . date("H:i:s") . TextFormat::RESET : date("H:i:s")) . " "; + $replaced = TextFormat::clean(preg_replace('/\x1b\[[0-9;]*m/', "", $time . $message)); + if($log === true and (!defined("LOG") or LOG === true)){ + log(date("Y-m-d") . " " . $replaced, "server", false, $level); + } + if(\pocketmine\ANSI === true){ + $add = ""; + if(preg_match("/^\\[([a-zA-Z0-9]*)\\]/", $message, $matches) > 0){ + switch($matches[1]){ + case "ERROR": + case "SEVERE": + $add .= TextFormat::RED; + break; + case "TRACE": + case "INTERNAL": + case "DEBUG": + $add .= TextFormat::GRAY; + break; + case "WARNING": + $add .= TextFormat::YELLOW; + break; + case "NOTICE": + $add .= TextFormat::AQUA; + break; + default: + $add = ""; + break; + } + } + $message = TextFormat::toANSI($time . $add . $message . TextFormat::RESET); + }else{ + $message = $replaced; + } + echo $message; + } + } + + function getTrace($start = 1){ + $e = new \Exception(); + $trace = $e->getTrace(); + $messages = array(); + $j = 0; + for($i = (int) $start; isset($trace[$i]); ++$i, ++$j){ + $params = ""; + if(isset($trace[$i]["args"])){ + foreach($trace[$i]["args"] as $name => $value){ + $params .= (is_object($value) ? get_class($value) . " " . (method_exists($value, "__toString") ? $value->__toString() : "object") : gettype($value) . " " . @strval($value)) . ", "; + } + } + $messages[] = "#$j " . (isset($trace[$i]["file"]) ? $trace[$i]["file"] : "") . "(" . (isset($trace[$i]["line"]) ? $trace[$i]["line"] : "") . "): " . (isset($trace[$i]["class"]) ? $trace[$i]["class"] . $trace[$i]["type"] : "") . $trace[$i]["function"] . "(" . substr($params, 0, -2) . ")"; + } + + return $messages; + } + + function error_handler($errno, $errstr, $errfile, $errline){ + if(error_reporting() === 0){ //@ error-control + return false; + } + $errorConversion = array( + E_ERROR => "E_ERROR", + E_WARNING => "E_WARNING", + E_PARSE => "E_PARSE", + E_NOTICE => "E_NOTICE", + E_CORE_ERROR => "E_CORE_ERROR", + E_CORE_WARNING => "E_CORE_WARNING", + E_COMPILE_ERROR => "E_COMPILE_ERROR", + E_COMPILE_WARNING => "E_COMPILE_WARNING", + E_USER_ERROR => "E_USER_ERROR", + E_USER_WARNING => "E_USER_WARNING", + E_USER_NOTICE => "E_USER_NOTICE", + E_STRICT => "E_STRICT", + E_RECOVERABLE_ERROR => "E_RECOVERABLE_ERROR", + E_DEPRECATED => "E_DEPRECATED", + E_USER_DEPRECATED => "E_USER_DEPRECATED", + ); + $type = ($errno === E_ERROR or $errno === E_WARNING or $errno === E_USER_ERROR or $errno === E_USER_WARNING) ? "ERROR" : "NOTICE"; + $errno = isset($errorConversion[$errno]) ? $errorConversion[$errno] : $errno; + console("[$type] A $errno error happened: \"$errstr\" in \"$errfile\" at line $errline", true, true, 0); + foreach(getTrace() as $i => $line){ + console("[TRACE] $line"); + } + + return true; + } + + function log($message, $name, $EOL = true, $level = 2, $close = false){ + global $fpointers; + if((!defined("pocketmine\\DEBUG") or \pocketmine\DEBUG >= $level) and (!defined("pocketmine\\LOG") or \pocketmine\LOG === true)){ + $message .= $EOL === true ? PHP_EOL : ""; + if(!isset($fpointers)){ + $fpointers = array(); + } + if(!isset($fpointers[$name]) or $fpointers[$name] === false){ + $fpointers[$name] = @fopen(\pocketmine\DATA . "/" . $name . ".log", "ab"); + } + @fwrite($fpointers[$name], $message); + if($close === true){ + fclose($fpointers[$name]); + unset($fpointers[$name]); + } + } + } + + + set_error_handler("\\pocketmine\\error_handler", E_ALL); + + $errors = 0; + + if(version_compare("5.4.0", PHP_VERSION) > 0){ + console("[ERROR] Use PHP >= 5.4.0", true, true, 0); + ++$errors; + } + + if(php_sapi_name() !== "cli"){ + console("[ERROR] You must run PocketMine-MP using the CLI.", true, true, 0); + ++$errors; + } + + if(!extension_loaded("sockets")){ + console("[ERROR] Unable to find the Socket extension.", true, true, 0); + ++$errors; + } + + if(!extension_loaded("pthreads")){ + console("[ERROR] Unable to find the pthreads extension.", true, true, 0); + ++$errors; + }else{ + $pthreads_version = phpversion("pthreads"); + if(substr_count($pthreads_version, ".") < 2){ + $pthreads_version = "0.$pthreads_version"; + } + if(version_compare($pthreads_version, "2.0.4") < 0){ + console("[ERROR] pthreads >= 2.0.4 is required, while you have $pthreads_version.", true, true, 0); + ++$errors; + } + } + + if(!extension_loaded("curl")){ + console("[ERROR] Unable to find the cURL extension.", true, true, 0); + ++$errors; + } + + if(!extension_loaded("sqlite3")){ + console("[ERROR] Unable to find the SQLite3 extension.", true, true, 0); + ++$errors; + } + + if(!extension_loaded("yaml")){ + console("[ERROR] Unable to find the YAML extension.", true, true, 0); + ++$errors; + } + + if(!extension_loaded("zlib")){ + console("[ERROR] Unable to find the Zlib extension.", true, true, 0); + ++$errors; + } + + if($errors > 0){ + console("[ERROR] Please use the installer provided on the homepage, or recompile PHP again.", true, true, 0); + exit(1); //Exit with error + } + + if(file_exists(\pocketmine\PATH . ".git/refs/heads/master")){ //Found Git information! + define("pocketmine\\GIT_COMMIT", strtolower(trim(file_get_contents(\pocketmine\PATH . ".git/refs/heads/master")))); + }else{ //Unknown :( + define("pocketmine\\GIT_COMMIT", str_repeat("00", 20)); + } + + @ini_set("opcache.mmap_base", bin2hex(Utils::getRandomBytes(8, false))); //Fix OPCache address errors + + if(!file_exists(\pocketmine\DATA . "server.properties") and !isset($opts["no-wizard"])){ + new Installer(); + } + + if(substr(__FILE__, 0, 7) !== "phar"){ + console("[WARNING] Non-packaged PocketMine-MP installation detected, do not use on production."); + } + + $server = new Server($autoloader, \pocketmine\PATH, \pocketmine\DATA, \pocketmine\PLUGIN_PATH); + $server->start(); + + kill(getmypid()); + exit(0); + +} \ No newline at end of file diff --git a/src/pocketmine/Server.php b/src/pocketmine/Server.php new file mode 100644 index 000000000..71ed0999c --- /dev/null +++ b/src/pocketmine/Server.php @@ -0,0 +1,1113 @@ +isRunning === true; + } + + /** + * @return string + */ + public function getPocketMineVersion(){ + return \pocketmine\VERSION; + } + + /** + * @return string + */ + public function getCodename(){ + return \pocketmine\CODENAME; + } + + /** + * @return string + */ + public function getVersion(){ + return \pocketmine\MINECRAFT_VERSION; + } + + /** + * @return string + */ + public function getApiVersion(){ + return \pocketmine\API_VERSION; + } + + /** + * @return string + */ + public function getFilePath(){ + return $this->filePath; + } + + /** + * @return string + */ + public function getDataPath(){ + return $this->dataPath; + } + + /** + * @return string + */ + public function getPluginPath(){ + return $this->pluginPath; + } + + /** + * @return int + */ + public function getMaxPlayers(){ + return $this->maxPlayers; + } + + /** + * @return int + */ + public function getPort(){ + return $this->getConfigInt("server-port", 19132); + } + + /** + * @return int + */ + public function getViewDistance(){ + return $this->getConfigInt("view-distance", 8); + } + + /** + * @return string + */ + public function getIp(){ + return $this->getConfigString("server-ip", ""); + } + + /** + * @return string + */ + public function getServerName(){ + return $this->getConfigString("server-name", "Unknown server"); + } + + /** + * @return string + */ + public function getLevelType(){ + return $this->getConfigString("level-type", "DEFAULT"); + } + + /** + * @return bool + */ + public function getGenerateStructures(){ + return $this->getConfigBoolean("generate-structures", true); + } + + /** + * @return int + */ + public function getGamemode(){ + return $this->getConfigInt("gamemode", 0) & 0b11; + } + + /** + * Returns the gamemode text name + * + * @param int $mode + * + * @return string + */ + public static function getGamemodeString($mode){ + switch((int) $mode){ + case Player::SURVIVAL: + return "SURVIVAL"; + case Player::CREATIVE: + return "CREATIVE"; + case Player::ADVENTURE: + return "ADVENTURE"; + case Player::SPECTATOR: + return "SPECTATOR"; + } + + return "UNKNOWN"; + } + + /** + * Parses a string and returns a gamemode integer, -1 if not found + * + * @param string $str + * + * @return int + */ + public static function getGamemodeFromString($str){ + switch(strtolower(trim($str))){ + case (string) Player::SURVIVAL: + case "survival": + case "s": + return Player::SURVIVAL; + + case (string) Player::CREATIVE: + case "creative": + case "c": + return Player::CREATIVE; + + case (string) Player::ADVENTURE: + case "adventure": + case "a": + return Player::ADVENTURE; + + case (string) Player::SPECTATOR: + case "spectator": + case "view": + case "v": + return Player::SPECTATOR; + } + return -1; + } + + /** + * @param string $str + * + * @return int + */ + public static function getDifficultyFromString($str){ + switch(strtolower(trim($str))){ + case "0": + case "peaceful": + case "p": + return 0; + + case "1": + case "easy": + case "e": + return 1; + + case "2": + case "normal": + case "n": + return 2; + + case "3": + case "hard": + case "h": + return 3; + } + return -1; + } + + /** + * @return int + */ + public function getDifficulty(){ + return $this->getConfigInt("difficulty", 1); + } + + /** + * @return bool + */ + public function hasWhitelist(){ + return $this->getConfigBoolean("white-list", false); + } + + /** + * @return int + */ + public function getSpawnRadius(){ + return $this->getConfigInt("spawn-protection", 16); + } + + /** + * @return bool + */ + public function getAllowFlight(){ + return $this->getConfigBoolean("allow-flight", false); + } + + /** + * @return bool + */ + public function isHardcore(){ + return $this->getConfigBoolean("hardcore", false); + } + + /** + * @return int + */ + public function getDefaultGamemode(){ + return $this->getConfigInt("gamemode", 0) & 0b11; + } + + /** + * @return string + */ + public function getMotd(){ + return $this->getConfigString("motd", "Minecraft: PE Server"); + } + + /** + * @return \SplClassLoader + */ + public function getLoader(){ + return $this->autoloader; + } + + /** + * @return PluginManager + */ + public function getPluginManager(){ + return $this->pluginManager; + } + + /** + * @return ServerScheduler + */ + public function getScheduler(){ + return $this->scheduler; + } + + /** + * @return int + */ + public function getTick(){ + return $this->tickCounter; + } + + /** + * Returns the last server TPS measure + * + * @return float + */ + public function getTicksPerSecond(){ + return $this->tickScheduler->getTPS(); + } + + /** + * @return ThreadedHandler + */ + public function getNetwork(){ + return $this->interface; + } + + /** + * @return SimpleCommandMap + */ + public function getCommandMap(){ + return $this->commandMap; + } + + /** + * @param string $variable + * @param string $defaultValue + * + * @return string + */ + public function getConfigString($variable, $defaultValue = ""){ + $v = getopt("", array("$variable::")); + if(isset($v[$variable])){ + return (string) $v[$variable]; + } + + return $this->properties->exists($variable) ? $this->properties->get($variable) : $defaultValue; + } + + /** + * @param string $variable + * @param string $value + */ + public function setConfigString($variable, $value){ + $this->properties->set($variable, $value); + } + + /** + * @param string $variable + * @param int $defaultValue + * + * @return int + */ + public function getConfigInt($variable, $defaultValue = 0){ + $v = getopt("", array("$variable::")); + if(isset($v[$variable])){ + return (int) $v[$variable]; + } + + return $this->properties->exists($variable) ? (int) $this->properties->get($variable) : (int) $defaultValue; + } + + /** + * @param string $variable + * @param int $value + */ + public function setConfigInt($variable, $value){ + $this->properties->set($variable, (int) $value); + } + + /** + * @param string $variable + * @param boolean $defaultValue + * + * @return boolean + */ + public function getConfigBoolean($variable, $defaultValue = false){ + $v = getopt("", array("$variable::")); + if(isset($v[$variable])){ + $value = $v[$variable]; + }else{ + $value = $this->properties->exists($variable) ? $this->properties->get($variable) : $defaultValue; + } + + if(is_bool($value)){ + return $value; + } + switch(strtolower($value)){ + case "on": + case "true": + case "1": + case "yes": + return true; + } + + return false; + } + + /** + * @param string $variable + * @param bool $value + */ + public function setConfigBool($variable, $value){ + $this->properties->set($variable, $value == true ? "1" : "0"); + } + + /** + * @param string $name + * + * @return PluginCommand + */ + public function getPluginCommand($name){ + if(($command = $this->commandMap->getCommand($name)) instanceof PluginCommand){ + return $command; + }else{ + return null; + } + } + + /** + * @return BanList + */ + public function getNameBans(){ + return $this->banByName; + } + + /** + * @return BanList + */ + public function getIPBans(){ + return $this->banByIP; + } + + /** + * @param string $name + */ + public function addOp($name){ + $this->operators->set(strtolower($name), true); + + if(($player = Player::get($name, false, false)) instanceof Player){ + $player->recalculatePermissions(); + } + } + + /** + * @param string $name + */ + public function removeOp($name){ + $this->operators->remove(strtolower($name)); + + if(($player = Player::get($name, false, false)) instanceof Player){ + $player->recalculatePermissions(); + } + } + + /** + * @param string $name + */ + public function addWhitelist($name){ + $this->whitelist->set(strtolower($name), true); + } + + /** + * @param string $name + */ + public function removeWhitelist($name){ + $this->whitelist->remove(strtolower($name)); + } + + /** + * @param string $name + * + * @return bool + */ + public function isWhitelisted($name){ + return !$this->hasWhitelist() or $this->operators->exists($name, true) or $this->whitelist->exists($name, true); + } + + /** + * @param string $name + * + * @return bool + */ + public function isOp($name){ + return $this->operators->exists($name, true); + } + + /** + * @return Config + */ + public function getWhitelisted(){ + return $this->whitelist; + } + + /** + * @return Config + */ + public function getOPs(){ + return $this->operators; + } + + public function reloadWhitelist(){ + $this->whitelist->reload(); + } + + /** + * @return Server + */ + public static function getInstance(){ + return self::$instance; + } + + /** + * @param \SplClassLoader $autoloader + * @param string $filePath + * @param string $dataPath + * @param string $pluginPath + */ + public function __construct(\SplClassLoader $autoloader, $filePath, $dataPath, $pluginPath){ + self::$instance = $this; + + $this->autoloader = $autoloader; + $this->filePath = $filePath; + $this->dataPath = $dataPath; + $this->pluginPath = $pluginPath; + @mkdir($this->dataPath . "worlds/", 0777); + @mkdir($this->dataPath . "players/", 0777); + @mkdir($this->pluginPath, 0777); + + $this->operators = new Config($this->dataPath . "ops.txt", Config::ENUM); + $this->whitelist = new Config($this->dataPath . "white-list.txt", Config::ENUM); + if(file_exists($this->dataPath . "banned.txt") and !file_exists($this->dataPath . "banned-players.txt")){ + @rename($this->dataPath . "banned.txt", $this->dataPath . "banned-players.txt"); + } + @touch($this->dataPath . "banned-players.txt"); + $this->banByName = new BanList($this->dataPath . "banned-players.txt"); + $this->banByName->load(); + @touch($this->dataPath . "banned-ips.txt"); + $this->banByIP = new BanList($this->dataPath . "banned-ips.txt"); + $this->banByIP->load(); + + $this->tickScheduler = new TickScheduler(20); + $this->scheduler = new ServerScheduler(); + $this->console = new CommandReader(); + + $version = new VersionString($this->getPocketMineVersion()); + console("[INFO] Starting Minecraft: PE server version " . TextFormat::AQUA . $this->getVersion()); + + console("[INFO] Loading properties..."); + $this->properties = new Config($this->dataPath . "server.properties", Config::PROPERTIES, array( + "motd" => "Minecraft: PE Server", + "server-port" => 19132, + "memory-limit" => "128M", + "white-list" => false, + "announce-player-achievements" => true, + "spawn-protection" => 16, + "view-distance" => 8, + "max-players" => 20, + "allow-flight" => false, + "spawn-animals" => true, + "spawn-mobs" => true, + "gamemode" => 0, + "hardcore" => false, + "pvp" => true, + "difficulty" => 1, + "generator-settings" => "", + "level-name" => "world", + "level-seed" => "", + "level-type" => "DEFAULT", + "enable-query" => true, + "enable-rcon" => false, + "rcon.password" => substr(base64_encode(Utils::getRandomBytes(20, false)), 3, 10), + "auto-save" => true, + )); + + $this->maxPlayers = $this->getConfigInt("max-players", 20); + + if(($memory = str_replace("B", "", strtoupper($this->getConfigString("memory-limit", "128M")))) !== false){ + $value = array("M" => 1, "G" => 1024); + $real = ((int) substr($memory, 0, -1)) * $value[substr($memory, -1)]; + if($real < 128){ + console("[WARNING] PocketMine-MP may not work right with less than 128MB of RAM", true, true, 0); + } + @ini_set("memory_limit", $memory); + }else{ + $this->setConfigString("memory-limit", "128M"); + } + + if($this->getConfigBoolean("hardcore", false) === true and $this->getDifficulty() < 3){ + $this->setConfigInt("difficulty", 3); + } + + define("pocketmine\\DEBUG", $this->getConfigInt("debug", 1)); + define("ADVANCED_CACHE", $this->getConfigBoolean("enable-advanced-cache", false)); + define("MAX_CHUNK_RATE", 20 / $this->getConfigInt("max-chunks-per-second", 7)); //Default rate ~448 kB/s + if(ADVANCED_CACHE == true){ + console("[INFO] Advanced cache enabled"); + } + + if(defined("pocketmine\\DEBUG") and \pocketmine\DEBUG >= 0 and function_exists("cli_set_process_title")){ + @cli_set_process_title("PocketMine-MP " . $this->getPocketMineVersion()); + } + + console("[INFO] Starting Minecraft PE server on " . ($this->getIp() === "" ? "*" : $this->getIp()) . ":" . $this->getPort()); + define("BOOTUP_RANDOM", Utils::getRandomBytes(16)); + $this->serverID = Utils::readLong(substr(Utils::getUniqueID(true, $this->getIp() . $this->getPort()), 8)); + $this->interface = new ThreadedHandler("255.255.255.255", $this->getPort(), $this->getIp() === "" ? "0.0.0.0" : $this->getIp()); + + console("[INFO] This server is running PocketMine-MP version " . ($version->isDev() ? TextFormat::YELLOW : "") . $this->getPocketMineVersion() . TextFormat::RESET . " \"" . $this->getCodename() . "\" (API " . $this->getApiVersion() . ")", true, true, 0); + console("[INFO] PocketMine-MP is distributed under the LGPL License", true, true, 0); + + $this->consoleSender = new ConsoleCommandSender(); + $this->commandMap = new SimpleCommandMap($this); + $this->pluginManager = new PluginManager($this, $this->commandMap); + $this->pluginManager->subscribeToPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this->consoleSender); + $this->pluginManager->registerInterface("pocketmine\\plugin\\FolderPluginLoader"); + $this->pluginManager->registerInterface("pocketmine\\plugin\\PharPluginLoader"); + $this->pluginManager->loadPlugins($this->pluginPath); + + //TODO: update checking (async) + + $this->enablePlugins(PluginLoadOrder::STARTUP); + Block::init(); + Item::init(); + Crafting::init(); + + Generator::addGenerator("pocketmine\\level\\generator\\Flat", "flat"); + Generator::addGenerator("pocketmine\\level\\generator\\Normal", "normal"); + Generator::addGenerator("pocketmine\\level\\generator\\Normal", "default"); + Level::init(); + + $this->properties->save(); + //TODO + /*if($this->getProperty("send-usage", true) !== false){ + $this->server->schedule(6000, array($this, "sendUsage"), array(), true); //Send the info after 5 minutes have passed + $this->sendUsage(); + } + if(!defined("NO_THREADS") and $this->getProperty("enable-rcon") === true){ + $this->rcon = new RCON($this->getProperty("rcon.password", ""), $this->getProperty("rcon.port", $this->getProperty("server-port")), ($ip = $this->getProperty("server-ip")) != "" ? $ip : "0.0.0.0", $this->getProperty("rcon.threads", 1), $this->getProperty("rcon.clients-per-thread", 50)); + }*/ + $this->scheduler->scheduleRepeatingTask(new CallbackTask("pocketmine\\utils\\Cache::cleanup"), 20 * 45); + if($this->getConfigBoolean("auto-save", true) === true){ + $this->scheduler->scheduleRepeatingTask(new CallbackTask(array($this, "doAutoSave")), 18000); + } + + $this->enablePlugins(PluginLoadOrder::POSTWORLD); + } + + /** + * @param $message + * + * @return int + */ + public function broadcastMessage($message){ + return $this->broadcast($message, self::BROADCAST_CHANNEL_USERS); + } + + /** + * @param string $message + * @param string $permission + * + * @return int + */ + public function broadcast($message, $permission){ + $count = 0; + foreach($this->pluginManager->getPermissionSubscriptions($permission) as $permissible){ + if($permissible instanceof CommandSender and $permissible->hasPermission($permission)){ + $permissible->sendMessage($message); + ++$count; + } + } + + return $count; + } + + + /** + * @param int $type + */ + public function enablePlugins($type){ + foreach($this->pluginManager->getPlugins() as $plugin){ + if(!$plugin->isEnabled() and $plugin->getDescription()->getOrder() === $type){ + $this->loadPlugin($plugin); + } + } + + if($type === PluginLoadOrder::POSTWORLD){ + $this->commandMap->registerServerAliases(); + $this->loadCustomPermissions(); + } + } + + private function loadCustomPermissions(){ + DefaultPermissions::registerCorePermissions(); + } + + /** + * @param Plugin $plugin + */ + public function loadPlugin(Plugin $plugin){ + $this->pluginManager->enablePlugin($plugin); + } + + public function disablePlugins(){ + $this->pluginManager->disablePlugins(); + } + + public function checkConsole(){ + if(($line = $this->console->getLine()) !== null){ + $this->pluginManager->callEvent($ev = new ServerCommandEvent($this->consoleSender, $line)); + $this->dispatchCommand($this->consoleSender, $ev->getCommand()); + } + } + + /** + * Executes a command from a CommandSender + * + * @param CommandSender $sender + * @param string $commandLine + * + * @return bool + */ + public function dispatchCommand(CommandSender $sender, $commandLine){ + if($this->commandMap->dispatch($sender, $commandLine)){ + return true; + } + + if($sender instanceof Player){ + $sender->sendMessage("Unknown command. Type \"/help\" for help."); + }else{ + $sender->sendMessage("Unknown command. Type \"help\" for help."); + } + + return false; + } + + public function shutdown(){ + $this->isRunning = false; + } + + /** + * Starts the PocketMine-MP server and starts processing ticks and packets + */ + public function start(){ + if($this->getConfigBoolean("enable-query", true) === true){ + $this->queryHandler = new QueryHandler(); + } + + if($this->getConfigBoolean("upnp-forwarding", false) == true){ + console("[INFO] [UPnP] Trying to port forward..."); + UPnP::PortForward($this->getPort()); + } + + $this->tickCounter = 0; + register_tick_function(array($this, "tick")); + /* + register_shutdown_function(array($this, "dumpError")); + register_shutdown_function(array($this, "close")); + if(function_exists("pcntl_signal")){ + //pcntl_signal(SIGTERM, array($this, "close")); + pcntl_signal(SIGINT, array($this, "close")); + pcntl_signal(SIGHUP, array($this, "close")); + } + */ + console("[INFO] Default game type: " . self::getGamemodeString($this->getGamemode())); //TODO: string name + //$this->trigger("server.start", microtime(true)); + console('[INFO] Done (' . round(microtime(true) - \pocketmine\START_TIME, 3) . 's)! For help, type "help" or "?"'); + if(Utils::getOS() === "win"){ //Workaround less usleep() waste + $this->tickProcessorWindows(); + }else{ + $this->tickProcessor(); + } + + $this->pluginManager->disablePlugins(); + + foreach(Player::getAll() as $player){ + $player->kick("server stop"); + } + + foreach(Level::getAll() as $level){ + $level->unload(true); + } + + HandlerList::unregisterAll(); + $this->scheduler->cancelAllTasks(); + $this->scheduler->mainThreadHeartbeat(PHP_INT_MAX); + + $this->properties->save(); + + $this->tickScheduler->kill(); + $this->console->kill(); + + } + + private function tickProcessorWindows(){ + $lastLoop = 0; + while($this->isRunning){ + if(($packet = $this->interface->readPacket()) instanceof Packet){ + $this->pluginManager->callEvent($ev = new PacketReceiveEvent($packet)); + if(!$ev->isCancelled()){ + $this->handlePacket($packet); + } + $lastLoop = 0; + } + if(($ticks = $this->tick()) !== true){ + ++$lastLoop; + if($lastLoop > 128){ + usleep(1000); + } + }else{ + $lastLoop = 0; + } + } + } + + private function tickProcessor(){ + $lastLoop = 0; + while($this->isRunning){ + if(($packet = $this->interface->readPacket()) instanceof Packet){ + $this->pluginManager->callEvent($ev = new PacketReceiveEvent($packet)); + if(!$ev->isCancelled()){ + $this->handlePacket($packet); + } + $lastLoop = 0; + } + if(($ticks = $this->tick()) !== true){ + ++$lastLoop; + if($lastLoop > 16 and $lastLoop < 128){ + usleep(200); + }elseif($lastLoop < 512){ + usleep(400); + }else{ + usleep(1000); + } + }else{ + $lastLoop = 0; + } + } + } + + public function handlePacket(Packet $packet){ + if($packet instanceof QueryPacket and isset($this->queryHandler)){ + $this->queryHandler->handle($packet); + }elseif($packet instanceof RakNetPacket){ + $CID = $packet->ip . ":" . $packet->port; + if(isset(Player::$list[$CID])){ + Player::$list[$CID]->handlePacket($packet); + }else{ + switch($packet->pid()){ + case RakNetInfo::UNCONNECTED_PING: + case RakNetInfo::UNCONNECTED_PING_OPEN_CONNECTIONS: + $pk = new RakNetPacket(RakNetInfo::UNCONNECTED_PONG); + $pk->pingID = $packet->pingID; + $pk->serverID = $this->serverID; + $pk->serverType = "MCCPP;Demo;" . $this->getMotd() . " [" . count(Player::$list) . "/" . $this->getMaxPlayers() . "]"; + $pk->ip = $packet->ip; + $pk->port = $packet->port; + $this->sendPacket($pk); + break; + case RakNetInfo::OPEN_CONNECTION_REQUEST_1: + if($packet->structure !== RakNetInfo::STRUCTURE){ + console("[DEBUG] Incorrect structure #" . $packet->structure . " from " . $packet->ip . ":" . $packet->port, true, true, 2); + $pk = new RakNetPacket(RakNetInfo::INCOMPATIBLE_PROTOCOL_VERSION); + $pk->serverID = $this->serverID; + $pk->ip = $packet->ip; + $pk->port = $packet->port; + $this->sendPacket($pk); + }else{ + $pk = new RakNetPacket(RakNetInfo::OPEN_CONNECTION_REPLY_1); + $pk->serverID = $this->serverID; + $pk->mtuSize = strlen($packet->buffer); + $pk->ip = $packet->ip; + $pk->port = $packet->port; + $this->sendPacket($pk); + } + break; + case RakNetInfo::OPEN_CONNECTION_REQUEST_2: + new Player($packet->clientID, $packet->ip, $packet->port, $packet->mtuSize); //New Session! + $pk = new RakNetPacket(RakNetInfo::OPEN_CONNECTION_REPLY_2); + $pk->serverID = $this->serverID; + $pk->serverPort = $this->getPort(); + $pk->mtuSize = $packet->mtuSize; + $pk->ip = $packet->ip; + $pk->port = $packet->port; + $this->sendPacket($pk); + break; + } + } + } + } + + /** + * Sends a packet to the processing queue. Returns the number of bytes + * + * @param Packet $packet + * + * @return int + */ + public function sendPacket(Packet $packet){ + $this->pluginManager->callEvent($ev = new PacketSendEvent($packet)); + if(!$ev->isCancelled()){ + return $this->interface->writePacket($packet); + } + + return 0; + } + + private function checkTickUpdates(){ + //Update entities that need update + if(count(Entity::$needUpdate) > 0){ + foreach(Entity::$needUpdate as $id => $entity){ + if($entity->onUpdate() === false){ + unset(Entity::$needUpdate[$id]); + } + } + } + + //Update tiles that need update + if(count(Tile::$needUpdate) > 0){ + foreach(Tile::$needUpdate as $id => $tile){ + if($tile->onUpdate() === false){ + unset(Tile::$needUpdate[$id]); + } + } + } + + //TODO: Add level blocks + + //Do level ticks + foreach(Level::getAll() as $level){ + $level->doTick(); + } + } + + public function doAutoSave(){ + $this->broadcast(TextFormat::GRAY . "Saving...", self::BROADCAST_CHANNEL_ADMINISTRATIVE); + Level::saveAll(); + } + + public function sendUsage(){ + //TODO + /*console("[DEBUG] Sending usage data...", true, true, 2); + $plist = ""; + foreach(Server::getInstance()->getPluginManager()->getPlugins() as $p){ + $d = $p->getDescription(); + $plist .= str_replace(array(";", ":"), "", $d->getName()) . ":" . str_replace(array(";", ":"), "", $d->getVersion()) . ";"; + } + + $this->asyncOperation(ASYNC_CURL_POST, array( + "url" => "http://stats.pocketmine.net/usage.php", + "data" => array( + "serverid" => $this->server->serverID, + "port" => $this->server->port, + "os" => Utils::getOS(), + "memory_total" => $this->getProperty("memory-limit"), + "memory_usage" => memory_get_usage(true), + "php_version" => PHP_VERSION, + "version" => VERSION, + "mc_version" => MINECRAFT_VERSION, + "protocol" => Info::CURRENT_PROTOCOL, + "online" => count(Player::$list), + "max" => $this->server->maxClients, + "plugins" => $plist, + ), + ), null);*/ + } + + public function titleTick(){ + if(defined("pocketmine\\DEBUG") and \pocketmine\DEBUG >= 0 and \pocketmine\ANSI === true){ + echo "\x1b]0;PocketMine-MP " . $this->getPocketMineVersion() . " | Online " . count(Player::$list) . "/" . $this->getMaxPlayers() . " | RAM " . round((memory_get_usage() / 1024) / 1024, 2) . "/" . round((memory_get_usage(true) / 1024) / 1024, 2) . " MB | U " . round($this->interface->getUploadSpeed() / 1024, 2) . " D " . round($this->interface->getDownloadSpeed() / 1024, 2) . " kB/s | TPS " . $this->getTicksPerSecond() . "\x07"; + } + } + + + /** + * Tries to execute a server tick + */ + public function tick(){ + if($this->inTick === false and $this->tickScheduler->hasTick()){ + $this->inTick = true; //Fix race conditions + ++$this->tickCounter; + + $this->checkConsole(); + $this->scheduler->mainThreadHeartbeat($this->tickCounter); + if(($this->tickCounter & 0b1) === 0){ + $this->checkTickUpdates(); + if(($this->tickCounter & 0b1111) === 0){ + $this->titleTick(); + if(isset($this->queryHandler) and ($this->tickCounter & 0b111111111) === 0){ + $this->queryHandler->regenerateInfo(); + } + } + } + $this->tickScheduler->doTick(); + $this->inTick = false; + + return true; + } + + return false; + } + +} \ No newline at end of file diff --git a/src/pocketmine/ServerAPI.php b/src/pocketmine/ServerAPI.php new file mode 100644 index 000000000..96a94bb40 --- /dev/null +++ b/src/pocketmine/ServerAPI.php @@ -0,0 +1,363 @@ +run(); + } + + public function run(){ + $this->load(); + + return $this->init(); + } + + public function load(){ + @mkdir(\pocketmine\DATA . "players/", 0755); + @mkdir(\pocketmine\DATA . "worlds/", 0755); + @mkdir(\pocketmine\DATA . "plugins/", 0755); + + $version = new VersionString(); + console("[INFO] Starting Minecraft: PE server version " . TextFormat::AQUA . MINECRAFT_VERSION); + + console("[INFO] Loading properties..."); + $this->config = new Config(\pocketmine\DATA . "server.properties", Config::PROPERTIES, array( + "server-name" => "Minecraft: PE Server", + "description" => "Server made using PocketMine-MP", + "motd" => "Welcome @player to this server!", + "server-port" => 19132, + "server-type" => "normal", + "memory-limit" => "128M", + "last-update" => false, + "white-list" => false, + "announce-player-achievements" => true, + "spawn-protection" => 16, + "view-distance" => 8, + "max-players" => 20, + "allow-flight" => false, + "spawn-animals" => true, + "spawn-mobs" => true, + "gamemode" => 0, + "hardcore" => false, + "pvp" => true, + "difficulty" => 1, + "generator-settings" => "", + "level-name" => "world", + "level-seed" => "", + "level-type" => "DEFAULT", + "enable-query" => true, + "enable-rcon" => false, + "rcon.password" => substr(base64_encode(Utils::getRandomBytes(20, false)), 3, 10), + "auto-save" => true, + )); + + $this->parseProperties(); + + //Load advanced properties + define("pocketmine\\DEBUG", $this->getProperty("debug", 1)); + define("ADVANCED_CACHE", $this->getProperty("enable-advanced-cache", false)); + define("MAX_CHUNK_RATE", 20 / $this->getProperty("max-chunks-per-second", 7)); //Default rate ~448 kB/s + if(ADVANCED_CACHE == true){ + console("[INFO] Advanced cache enabled"); + } + if($this->getProperty("upnp-forwarding") == true){ + console("[INFO] [UPnP] Trying to port forward..."); + UPnP::PortForward($this->getProperty("server-port")); + } + $this->server = new Server($this->getProperty("server-name"), $this->getProperty("gamemode"), ($seed = $this->getProperty("level-seed")) != "" ? (int) $seed : false, $this->getProperty("server-port"), ($ip = $this->getProperty("server-ip")) != "" ? $ip : "0.0.0.0"); + $this->server->api = $this; + self::$serverRequest = $this->server; + console("[INFO] This server is running PocketMine-MP version " . ($version->isDev() ? TextFormat::YELLOW : "") . VERSION . TextFormat::RESET . " \"" . CODENAME . "\" (API " . API_VERSION . ")", true, true, 0); + console("[INFO] PocketMine-MP is distributed under the LGPL License", true, true, 0); + + if($this->getProperty("last-update") === false or ($this->getProperty("last-update") + 3600) < time()){ + console("[INFO] Checking for new server version"); + console("[INFO] Last check: " . TextFormat::AQUA . date("Y-m-d H:i:s", $this->getProperty("last-update")) . "\x1b[0m"); + if($this->server->version->isDev()){ + $info = json_decode(Utils::getURL("https://api.github.com/repos/PocketMine/PocketMine-MP/commits"), true); + if($info === false or !isset($info[0])){ + console("[ERROR] Github API error"); + }else{ + $last = new \DateTime($info[0]["commit"]["committer"]["date"]); + $last = $last->getTimestamp(); + if($last >= $this->getProperty("last-update") and $this->getProperty("last-update") !== false and \pocketmine\GIT_COMMIT != $info[0]["sha"]){ + console("[NOTICE] " . TextFormat::YELLOW . "A new DEVELOPMENT version of PocketMine-MP has been released!"); + console("[NOTICE] " . TextFormat::YELLOW . "Commit \"" . $info[0]["commit"]["message"] . "\" [" . substr($info[0]["sha"], 0, 10) . "] by " . $info[0]["commit"]["committer"]["name"]); + console("[NOTICE] " . TextFormat::YELLOW . "Get it at PocketMine.net or at https://github.com/PocketMine/PocketMine-MP/archive/" . $info[0]["sha"] . ".zip"); + console("[NOTICE] This message will disappear after issuing the command \"/update-done\""); + }else{ + $this->setProperty("last-update", time()); + console("[INFO] " . TextFormat::AQUA . "This is the latest DEVELOPMENT version"); + } + } + }else{ + $info = json_decode(Utils::getURL("https://api.github.com/repos/PocketMine/PocketMine-MP/tags"), true); + if($info === false or !isset($info[0])){ + console("[ERROR] Github API error"); + }else{ + $newest = new VersionString(VERSION); + $newestN = $newest->getNumber(); + $update = new VersionString($info[0]["name"]); + $updateN = $update->getNumber(); + if($updateN > $newestN){ + console("[NOTICE] " . TextFormat::GREEN . "A new STABLE version of PocketMine-MP has been released!"); + console("[NOTICE] " . TextFormat::GREEN . "Version \"" . $info[0]["name"] . "\" #" . $updateN); + console("[NOTICE] Get it at PocketMine.net or at " . $info[0]["zipball_url"]); + console("[NOTICE] This message will disappear as soon as you update"); + }else{ + $this->setProperty("last-update", time()); + console("[INFO] " . TextFormat::AQUA . "This is the latest STABLE version"); + } + } + } + } + + $this->loadProperties(); + + + $this->apiList[] = $this->console = new ConsoleAPI(); + $this->apiList[] = $this->level = new LevelAPI(); + + $this->apiList[] = $this->block = new BlockAPI(); + $this->apiList[] = $this->chat = new ChatAPI(); + $this->apiList[] = $this->ban = new BanAPI(); + $this->apiList[] = $this->player = new PlayerAPI(); + $this->apiList[] = $this->time = new TimeAPI(); + + foreach($this->apiList as $ob){ + if(is_callable(array($ob, "init"))){ + $ob->init(); //Fails sometimes!!! + } + } + + console("[INFO] Loaded " . count(PluginManager::loadPlugins(\pocketmine\DATA . "plugins/")) . " plugin(s)."); + + } + + public function async(callable $callable, $params = array(), $remove = false){ + $cnt = $this->asyncCnt++; + $this->asyncCalls[$cnt] = new \Async($callable, $params); + + return $remove === true ? $this->getAsync($cnt) : $cnt; + } + + public function getAsync($id){ + if(!isset($this->asyncCalls[$id])){ + return false; + } + $ob = $this->asyncCalls[$id]; + unset($this->asyncCalls[$id]); + + return $ob; + } + + public function __destruct(){ + foreach($this->apiList as $i => $ob){ + if(method_exists($ob, "__destruct")){ + $ob->__destruct(); + unset($this->apiList[$i]); + } + } + } + + private function writeProperties(){ + $this->config->save(); + } + + public function init(){ + if(!(self::$serverRequest instanceof Server)){ + self::$serverRequest = $this->server; + } + + + if($this->getProperty("send-usage", true) !== false){ + $this->server->schedule(6000, array($this, "sendUsage"), array(), true); //Send the info after 5 minutes have passed + $this->sendUsage(); + } + if($this->getProperty("auto-save") === true){ + $this->server->schedule(18000, array($this, "autoSave"), array(), true); + } + if(!defined("NO_THREADS") and $this->getProperty("enable-rcon") === true){ + $this->rcon = new RCON($this->getProperty("rcon.password", ""), $this->getProperty("rcon.port", $this->getProperty("server-port")), ($ip = $this->getProperty("server-ip")) != "" ? $ip : "0.0.0.0", $this->getProperty("rcon.threads", 1), $this->getProperty("rcon.clients-per-thread", 50)); + } + + if($this->getProperty("enable-query") === true){ + $this->query = new QueryHandler(); + } + + $this->schedule(2, array($this, "checkTickUpdates"), array(), true); + $this->server->init(); + unregister_tick_function(array($this->server, "tick")); + $this->console->__destruct(); + if($this->rcon instanceof RCON){ + $this->rcon->stop(); + } + $this->__destruct(); + if($this->getProperty("upnp-forwarding") === true){ + console("[INFO] [UPnP] Removing port forward..."); + UPnP::RemovePortForward($this->getProperty("server-port")); + } + + return $this->restart; + } + + /*-------------------------------------------------------------*/ + + public function asyncOperation($t, $d, $c = null){ + return $this->server->asyncOperation($t, $d, $c); + } + + public function addHandler($e, $c, $p = 5){ + return $this->server->addHandler($e, $c, $p); + } + + public function dhandle($e, $d){ + return $this->server->handle($e, $d); + } + + public function handle($e, &$d){ + return $this->server->handle($e, $d); + } + + public function schedule($t, $c, $d, $r = false, $e = "server.schedule"){ + return $this->server->schedule($t, $c, $d, $r, $e); + } + + public function event($e, $d){ + return $this->server->event($e, $d); + } + + public function trigger($e, $d){ + return $this->server->trigger($e, $d); + } + + public function deleteEvent($id){ + return $this->server->deleteEvent($id); + } + + public function getProperties(){ + return $this->config->getAll(); + } + + public function getProperty($name, $default = false){ + $v = getopt("", array("$name::")); + if(isset($v[$name]) !== false){ //Allow for command-line arguments + $v = $v[$name]; + switch(strtolower(trim($v))){ + case "": + case "on": + case "true": + case "yes": + $v = true; + break; + case "off": + case "false": + case "no": + $v = false; + break; + } + switch($name){ + case "last-update": + if($v === false){ + $v = time(); + }else{ + $v = (int) $v; + } + break; + case "gamemode": + case "max-players": + case "server-port": + case "debug": + case "difficulty": + $v = (int) $v; + break; + } + + return $v; + } + + return ($this->config->exists($name) ? $this->config->get($name) : $default); + } + + public function setProperty($name, $value, $save = true){ + $this->config->set($name, $value); + if($save == true){ + $this->writeProperties(); + } + $this->loadProperties(); + } + + public function getList(){ + return $this->apiList; + } +} diff --git a/src/pocketmine/ServerOld.php b/src/pocketmine/ServerOld.php new file mode 100644 index 000000000..02e6b9e82 --- /dev/null +++ b/src/pocketmine/ServerOld.php @@ -0,0 +1,735 @@ +version = new VersionString(); + if(defined("pocketmine\\DEBUG") and \pocketmine\DEBUG >= 0 and function_exists("cli_set_process_title")){ + @cli_set_process_title("PocketMine-MP " . \pocketmine\VERSION); + } + console("[INFO] Starting Minecraft PE server on " . ($this->serverip === "0.0.0.0" ? "*" : $this->serverip) . ":" . $this->port); + define("BOOTUP_RANDOM", Utils::getRandomBytes(16)); + $this->serverID = $this->serverID === false ? Utils::readLong(substr(Utils::getUniqueID(true, $this->serverip . $this->port), 8)) : $this->serverID; + $this->seed = $this->seed === false ? Utils::readInt(Utils::getRandomBytes(4, false)) : $this->seed; + $this->startDatabase(); + $this->api = false; + $this->tCnt = 1; + $this->events = array(); + $this->eventsID = array(); + $this->handlers = array(); + $this->invisible = false; + $this->difficulty = 1; + $this->custom = array(); + $this->evCnt = 1; + $this->handCnt = 1; + $this->eidCnt = 1; + $this->maxClients = 20; + $this->schedule = array(); + $this->scheduleCnt = 1; + $this->description = ""; + $this->memoryStats = array(); + $this->spawn = false; + $this->saveEnabled = true; + $this->whitelist = false; + $this->tickMeasure = array_fill(0, 40, 0); + $this->setType("normal"); + $this->interface = new Handler("255.255.255.255", $this->port, $this->serverip); + $this->stop = false; + $this->ticks = 0; + if(!defined("NO_THREADS")){ + $this->asyncThread = new \AsyncMultipleQueue(); + } + } + + function __construct($name, $gamemode = 0, $seed = false, $port = 19132, $serverip = "0.0.0.0"){ + $this->port = (int) $port; + $this->doTick = true; + $this->gamemode = (int) $gamemode; + $this->name = $name; + $this->motd = "Welcome to " . $name; + $this->serverID = false; + $this->seed = $seed; + $this->serverip = $serverip; + self::$instance = $this; + $this->load(); + } + + /** + * @return float + */ + public function getTPS(){ + $v = array_values($this->tickMeasure); + $tps = 40 / ($v[39] - $v[0]); + + return round($tps, 4); + } + + public function titleTick(){ + $time = microtime(true); + if(defined("pocketmine\\DEBUG") and \pocketmine\DEBUG >= 0 and \pocketmine\ANSI === true){ + echo "\x1b]0;PocketMine-MP " . VERSION . " | Online " . count(Player::$list) . "/" . $this->maxClients . " | RAM " . round((memory_get_usage() / 1024) / 1024, 2) . "MB | U " . round(($this->interface->bandwidth[1] / max(1, $time - $this->interface->bandwidth[2])) / 1024, 2) . " D " . round(($this->interface->bandwidth[0] / max(1, $time - $this->interface->bandwidth[2])) / 1024, 2) . " kB/s | TPS " . $this->getTPS() . "\x07"; + } + $this->interface->bandwidth = array(0, 0, $time); + } + + public function loadEvents(){ + if(\pocketmine\ANSI === true){ + $this->schedule(30, array($this, "titleTick"), array(), true); + } + $this->schedule(20 * 15, array($this, "checkTicks"), array(), true); + $this->schedule(20 * 60, array($this, "checkMemory"), array(), true); + $this->schedule(20 * 45, "pocketmine\\utils\\Cache::cleanup", array(), true); + $this->schedule(20, array($this, "asyncOperationChecker"), array(), true); + } + + public function checkTicks(){ + if($this->getTPS() < 12){ + console("[WARNING] Can't keep up! Is the server overloaded?"); + } + } + + public function checkMemory(){ + $info = $this->debugInfo(); + $data = $info["memory_usage"] . "," . $info["players"] . "," . $info["entities"]; + $i = count($this->memoryStats) - 1; + if($i < 0 or $this->memoryStats[$i] !== $data){ + $this->memoryStats[] = $data; + } + } + + public function startDatabase(){ + $this->preparedSQL = new \stdClass(); + $this->preparedSQL->entity = new \stdClass(); + $this->database = new \SQLite3(":memory:", SQLITE3_OPEN_READWRITE | SQLITE3_OPEN_CREATE); + $this->query("PRAGMA journal_mode = OFF;"); + $this->query("PRAGMA encoding = \"UTF-8\";"); + $this->query("PRAGMA secure_delete = OFF;"); + $this->query("CREATE TABLE actions (ID INTEGER PRIMARY KEY, interval NUMERIC, last NUMERIC, code TEXT, repeat NUMERIC);"); + $this->query("CREATE TABLE handlers (ID INTEGER PRIMARY KEY, name TEXT, priority NUMERIC);"); + $this->query("CREATE TABLE blockUpdates (level TEXT, x INTEGER, y INTEGER, z INTEGER, type INTEGER, delay NUMERIC);"); + $this->query("CREATE TABLE recipes (id INTEGER PRIMARY KEY, type NUMERIC, recipe TEXT);"); + $this->query("PRAGMA synchronous = OFF;"); + $this->preparedSQL->selectHandlers = $this->database->prepare("SELECT DISTINCT ID FROM handlers WHERE name = :name ORDER BY priority DESC;"); + $this->preparedSQL->selectActions = $this->database->prepare("SELECT ID,code,repeat FROM actions WHERE last <= (:time - interval);"); + $this->preparedSQL->updateAction = $this->database->prepare("UPDATE actions SET last = :time WHERE ID = :id;"); + } + + public function query($sql, $fetch = false){ + $result = $this->database->query($sql) or console("[ERROR] [SQL Error] " . $this->database->lastErrorMsg() . ". Query: " . $sql, true, true, 0); + if($fetch === true and ($result instanceof \SQLite3Result)){ + $result = $result->fetchArray(SQLITE3_ASSOC); + } + + return $result; + } + + public function debugInfo($console = false){ + $info = array(); + $info["tps"] = $this->getTPS(); + $info["memory_usage"] = round((memory_get_usage() / 1024) / 1024, 2) . "MB"; + $info["memory_peak_usage"] = round((memory_get_peak_usage() / 1024) / 1024, 2) . "MB"; + $info["entities"] = count(Entity::$list); + $info["players"] = count(Player::$list); + $info["events"] = count($this->eventsID); + $info["handlers"] = $this->query("SELECT count(ID) as count FROM handlers;", true); + $info["handlers"] = $info["handlers"]["count"]; + $info["actions"] = $this->query("SELECT count(ID) as count FROM actions;", true); + $info["actions"] = $info["actions"]["count"]; + $info["garbage"] = gc_collect_cycles(); + $this->handle("server.debug", $info); + if($console === true){ + console("[DEBUG] TPS: " . $info["tps"] . ", Memory usage: " . $info["memory_usage"] . " (Peak " . $info["memory_peak_usage"] . "), Entities: " . $info["entities"] . ", Events: " . $info["events"] . ", Handlers: " . $info["handlers"] . ", Actions: " . $info["actions"] . ", Garbage: " . $info["garbage"], true, true, 2); + } + + return $info; + } + + /** + * @param string $reason + */ + public function close($reason = "server stop"){ + if($this->stop !== true){ + if(is_int($reason)){ + $reason = "signal stop"; + } + if(($this->api instanceof ServerAPI) === true){ + if(($this->api->chat instanceof ChatAPI) === true){ + Player::broadcastMessage("Stopping server..."); + } + } + $this->stop = true; + $this->trigger("server.close", $reason); + $this->interface->close(); + + if(!defined("NO_THREADS")){ + @$this->asyncThread->stop = true; + } + } + } + + public function setType($type = "normal"){ + switch(trim(strtolower($type))){ + case "normal": + case "demo": + $this->serverType = "MCCPP;Demo;"; + break; + case "minecon": + $this->serverType = "MCCPP;MINECON;"; + break; + } + + } + + public function asyncOperation($type, array $data, callable $callable = null){ + if(defined("NO_THREADS")){ + return false; + } + $d = ""; + $type = (int) $type; + switch($type){ + case ASYNC_CURL_GET: + $d .= Utils::writeShort(strlen($data["url"])) . $data["url"] . (isset($data["timeout"]) ? Utils::writeShort($data["timeout"]) : Utils::writeShort(10)); + break; + case ASYNC_CURL_POST: + $d .= Utils::writeShort(strlen($data["url"])) . $data["url"] . (isset($data["timeout"]) ? Utils::writeShort($data["timeout"]) : Utils::writeShort(10)); + $d .= Utils::writeShort(count($data["data"])); + foreach($data["data"] as $key => $value){ + $d .= Utils::writeShort(strlen($key)) . $key . Utils::writeInt(strlen($value)) . $value; + } + break; + case ASYNC_FUNCTION: + $params = serialize($data["arguments"]); + $d .= Utils::writeShort(strlen($data["function"])) . $data["function"] . Utils::writeInt(strlen($params)) . $params; + break; + default: + return false; + } + $ID = $this->asyncID++; + $this->async[$ID] = $callable; + $this->asyncThread->input .= Utils::writeInt($ID) . Utils::writeShort($type) . $d; + + return $ID; + } + + public function asyncOperationChecker(){ + if(defined("NO_THREADS")){ + return false; + } + if(isset($this->asyncThread->output{5})){ + $offset = 0; + $ID = Utils::readInt(substr($this->asyncThread->output, $offset, 4)); + $offset += 4; + $type = Utils::readShort(substr($this->asyncThread->output, $offset, 2)); + $offset += 2; + $data = array(); + switch($type){ + case ASYNC_CURL_GET: + case ASYNC_CURL_POST: + $len = Utils::readInt(substr($this->asyncThread->output, $offset, 4)); + $offset += 4; + $data["result"] = substr($this->asyncThread->output, $offset, $len); + $offset += $len; + break; + case ASYNC_FUNCTION: + $len = Utils::readInt(substr($this->asyncThread->output, $offset, 4)); + $offset += 4; + $data["result"] = unserialize(substr($this->asyncThread->output, $offset, $len)); + $offset += $len; + break; + } + $this->asyncThread->output = substr($this->asyncThread->output, $offset); + if(isset($this->async[$ID]) and $this->async[$ID] !== null and is_callable($this->async[$ID])){ + if(is_array($this->async[$ID])){ + $method = $this->async[$ID][1]; + $result = $this->async[$ID][0]->$method($data, $type, $ID); + }else{ + $result = $this->async[$ID]($data, $type, $ID); + } + } + unset($this->async[$ID]); + } + } + + /** + * @param string $event + * @param callable $callable + * @param integer $priority + * + * @return boolean + */ + public function addHandler($event, callable $callable, $priority = 5){ + if(!is_callable($callable)){ + return false; + } + $priority = (int) $priority; + $hnid = $this->handCnt++; + $this->handlers[$hnid] = $callable; + $this->query("INSERT INTO handlers (ID, name, priority) VALUES (" . $hnid . ", '" . str_replace("'", "\\'", $event) . "', " . $priority . ");"); + console("[INTERNAL] New handler " . (is_array($callable) ? get_class($callable[0]) . "::" . $callable[1] : $callable) . " to special event " . $event . " (ID " . $hnid . ")", true, true, 3); + + return $hnid; + } + + public function dhandle($e, $d){ + return $this->handle($e, $d); + } + + public function handle($event, &$data){ + $this->preparedSQL->selectHandlers->reset(); + $this->preparedSQL->selectHandlers->clear(); + $this->preparedSQL->selectHandlers->bindValue(":name", $event, SQLITE3_TEXT); + $handlers = $this->preparedSQL->selectHandlers->execute(); + $result = null; + if($handlers instanceof \SQLite3Result){ + $call = array(); + while(($hn = $handlers->fetchArray(SQLITE3_ASSOC)) !== false){ + $call[(int) $hn["ID"]] = true; + } + $handlers->finalize(); + foreach($call as $hnid => $boolean){ + if($result !== false and $result !== true){ + $handler = $this->handlers[$hnid]; + if(is_array($handler)){ + $method = $handler[1]; + $result = $handler[0]->$method($data, $event); + }else{ + $result = $handler($data, $event); + } + }else{ + break; + } + } + } + + if($result !== false){ + $this->trigger($event, $data); + } + + return $result; + } + + public function eventHandler($data, $event){ + switch($event){ + + } + } + + /** + * TODO + * @return string + */ + public function getGamemode(){ + switch($this->gamemode){ + case 0: + return "survival"; + case 1: + return "creative"; + case 2: + return "adventure"; + case 3: + return "view"; + } + } + + + public function init(){ + register_tick_function(array($this, "tick")); + declare(ticks = 5000); //Minimum TPS for main thread locks + + $this->loadEvents(); + register_shutdown_function(array($this, "dumpError")); + register_shutdown_function(array($this, "close")); + if(function_exists("pcntl_signal")){ + pcntl_signal(SIGTERM, array($this, "close")); + pcntl_signal(SIGINT, array($this, "close")); + pcntl_signal(SIGHUP, array($this, "close")); + } + console("[INFO] Default game type: " . strtoupper($this->getGamemode())); + $this->trigger("server.start", microtime(true)); + console('[INFO] Done (' . round(microtime(true) - \pocketmine\START_TIME, 3) . 's)! For help, type "help" or "?"'); + $this->process(); + } + + public function dumpError(){ + if($this->stop === true){ + return; + } + ini_set("memory_limit", "-1"); //Fix error dump not dumped on memory problems + console("[SEVERE] An unrecovereable has ocurred and the server has crashed. Creating an error dump"); + $dump = "```\r\n# PocketMine-MP Error Dump " . date("D M j H:i:s T Y") . "\r\n"; + $er = error_get_last(); + $errorConversion = array( + E_ERROR => "E_ERROR", + E_WARNING => "E_WARNING", + E_PARSE => "E_PARSE", + E_NOTICE => "E_NOTICE", + E_CORE_ERROR => "E_CORE_ERROR", + E_CORE_WARNING => "E_CORE_WARNING", + E_COMPILE_ERROR => "E_COMPILE_ERROR", + E_COMPILE_WARNING => "E_COMPILE_WARNING", + E_USER_ERROR => "E_USER_ERROR", + E_USER_WARNING => "E_USER_WARNING", + E_USER_NOTICE => "E_USER_NOTICE", + E_STRICT => "E_STRICT", + E_RECOVERABLE_ERROR => "E_RECOVERABLE_ERROR", + E_DEPRECATED => "E_DEPRECATED", + E_USER_DEPRECATED => "E_USER_DEPRECATED", + ); + $er["type"] = isset($errorConversion[$er["type"]]) ? $errorConversion[$er["type"]] : $er["type"]; + $dump .= "Error: " . var_export($er, true) . "\r\n\r\n"; + if(stripos($er["file"], "plugin") !== false){ + $dump .= "THIS ERROR WAS CAUSED BY A PLUGIN. REPORT IT TO THE PLUGIN DEVELOPER.\r\n"; + } + + $dump .= "Code: \r\n"; + $file = @file($er["file"], FILE_IGNORE_NEW_LINES); + for($l = max(0, $er["line"] - 10); $l < $er["line"] + 10; ++$l){ + $dump .= "[" . ($l + 1) . "] " . @$file[$l] . "\r\n"; + } + $dump .= "\r\n\r\n"; + $dump .= "Backtrace: \r\n"; + foreach(getTrace() as $line){ + $dump .= "$line\r\n"; + } + $dump .= "\r\n\r\n"; + $version = new VersionString(); + $dump .= "PocketMine-MP version: " . $version . " #" . $version->getNumber() . " [Protocol " . Info::CURRENT_PROTOCOL . "; API " . API_VERSION . "]\r\n"; + $dump .= "Git commit: " . GIT_COMMIT . "\r\n"; + $dump .= "uname -a: " . php_uname("a") . "\r\n"; + $dump .= "PHP Version: " . phpversion() . "\r\n"; + $dump .= "Zend version: " . zend_version() . "\r\n"; + $dump .= "OS : " . PHP_OS . ", " . Utils::getOS() . "\r\n"; + $dump .= "Debug Info: " . var_export($this->debugInfo(false), true) . "\r\n\r\n\r\n"; + global $arguments; + $dump .= "Parameters: " . var_export($arguments, true) . "\r\n\r\n\r\n"; + $p = $this->api->getProperties(); + if($p["rcon.password"] != ""){ + $p["rcon.password"] = "******"; + } + $dump .= "server.properties: " . var_export($p, true) . "\r\n\r\n\r\n"; + if(class_exists("pocketmine\\plugin\\PluginManager", false)){ + $dump .= "Loaded plugins:\r\n"; + foreach(PluginManager::getPlugins() as $p){ + $d = $p->getDescription(); + $dump .= $d->getName() . " " . $d->getVersion() . " by " . implode(", ", $d->getAuthors()) . "\r\n"; + } + $dump .= "\r\n\r\n"; + } + + $extensions = array(); + foreach(get_loaded_extensions() as $ext){ + $extensions[$ext] = phpversion($ext); + } + + $dump .= "Loaded Modules: " . var_export($extensions, true) . "\r\n"; + $this->checkMemory(); + $dump .= "Memory Usage Tracking: \r\n" . chunk_split(base64_encode(gzdeflate(implode(";", $this->memoryStats), 9))) . "\r\n"; + ob_start(); + phpinfo(); + $dump .= "\r\nphpinfo(): \r\n" . chunk_split(base64_encode(gzdeflate(ob_get_contents(), 9))) . "\r\n"; + ob_end_clean(); + $dump .= "\r\n```"; + $name = "Error_Dump_" . date("D_M_j-H.i.s-T_Y"); + log($dump, $name, true, 0, true); + console("[SEVERE] Please submit the \"{$name}.log\" file to the Bug Reporting page. Give as much info as you can.", true, true, 0); + } + + public function tick(){ + $time = microtime(true); + if($this->lastTick <= ($time - 0.05)){ + $this->tickMeasure[] = $this->lastTick = $time; + unset($this->tickMeasure[key($this->tickMeasure)]); + ++$this->ticks; + + return $this->tickerFunction($time); + } + + return 0; + } + + public static function clientID($ip, $port){ + return crc32($ip . $port) ^ crc32($port . $ip . BOOTUP_RANDOM); + //return $ip . ":" . $port; + } + + public function packetHandler(Packet $packet){ + $data =& $packet; + $CID = Server::clientID($packet->ip, $packet->port); + if(isset(Player::$list[$CID])){ + if($packet instanceof RakNetPacket){ + Player::$list[$CID]->handlePacket($packet); + } + }else{ + switch($packet->pid()){ + case RakNetInfo::UNCONNECTED_PING: + case RakNetInfo::UNCONNECTED_PING_OPEN_CONNECTIONS: + if($this->invisible === true){ + $pk = new RakNetPacket(RakNetInfo::UNCONNECTED_PONG); + $pk->pingID = $packet->pingID; + $pk->serverID = $this->serverID; + $pk->serverType = $this->serverType; + $pk->ip = $packet->ip; + $pk->port = $packet->port; + $this->send($pk); + break; + } + if(!isset($this->custom["times_" . $CID])){ + $this->custom["times_" . $CID] = 0; + } + $ln = 15; + if($this->description == "" or substr($this->description, -1) != " "){ + $this->description .= " "; + } + $txt = substr($this->description, $this->custom["times_" . $CID], $ln); + $txt .= substr($this->description, 0, $ln - strlen($txt)); + $pk = new RakNetPacket(RakNetInfo::UNCONNECTED_PONG); + $pk->pingID = $packet->pingID; + $pk->serverID = $this->serverID; + $pk->serverType = $this->serverType . $this->name . " [" . count(Player::$list) . "/" . $this->maxClients . "] " . $txt; + $pk->ip = $packet->ip; + $pk->port = $packet->port; + $this->send($pk); + $this->custom["times_" . $CID] = ($this->custom["times_" . $CID] + 1) % strlen($this->description); + break; + case RakNetInfo::OPEN_CONNECTION_REQUEST_1: + if($packet->structure !== RakNetInfo::STRUCTURE){ + console("[DEBUG] Incorrect structure #" . $packet->structure . " from " . $packet->ip . ":" . $packet->port, true, true, 2); + $pk = new RakNetPacket(RakNetInfo::INCOMPATIBLE_PROTOCOL_VERSION); + $pk->serverID = $this->serverID; + $pk->ip = $packet->ip; + $pk->port = $packet->port; + $this->send($pk); + }else{ + $pk = new RakNetPacket(RakNetInfo::OPEN_CONNECTION_REPLY_1); + $pk->serverID = $this->serverID; + $pk->mtuSize = strlen($packet->buffer); + $pk->ip = $packet->ip; + $pk->port = $packet->port; + $this->send($pk); + } + break; + case RakNetInfo::OPEN_CONNECTION_REQUEST_2: + if($this->invisible === true){ + break; + } + + new Player($packet->clientID, $packet->ip, $packet->port, $packet->mtuSize); //New Session! + $pk = new RakNetPacket(RakNetInfo::OPEN_CONNECTION_REPLY_2); + $pk->serverID = $this->serverID; + $pk->serverPort = $this->port; + $pk->mtuSize = $packet->mtuSize; + $pk->ip = $packet->ip; + $pk->port = $packet->port; + $this->send($pk); + break; + } + } + } + + public function send(Packet $packet){ + return $this->interface->writePacket($packet); + } + + public function process(){ + $lastLoop = 0; + while($this->stop === false){ + $packet = $this->interface->readPacket(); + if($packet instanceof Packet){ + $this->packetHandler($packet); + $lastLoop = 0; + } + if(($ticks = $this->tick()) === 0){ + ++$lastLoop; + if($lastLoop < 16){ + usleep(1); + }elseif($lastLoop < 128){ + usleep(1000); + }elseif($lastLoop < 256){ + usleep(2000); + }else{ + usleep(4000); + } + } + } + } + + public function trigger($event, $data = ""){ + if(isset($this->events[$event])){ + foreach($this->events[$event] as $evid => $ev){ + if(!is_callable($ev)){ + $this->deleteEvent($evid); + continue; + } + if(is_array($ev)){ + $method = $ev[1]; + $ev[0]->$method($data, $event); + }else{ + $ev($data, $event); + } + } + } + } + + public function schedule($ticks, callable $callback, $data = array(), $repeat = false, $eventName = "server.schedule"){ + if(!is_callable($callback)){ + return false; + } + $chcnt = $this->scheduleCnt++; + $this->schedule[$chcnt] = array($callback, $data, $eventName); + $this->query("INSERT INTO actions (ID, interval, last, repeat) VALUES(" . $chcnt . ", " . ($ticks / 20) . ", " . microtime(true) . ", " . (((bool) $repeat) === true ? 1 : 0) . ");"); + + return $chcnt; + } + + public function tickerFunction($time){ + //actions that repeat every x time will go here + $this->preparedSQL->selectActions->reset(); + $this->preparedSQL->selectActions->bindValue(":time", $time, SQLITE3_FLOAT); + $actions = $this->preparedSQL->selectActions->execute(); + + $actionCount = 0; + if($actions instanceof \SQLite3Result){ + while(($action = $actions->fetchArray(SQLITE3_ASSOC)) !== false){ + $cid = $action["ID"]; + $this->preparedSQL->updateAction->reset(); + $this->preparedSQL->updateAction->bindValue(":time", $time, SQLITE3_FLOAT); + $this->preparedSQL->updateAction->bindValue(":id", $cid, SQLITE3_INTEGER); + $this->preparedSQL->updateAction->execute(); + if(!@is_callable($this->schedule[$cid][0])){ + $return = false; + }else{ + ++$actionCount; + $return = call_user_func($this->schedule[$cid][0], $this->schedule[$cid][1], $this->schedule[$cid][2]); + } + + if($action["repeat"] == 0 or $return === false){ + $this->query("DELETE FROM actions WHERE ID = " . $action["ID"] . ";"); + $this->schedule[$cid] = null; + unset($this->schedule[$cid]); + } + } + $actions->finalize(); + } + + return $actionCount; + } + + public function event($event, callable $func){ + if(!is_callable($func)){ + return false; + } + $evid = $this->evCnt++; + if(!isset($this->events[$event])){ + $this->events[$event] = array(); + } + $this->events[$event][$evid] = $func; + $this->eventsID[$evid] = $event; + console("[INTERNAL] Attached " . (is_array($func) ? get_class($func[0]) . "::" . $func[1] : $func) . " to event " . $event . " (ID " . $evid . ")", true, true, 3); + + return $evid; + } + + public function deleteEvent($id){ + $id = (int) $id; + if(isset($this->eventsID[$id])){ + $ev = $this->eventsID[$id]; + $this->eventsID[$id] = null; + unset($this->eventsID[$id]); + $this->events[$ev][$id] = null; + unset($this->events[$ev][$id]); + if(count($this->events[$ev]) === 0){ + unset($this->events[$ev]); + } + } + } + +} diff --git a/src/pocketmine/TimeAPI.php b/src/pocketmine/TimeAPI.php new file mode 100644 index 000000000..7804cb150 --- /dev/null +++ b/src/pocketmine/TimeAPI.php @@ -0,0 +1,144 @@ + 0, + "sunset" => 9500, + "night" => 10900, + "sunrise" => 17800, + ); + private $server; + + function __construct(){ + $this->server = Server::getInstance(); + } + + public function init(){ + $this->server->api->console->register("time", " [time]", array($this, "commandHandler")); + } + + public function commandHandler($cmd, $params, $issuer, $alias){ + $output = ""; + switch($cmd){ + case "time": + $level = false; + if($issuer instanceof Player){ + $level = $issuer->level; + } + $p = strtolower(array_shift($params)); + switch($p){ + case "check": + $output .= "Time: " . $this->getDate($level) . ", " . $this->getPhase($level) . " (" . $this->get(true, $level) . ")\n"; + break; + case "add": + $output .= "Set the time to " . $this->add(array_shift($params), $level) . "\n"; + break; + case "set": + $output .= "Set the time to " . $this->set(array_shift($params), $level) . "\n"; + break; + case "sunrise": + case "day": + case "sunset": + case "night": + $output .= "Set the time to " . $this->set($p, $level) . "\n"; + break; + default: + $output .= "Usage: /time [time]\n"; + break; + } + break; + } + + return $output; + } + + public function night(){ + return $this->set("night"); + } + + public function day(){ + return $this->set("day"); + } + + public function sunrise(){ + return $this->set("sunrise"); + } + + public function sunset(){ + return $this->set("sunset"); + } + + public function get($raw = false, $level = false){ + if(!($level instanceof Level)){ + $level = Level::getDefault(); + } + + return $raw === true ? $level->getTime() : abs($level->getTime()) % 19200; + } + + public function add($time, $level = false){ + if(!($level instanceof Level)){ + $level = Level::getDefault(); + } + $level->setTime($level->getTime() + (int) $time); + } + + public function getDate($time = false){ + $time = !is_integer($time) ? $this->get(false, $time) : $time; + + return str_pad(strval((floor($time / 800) + 6) % 24), 2, "0", STR_PAD_LEFT) . ":" . str_pad(strval(floor(($time % 800) / 13.33)), 2, "0", STR_PAD_LEFT); + } + + public function getPhase($time = false){ + $time = !is_integer($time) ? $this->get(false, $time) : $time; + if($time < TimeAPI::$phases["sunset"]){ + $time = "day"; + }elseif($time < TimeAPI::$phases["night"]){ + $time = "sunset"; + }elseif($time < TimeAPI::$phases["sunrise"]){ + $time = "night"; + }else{ + $time = "sunrise"; + } + + return $time; + } + + public function set($time, $level = false){ + if(!($level instanceof Level)){ + $level = Level::getDefault(); + } + if(is_string($time) and isset(TimeAPI::$phases[$time])){ + $level->setTime(TimeAPI::$phases[$time]); + }else{ + $level->setTime((int) $time); + } + + return $level->getTime(); + } + + +} diff --git a/src/pocketmine/block/Air.php b/src/pocketmine/block/Air.php new file mode 100644 index 000000000..ad1ee5ff4 --- /dev/null +++ b/src/pocketmine/block/Air.php @@ -0,0 +1,44 @@ +isActivable = false; + $this->breakable = false; + $this->isFlowable = true; + $this->isTransparent = true; + $this->isReplaceable = true; + $this->isPlaceable = false; + $this->hasPhysics = false; + $this->isSolid = false; + $this->isFullBlock = true; + $this->hardness = 0; + + } + +} \ No newline at end of file diff --git a/src/pocketmine/block/Bed.php b/src/pocketmine/block/Bed.php new file mode 100644 index 000000000..8a56ca641 --- /dev/null +++ b/src/pocketmine/block/Bed.php @@ -0,0 +1,141 @@ +isActivable = true; + $this->isFullBlock = false; + $this->hardness = 1; + } + + public function onActivate(Item $item, Player $player = null){ + if($player instanceof Player and Server::getInstance()->api->time->getPhase($this->level) !== "night"){ + $pk = new ChatPacket; + $pk->message = "You can only sleep at night"; + $player->dataPacket($pk); + + return true; + } + + $blockNorth = $this->getSide(2); //Gets the blocks around them + $blockSouth = $this->getSide(3); + $blockEast = $this->getSide(5); + $blockWest = $this->getSide(4); + if(($this->meta & 0x08) === 0x08){ //This is the Top part of bed + $b = $this; + }else{ //Bottom Part of Bed + if($blockNorth->getID() === $this->id and ($blockNorth->meta & 0x08) === 0x08){ + $b = $blockNorth; + }elseif($blockSouth->getID() === $this->id and ($blockSouth->meta & 0x08) === 0x08){ + $b = $blockSouth; + }elseif($blockEast->getID() === $this->id and ($blockEast->meta & 0x08) === 0x08){ + $b = $blockEast; + }elseif($blockWest->getID() === $this->id and ($blockWest->meta & 0x08) === 0x08){ + $b = $blockWest; + }elseif($player instanceof Player){ + $pk = new ChatPacket; + $pk->message = "This bed is incomplete"; + $player->dataPacket($pk); + + return true; + } + } + + if($player instanceof Player and $player->sleepOn($b) === false){ + $pk = new ChatPacket; + $pk->message = "This bed is occupied"; + $player->dataPacket($pk); + } + + return true; + } + + public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ + $down = $this->getSide(0); + if($down->isTransparent === false){ + $faces = array( + 0 => 3, + 1 => 4, + 2 => 2, + 3 => 5, + ); + $d = $player instanceof Player ? $player->getDirection() : 0; + $next = $this->getSide($faces[(($d + 3) % 4)]); + $downNext = $this->getSide(0); + if($next->isReplaceable === true and $downNext->isTransparent === false){ + $meta = (($d + 3) % 4) & 0x03; + $this->level->setBlock($block, Block::get($this->id, $meta), true, false, true); + $this->level->setBlock($next, Block::get($this->id, $meta | 0x08), true, false, true); + + return true; + } + } + + return false; + } + + public function onBreak(Item $item){ + $blockNorth = $this->getSide(2); //Gets the blocks around them + $blockSouth = $this->getSide(3); + $blockEast = $this->getSide(5); + $blockWest = $this->getSide(4); + + if(($this->meta & 0x08) === 0x08){ //This is the Top part of bed + if($blockNorth->getID() === $this->id and $blockNorth->meta !== 0x08){ //Checks if the block ID and meta are right + $this->level->setBlock($blockNorth, new Air(), true, false, true); + }elseif($blockSouth->getID() === $this->id and $blockSouth->meta !== 0x08){ + $this->level->setBlock($blockSouth, new Air(), true, false, true); + }elseif($blockEast->getID() === $this->id and $blockEast->meta !== 0x08){ + $this->level->setBlock($blockEast, new Air(), true, false, true); + }elseif($blockWest->getID() === $this->id and $blockWest->meta !== 0x08){ + $this->level->setBlock($blockWest, new Air(), true, false, true); + } + }else{ //Bottom Part of Bed + if($blockNorth->getID() === $this->id and ($blockNorth->meta & 0x08) === 0x08){ + $this->level->setBlock($blockNorth, new Air(), true, false, true); + }elseif($blockSouth->getID() === $this->id and ($blockSouth->meta & 0x08) === 0x08){ + $this->level->setBlock($blockSouth, new Air(), true, false, true); + }elseif($blockEast->getID() === $this->id and ($blockEast->meta & 0x08) === 0x08){ + $this->level->setBlock($blockEast, new Air(), true, false, true); + }elseif($blockWest->getID() === $this->id and ($blockWest->meta & 0x08) === 0x08){ + $this->level->setBlock($blockWest, new Air(), true, false, true); + } + } + $this->level->setBlock($this, new Air(), true, false, true); + + return true; + } + + public function getDrops(Item $item){ + return array( + array(Item::BED, 0, 1), + ); + } + +} \ No newline at end of file diff --git a/src/pocketmine/block/Bedrock.php b/src/pocketmine/block/Bedrock.php new file mode 100644 index 000000000..5ef5aa996 --- /dev/null +++ b/src/pocketmine/block/Bedrock.php @@ -0,0 +1,37 @@ +breakable = false; + $this->hardness = 18000000; + } + + public function isBreakable(Item $item){ + return false; + } + +} \ No newline at end of file diff --git a/src/pocketmine/block/Beetroot.php b/src/pocketmine/block/Beetroot.php new file mode 100644 index 000000000..897000a3e --- /dev/null +++ b/src/pocketmine/block/Beetroot.php @@ -0,0 +1,94 @@ +isActivable = true; + $this->hardness = 0; + } + + public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ + $down = $this->getSide(0); + if($down->getID() === self::FARMLAND){ + $this->level->setBlock($block, $this, true, false, true); + + return true; + } + + return false; + } + + public function onActivate(Item $item, Player $player = null){ + if($item->getID() === Item::DYE and $item->getMetadata() === 0x0F){ //Bonemeal + $this->meta = 0x07; + $this->level->setBlock($this, $this, true, false, true); + $item->count--; + + return true; + } + + return false; + } + + public function onUpdate($type){ + if($type === Level::BLOCK_UPDATE_NORMAL){ + if($this->getSide(0)->isTransparent === true){ //Replace with common break method + //TODO + //Server::getInstance()->api->entity->drop($this, Item::get(BEETROOT_SEEDS, 0, 1)); + $this->level->setBlock($this, new Air(), false, false, true); + + return Level::BLOCK_UPDATE_NORMAL; + } + }elseif($type === Level::BLOCK_UPDATE_RANDOM){ + if(mt_rand(0, 2) == 1){ + if($this->meta < 0x07){ + ++$this->meta; + $this->level->setBlock($this, $this, true, false, true); + + return Level::BLOCK_UPDATE_RANDOM; + } + }else{ + return Level::BLOCK_UPDATE_RANDOM; + } + } + + return false; + } + + public function getDrops(Item $item){ + $drops = array(); + if($this->meta >= 0x07){ + $drops[] = array(Item::BEETROOT, 0, 1); + $drops[] = array(Item::BEETROOT_SEEDS, 0, mt_rand(0, 3)); + }else{ + $drops[] = array(Item::BEETROOT_SEEDS, 0, 1); + } + + return $drops; + } +} \ No newline at end of file diff --git a/src/pocketmine/block/BirchWoodStairs.php b/src/pocketmine/block/BirchWoodStairs.php new file mode 100644 index 000000000..ed4a3049b --- /dev/null +++ b/src/pocketmine/block/BirchWoodStairs.php @@ -0,0 +1,36 @@ +id, 0, 1), + ); + } +} \ No newline at end of file diff --git a/src/pocketmine/block/Block.php b/src/pocketmine/block/Block.php new file mode 100644 index 000000000..7f2261dfe --- /dev/null +++ b/src/pocketmine/block/Block.php @@ -0,0 +1,543 @@ + new Air(), + self::STONE => new Stone(), + self::GRASS => new Grass(), + self::DIRT => new Dirt(), + self::COBBLESTONE => new Cobblestone(), + self::PLANKS => new Planks(), + self::SAPLING => new Sapling(), + self::BEDROCK => new Bedrock(), + self::WATER => new Water(), + self::STILL_WATER => new StillWater(), + self::LAVA => new Lava(), + self::STILL_LAVA => new StillLava(), + self::SAND => new Sand(), + self::GRAVEL => new Gravel(), + self::GOLD_ORE => new GoldOre(), + self::IRON_ORE => new IronOre(), + self::COAL_ORE => new CoalOre(), + self::WOOD => new Wood(), + self::LEAVES => new Leaves(), + self::SPONGE => new Sponge(), + self::GLASS => new Glass(), + self::LAPIS_ORE => new LapisOre(), + self::LAPIS_BLOCK => new Lapis(), + self::SANDSTONE => new Sandstone(), + self::BED_BLOCK => new Bed(), + self::COBWEB => new Cobweb(), + self::TALL_GRASS => new TallGrass(), + self::DEAD_BUSH => new DeadBush(), + self::WOOL => new Wool(), + self::DANDELION => new Dandelion(), + self::CYAN_FLOWER => new CyanFlower(), + self::BROWN_MUSHROOM => new BrownMushroom(), + self::RED_MUSHROOM => new RedMushroom(), + self::GOLD_BLOCK => new Gold(), + self::IRON_BLOCK => new Iron(), + self::DOUBLE_SLAB => new DoubleSlab(), + self::SLAB => new Slab(), + self::BRICKS_BLOCK => new Bricks(), + self::TNT => new TNT(), + self::BOOKSHELF => new Bookshelf(), + self::MOSS_STONE => new MossStone(), + self::OBSIDIAN => new Obsidian(), + self::TORCH => new Torch(), + self::FIRE => new Fire(), + + self::WOOD_STAIRS => new WoodStairs(), + self::CHEST => new Chest(), + + self::DIAMOND_ORE => new DiamondOre(), + self::DIAMOND_BLOCK => new Diamond(), + self::WORKBENCH => new Workbench(), + self::WHEAT_BLOCK => new Wheat(), + self::FARMLAND => new Farmland(), + self::FURNACE => new Furnace(), + self::BURNING_FURNACE => new BurningFurnace(), + self::SIGN_POST => new SignPost(), + self::WOOD_DOOR_BLOCK => new WoodDoor(), + self::LADDER => new Ladder(), + + self::COBBLESTONE_STAIRS => new CobblestoneStairs(), + self::WALL_SIGN => new WallSign(), + + self::IRON_DOOR_BLOCK => new IronDoor(), + self::REDSTONE_ORE => new RedstoneOre(), + self::GLOWING_REDSTONE_ORE => new GlowingRedstoneOre(), + + self::SNOW_LAYER => new SnowLayer(), + self::ICE => new Ice(), + self::SNOW_BLOCK => new Snow(), + self::CACTUS => new Cactus(), + self::CLAY_BLOCK => new Clay(), + self::SUGARCANE_BLOCK => new Sugarcane(), + + self::FENCE => new Fence(), + self::PUMPKIN => new Pumpkin(), + self::NETHERRACK => new Netherrack(), + self::SOUL_SAND => new SoulSand(), + self::GLOWSTONE_BLOCK => new Glowstone(), + + self::LIT_PUMPKIN => new LitPumpkin(), + self::CAKE_BLOCK => new Cake(), + + self::TRAPDOOR => new Trapdoor(), + + self::STONE_BRICKS => new StoneBricks(), + + self::IRON_BARS => new IronBars(), + self::GLASS_PANE => new GlassPane(), + self::MELON_BLOCK => new Melon(), + self::PUMPKIN_STEM => new PumpkinStem(), + self::MELON_STEM => new MelonStem(), + + self::FENCE_GATE => new FenceGate(), + self::BRICK_STAIRS => new BrickStairs(), + self::STONE_BRICK_STAIRS => new StoneBrickStairs(), + + self::NETHER_BRICKS => new NetherBrick(), + + self::NETHER_BRICKS_STAIRS => new NetherBrickStairs(), + + self::SANDSTONE_STAIRS => new SandstoneStairs(), + + self::SPRUCE_WOOD_STAIRS => new SpruceWoodStairs(), + self::BIRCH_WOOD_STAIRS => new BirchWoodStairs(), + self::JUNGLE_WOOD_STAIRS => new JungleWoodStairs(), + self::STONE_WALL => new StoneWall(), + + self::CARROT_BLOCK => new Carrot(), + self::POTATO_BLOCK => new Potato(), + + self::QUARTZ_BLOCK => new Quartz(), + self::QUARTZ_STAIRS => new QuartzStairs(), + self::DOUBLE_WOOD_SLAB => new DoubleWoodSlab(), + self::WOOD_SLAB => new WoodSlab(), + + self::HAY_BALE => new HayBale(), + self::CARPET => new Carpet(), + + self::COAL_BLOCK => new Coal(), + + self::BEETROOT_BLOCK => new Beetroot(), + self::STONECUTTER => new Stonecutter(), + self::GLOWING_OBSIDIAN => new GlowingObsidian(), + ); + } + } + + /** + * @param int $id + * @param int $meta + * @param Position $pos + * + * @return Block + */ + public static function get($id, $meta = 0, Position $pos = null){ + if(isset(self::$list[$id])){ + $block = clone self::$list[$id]; + $block->setMetadata($meta); + }else{ + $block = new Generic($id, $meta); + } + if($pos instanceof Position){ + $block->position($pos); + } + + return $block; + } + + /** + * @param int $id + * @param int $meta + * @param string $name + */ + public function __construct($id, $meta = 0, $name = "Unknown"){ + $this->id = (int) $id; + $this->meta = (int) $meta; + $this->name = $name; + $this->breakTime = 0.20; + $this->hardness = 10; + } + + /** + * @return int + */ + final public function getHardness(){ + return $this->hardness; + } + + /** + * @return string + */ + final public function getName(){ + return $this->name; + } + + /** + * @return int + */ + final public function getID(){ + return $this->id; + } + + /** + * @return int + */ + final public function getMetadata(){ + return $this->meta; + } + + /** + * @param int $meta + */ + final public function setMetadata($meta){ + $this->meta = $meta & 0x0F; + } + + /** + * Sets the block position to a new Position object + * + * @param Position $v + */ + final public function position(Position $v){ + $this->level = $v->level; + $this->x = (int) $v->x; + $this->y = (int) $v->y; + $this->z = (int) $v->z; + } + + /** + * Returns an array of Item objects to be dropped + * + * @param Item $item + * + * @return array + */ + public function getDrops(Item $item){ + if(!isset(self::$list[$this->id])){ //Unknown blocks + return array(); + }else{ + return array( + array($this->id, $this->meta, 1), + ); + } + } + + /** + * Returns the seconds that this block takes to be broken using an specific Item + * + * @param Item $item + * + * @return float + */ + public function getBreakTime(Item $item){ + return $this->breakTime; + } + + /** + * Returns the Block on the side $side, works like Vector3::side() + * + * @param int $side + * + * @return Block + */ + public function getSide($side){ + $v = parent::getSide($side); + if($this->level instanceof Level){ + return $this->level->getBlock($v); + } + + return $v; + } + + /** + * @return string + */ + final public function __toString(){ + return "Block " . $this->name . " (" . $this->id . ":" . $this->meta . ")"; + } + + /** + * Returns if the item can be broken with an specific Item + * + * @param Item $item + * + * @return bool + */ + abstract function isBreakable(Item $item); + + /** + * Do the actions needed so the block is broken with the Item + * + * @param Item $item + * + * @return mixed + */ + abstract function onBreak(Item $item); + + /** + * Places the Block, using block space and block target, and side. Returns if the block has been placed. + * + * @param Item $item + * @param Block $block + * @param Block $target + * @param int $face + * @param float $fx + * @param float $fy + * @param float $fz + * @param Player $player = null + * + * @return bool + */ + abstract function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null); + + /** + * Do actions when activated by Item. Returns if it has done anything + * + * @param Item $item + * @param Player $player + * + * @return bool + */ + abstract function onActivate(Item $item, Player $player = null); + + /** + * Fires a block update on the Block + * + * @param int $type + * + * @return void + */ + abstract function onUpdate($type); +} diff --git a/src/pocketmine/block/Bookshelf.php b/src/pocketmine/block/Bookshelf.php new file mode 100644 index 000000000..6cabe255e --- /dev/null +++ b/src/pocketmine/block/Bookshelf.php @@ -0,0 +1,31 @@ +hardness = 7.5; + } + +} \ No newline at end of file diff --git a/src/pocketmine/block/BrickStairs.php b/src/pocketmine/block/BrickStairs.php new file mode 100644 index 000000000..ec63c17f9 --- /dev/null +++ b/src/pocketmine/block/BrickStairs.php @@ -0,0 +1,30 @@ +hardness = 30; + } + + public function getBreakTime(Item $item){ + switch($item->isPickaxe()){ + case 5: + return 0.4; + case 4: + return 0.5; + case 3: + return 0.75; + case 2: + return 0.25; + case 1: + return 1.5; + default: + return 10; + } + } + + public function getDrops(Item $item){ + if($item->isPickaxe() >= 1){ + return array( + array(Item::BRICKS_BLOCK, 0, 1), + ); + }else{ + return array(); + } + } +} \ No newline at end of file diff --git a/src/pocketmine/block/BrownMushroom.php b/src/pocketmine/block/BrownMushroom.php new file mode 100644 index 000000000..36021c2b8 --- /dev/null +++ b/src/pocketmine/block/BrownMushroom.php @@ -0,0 +1,58 @@ +hardness = 0; + } + + public function onUpdate($type){ + if($type === Level::BLOCK_UPDATE_NORMAL){ + if($this->getSide(0)->isTransparent === true){ //Replace with common break method + //TODO + //Server::getInstance()->api->entity->drop($this, Item::get($this->id)); + $this->level->setBlock($this, new Air(), false, false, true); + + return Level::BLOCK_UPDATE_NORMAL; + } + } + + return false; + } + + public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ + $down = $this->getSide(0); + if($down->isTransparent === false){ + $this->level->setBlock($block, $this, true, false, true); + + return true; + } + + return false; + } +} \ No newline at end of file diff --git a/src/pocketmine/block/BurningFurnace.php b/src/pocketmine/block/BurningFurnace.php new file mode 100644 index 000000000..71948e267 --- /dev/null +++ b/src/pocketmine/block/BurningFurnace.php @@ -0,0 +1,131 @@ +isActivable = true; + $this->hardness = 17.5; + } + + public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ + $faces = array( + 0 => 4, + 1 => 2, + 2 => 5, + 3 => 3, + ); + $this->meta = $faces[$player instanceof Player ? $player->getDirection() : 0]; + $this->level->setBlock($block, $this, true, false, true); + $nbt = new Compound(false, array( + new Enum("Items", array()), + new String("id", Tile::FURNACE), + new Int("x", $this->x), + new Int("y", $this->y), + new Int("z", $this->z) + )); + $nbt->Items->setTagType(NBT::TAG_Compound); + new Furnace($this->level, $nbt); + + return true; + } + + public function onBreak(Item $item){ + $this->level->setBlock($this, new Air(), true, true, true); + + return true; + } + + public function onActivate(Item $item, Player $player = null){ + if($player instanceof Player){ + $t = $this->level->getTile($this); + $furnace = false; + if($t instanceof Furnace){ + $furnace = $t; + }else{ + $nbt = new Compound(false, array( + new Enum("Items", array()), + new String("id", Tile::FURNACE), + new Int("x", $this->x), + new Int("y", $this->y), + new Int("z", $this->z) + )); + $nbt->Items->setTagType(NBT::TAG_Compound); + $furnace = new Furnace($this->level, $nbt); + } + + if(($player->getGamemode() & 0x01) === 0x01){ + return true; + } + + $furnace->openInventory($player); + } + + return true; + } + + public function getBreakTime(Item $item){ + switch($item->isPickaxe()){ + case 5: + return 0.7; + case 4: + return 0.9; + case 3: + return 1.35; + case 2: + return 0.45; + case 1: + return 2.65; + default: + return 17.5; + } + } + + public function getDrops(Item $item){ + $drops = array(); + if($item->isPickaxe() >= 1){ + $drops[] = array(Item::FURNACE, 0, 1); + } + $t = $this->level->getTile($this); + if($t instanceof Furnace){ + for($s = 0; $s < Furnace::SLOTS; ++$s){ + $slot = $t->getSlot($s); + if($slot->getID() > Item::AIR and $slot->getCount() > 0){ + $drops[] = array($slot->getID(), $slot->getMetadata(), $slot->getCount()); + } + } + } + + return $drops; + } +} \ No newline at end of file diff --git a/src/pocketmine/block/Cactus.php b/src/pocketmine/block/Cactus.php new file mode 100644 index 000000000..053715e3c --- /dev/null +++ b/src/pocketmine/block/Cactus.php @@ -0,0 +1,92 @@ +isFullBlock = false; + $this->hardness = 2; + } + + public function onUpdate($type){ + if($type === Level::BLOCK_UPDATE_NORMAL){ + $down = $this->getSide(0); + if($down->getID() !== self::SAND and $down->getID() !== self::CACTUS){ //Replace with common break method + $this->level->setBlock($this, new Air(), false); + Server::getInstance()->api->entity->drop($this, Item::get($this->id)); + + return Level::BLOCK_UPDATE_NORMAL; + } + }elseif($type === Level::BLOCK_UPDATE_RANDOM){ + if($this->getSide(0)->getID() !== self::CACTUS){ + if($this->meta == 0x0F){ + for($y = 1; $y < 3; ++$y){ + $b = $this->level->getBlock(new Vector3($this->x, $this->y + $y, $this->z)); + if($b->getID() === self::AIR){ + $this->level->setBlock($b, new Cactus(), true, false, true); + break; + } + } + $this->meta = 0; + $this->level->setBlock($this, $this, false); + }else{ + ++$this->meta; + $this->level->setBlock($this, $this, false); + } + + return Level::BLOCK_UPDATE_RANDOM; + } + } + + return false; + } + + public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ + $down = $this->getSide(0); + if($down->getID() === self::SAND or $down->getID() === self::CACTUS){ + $block0 = $this->getSide(2); + $block1 = $this->getSide(3); + $block2 = $this->getSide(4); + $block3 = $this->getSide(5); + if($block0->isTransparent === true and $block1->isTransparent === true and $block2->isTransparent === true and $block3->isTransparent === true){ + $this->level->setBlock($this, $this, true, false, true); + + return true; + } + } + + return false; + } + + public function getDrops(Item $item){ + return array( + array($this->id, 0, 1), + ); + } +} \ No newline at end of file diff --git a/src/pocketmine/block/Cake.php b/src/pocketmine/block/Cake.php new file mode 100644 index 000000000..e151d1050 --- /dev/null +++ b/src/pocketmine/block/Cake.php @@ -0,0 +1,80 @@ +isFullBlock = false; + $this->isActivable = true; + $this->meta = $meta & 0x07; + $this->hardness = 2.5; + } + + public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ + $down = $this->getSide(0); + if($down->getID() !== self::AIR){ + $this->level->setBlock($block, $this, true, false, true); + + return true; + } + + return false; + } + + public function onUpdate($type){ + if($type === Level::BLOCK_UPDATE_NORMAL){ + if($this->getSide(0)->getID() === self::AIR){ //Replace with common break method + $this->level->setBlock($this, new Air(), true, false, true); + + return Level::BLOCK_UPDATE_NORMAL; + } + } + + return false; + } + + public function getDrops(Item $item){ + return array(); + } + + public function onActivate(Item $item, Player $player = null){ + if($player instanceof Player and $player->getHealth() < 20){ + ++$this->meta; + $player->heal(3, "cake"); + if($this->meta >= 0x06){ + $this->level->setBlock($this, new Air(), true, false, true); + }else{ + $this->level->setBlock($this, $this, true, false, true); + } + + return true; + } + + return false; + } + +} \ No newline at end of file diff --git a/src/pocketmine/block/Carpet.php b/src/pocketmine/block/Carpet.php new file mode 100644 index 000000000..b52c26772 --- /dev/null +++ b/src/pocketmine/block/Carpet.php @@ -0,0 +1,80 @@ + "White Carpet", + 1 => "Orange Carpet", + 2 => "Magenta Carpet", + 3 => "Light Blue Carpet", + 4 => "Yellow Carpet", + 5 => "Lime Carpet", + 6 => "Pink Carpet", + 7 => "Gray Carpet", + 8 => "Light Gray Carpet", + 9 => "Cyan Carpet", + 10 => "Purple Carpet", + 11 => "Blue Carpet", + 12 => "Brown Carpet", + 13 => "Green Carpet", + 14 => "Red Carpet", + 15 => "Black Carpet", + ); + $this->name = $names[$this->meta]; + $this->hardness = 0; + $this->isFullBlock = false; + $this->isSolid = true; + } + + public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ + $down = $this->getSide(0); + if($down->getID() !== self::AIR){ + $this->level->setBlock($block, $this, true, false, true); + + return true; + } + + return false; + } + + public function onUpdate($type){ + if($type === Level::BLOCK_UPDATE_NORMAL){ + if($this->getSide(0)->getID() === self::AIR){ //Replace with common break method + //TODO + //Server::getInstance()->api->entity->drop($this, Item::get($this->id, $this->meta, 1)); + $this->level->setBlock($this, new Air(), true, false, true); + + return Level::BLOCK_UPDATE_NORMAL; + } + } + + return false; + } + +} \ No newline at end of file diff --git a/src/pocketmine/block/Carrot.php b/src/pocketmine/block/Carrot.php new file mode 100644 index 000000000..a3f92a4c9 --- /dev/null +++ b/src/pocketmine/block/Carrot.php @@ -0,0 +1,93 @@ +isActivable = true; + $this->hardness = 0; + } + + public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ + $down = $this->getSide(0); + if($down->getID() === self::FARMLAND){ + $this->level->setBlock($block, $this, true, false, true); + + return true; + } + + return false; + } + + public function onActivate(Item $item, Player $player = null){ + if($item->getID() === Item::DYE and $item->getMetadata() === 0x0F){ //Bonemeal + $this->meta = 0x07; + $this->level->setBlock($this, $this, true, false, true); + $item->count--; + + return true; + } + + return false; + } + + public function onUpdate($type){ + if($type === Level::BLOCK_UPDATE_NORMAL){ + if($this->getSide(0)->isTransparent === true){ //Replace with common break method + //TODO + //Server::getInstance()->api->entity->drop($this, Item::get(CARROT, 0, 1)); + $this->level->setBlock($this, new Air(), false, false, true); + + return Level::BLOCK_UPDATE_NORMAL; + } + }elseif($type === Level::BLOCK_UPDATE_RANDOM){ + if(mt_rand(0, 2) == 1){ + if($this->meta < 0x07){ + ++$this->meta; + $this->level->setBlock($this, $this, true, false, true); + + return Level::BLOCK_UPDATE_RANDOM; + } + }else{ + return Level::BLOCK_UPDATE_RANDOM; + } + } + + return false; + } + + public function getDrops(Item $item){ + $drops = array(); + if($this->meta >= 0x07){ + $drops[] = array(Item::CARROT, 0, mt_rand(1, 4)); + }else{ + $drops[] = array(Item::CARROT, 0, 1); + } + + return $drops; + } +} \ No newline at end of file diff --git a/src/pocketmine/block/Chest.php b/src/pocketmine/block/Chest.php new file mode 100644 index 000000000..a42018f25 --- /dev/null +++ b/src/pocketmine/block/Chest.php @@ -0,0 +1,149 @@ +isActivable = true; + $this->hardness = 15; + } + + public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ + $faces = array( + 0 => 4, + 1 => 2, + 2 => 5, + 3 => 3, + ); + + $chest = false; + $this->meta = $faces[$player instanceof Player ? $player->getDirection() : 0]; + + for($side = 2; $side <= 5; ++$side){ + if(($this->meta === 4 or $this->meta === 5) and ($side === 4 or $side === 5)){ + continue; + }elseif(($this->meta === 3 or $this->meta === 2) and ($side === 2 or $side === 3)){ + continue; + } + $c = $this->getSide($side); + if(($c instanceof TileChest) and $c->getMetadata() === $this->meta){ + if((($tile = $this->level->getTile($c)) instanceof TileChest) and !$tile->isPaired()){ + $chest = $tile; + break; + } + } + } + + $this->level->setBlock($block, $this, true, false, true); + $nbt = new Compound(false, array( + new Enum("Items", array()), + new String("id", Tile::CHEST), + new Int("x", $this->x), + new Int("y", $this->y), + new Int("z", $this->z) + )); + $nbt->Items->setTagType(NBT::TAG_Compound); + $tile = new TileChest($this->level, $nbt); + + if($chest instanceof TileChest){ + $chest->pairWith($tile); + $tile->pairWith($chest); + } + + return true; + } + + public function onBreak(Item $item){ + $t = $this->level->getTile($this); + if($t instanceof TileChest){ + $t->unpair(); + } + $this->level->setBlock($this, new Air(), true, true, true); + + return true; + } + + public function onActivate(Item $item, Player $player = null){ + if($player instanceof Player){ + $top = $this->getSide(1); + if($top->isTransparent !== true){ + return true; + } + + $t = $this->level->getTile($this); + $chest = false; + if($t instanceof TileChest){ + $chest = $t; + }else{ + $nbt = new Compound(false, array( + new Enum("Items", array()), + new String("id", Tile::CHEST), + new Int("x", $this->x), + new Int("y", $this->y), + new Int("z", $this->z) + )); + $nbt->Items->setTagType(NBT::TAG_Compound); + $chest = new TileChest($this->level, $nbt); + } + + + if(($player->gamemode & 0x01) === 0x01){ + return true; + } + + $chest->openInventory($player); + } + + return true; + } + + public function getDrops(Item $item){ + $drops = array( + array($this->id, 0, 1), + ); + $t = $this->level->getTile($this); + if($t instanceof Chest){ + for($s = 0; $s < Chest::SLOTS; ++$s){ + $slot = $t->getSlot($s); + if($slot->getID() > Item::AIR and $slot->getCount() > 0){ + $drops[] = array($slot->getID(), $slot->getMetadata(), $slot->getCount()); + } + } + } + + return $drops; + } +} \ No newline at end of file diff --git a/src/pocketmine/block/Clay.php b/src/pocketmine/block/Clay.php new file mode 100644 index 000000000..055e3fc02 --- /dev/null +++ b/src/pocketmine/block/Clay.php @@ -0,0 +1,37 @@ +hardness = 3; + } + + public function getDrops(Item $item){ + return array( + array(Item::CLAY, 0, 4), + ); + } +} \ No newline at end of file diff --git a/src/pocketmine/block/Coal.php b/src/pocketmine/block/Coal.php new file mode 100644 index 000000000..88e2b9dac --- /dev/null +++ b/src/pocketmine/block/Coal.php @@ -0,0 +1,58 @@ +hardness = 30; + } + + public function getBreakTime(Item $item){ + switch($item->isPickaxe()){ + case 5: + return 0.95; + case 4: + return 1.25; + case 3: + return 1.9; + case 2: + return 0.65; + case 1: + return 3.75; + default: + return 25; + } + } + + public function getDrops(Item $item){ + if($item->isPickaxe() >= 1){ + return array( + array(Item::COAL_BLOCK, 0, 1), + ); + }else{ + return array(); + } + } +} \ No newline at end of file diff --git a/src/pocketmine/block/CoalOre.php b/src/pocketmine/block/CoalOre.php new file mode 100644 index 000000000..4db7b5d35 --- /dev/null +++ b/src/pocketmine/block/CoalOre.php @@ -0,0 +1,59 @@ +hardness = 15; + } + + public function getBreakTime(Item $item){ + switch($item->isPickaxe()){ + case 5: + return 0.6; + case 4: + return 0.75; + case 3: + return 1.15; + case 2: + return 0.4; + case 1: + return 2.25; + default: + return 15; + } + } + + public function getDrops(Item $item){ + if($item->isPickaxe() >= 1){ + return array( + array(Item::COAL, 0, 1), + ); + }else{ + return array(); + } + } + +} \ No newline at end of file diff --git a/src/pocketmine/block/Cobblestone.php b/src/pocketmine/block/Cobblestone.php new file mode 100644 index 000000000..14f2cd253 --- /dev/null +++ b/src/pocketmine/block/Cobblestone.php @@ -0,0 +1,58 @@ +hardness = 30; + } + + public function getBreakTime(Item $item){ + switch($item->isPickaxe()){ + case 5: + return 0.4; + case 4: + return 0.5; + case 3: + return 0.75; + case 2: + return 0.25; + case 1: + return 1.5; + default: + return 10; + } + } + + public function getDrops(Item $item){ + if($item->isPickaxe() >= 1){ + return array( + array(Item::COBBLESTONE, 0, 1), + ); + }else{ + return array(); + } + } +} \ No newline at end of file diff --git a/src/pocketmine/block/CobblestoneStairs.php b/src/pocketmine/block/CobblestoneStairs.php new file mode 100644 index 000000000..f63780ac3 --- /dev/null +++ b/src/pocketmine/block/CobblestoneStairs.php @@ -0,0 +1,30 @@ +isSolid = true; + $this->isFullBlock = false; + $this->hardness = 25; + } + + public function getDrops(Item $item){ + return array(); + } +} \ No newline at end of file diff --git a/src/pocketmine/block/CyanFlower.php b/src/pocketmine/block/CyanFlower.php new file mode 100644 index 000000000..88672df66 --- /dev/null +++ b/src/pocketmine/block/CyanFlower.php @@ -0,0 +1,58 @@ +hardness = 0; + } + + public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ + $down = $this->getSide(0); + if($down->getID() === 2 or $down->getID() === 3 or $down->getID() === 60){ + $this->level->setBlock($block, $this, true, false, true); + + return true; + } + + return false; + } + + public function onUpdate($type){ + if($type === Level::BLOCK_UPDATE_NORMAL){ + if($this->getSide(0)->isTransparent === true){ //Replace with common break method + //TODO + //Server::getInstance()->api->entity->drop($this, Item::get($this->id)); + $this->level->setBlock($this, new Air(), false, false, true); + + return Level::BLOCK_UPDATE_NORMAL; + } + } + + return false; + } +} \ No newline at end of file diff --git a/src/pocketmine/block/Dandelion.php b/src/pocketmine/block/Dandelion.php new file mode 100644 index 000000000..bf49eaaf2 --- /dev/null +++ b/src/pocketmine/block/Dandelion.php @@ -0,0 +1,58 @@ +hardness = 0; + } + + public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ + $down = $this->getSide(0); + if($down->getID() === 2 or $down->getID() === 3 or $down->getID() === 60){ + $this->level->setBlock($block, $this, true, false, true); + + return true; + } + + return false; + } + + public function onUpdate($type){ + if($type === Level::BLOCK_UPDATE_NORMAL){ + if($this->getSide(0)->isTransparent === true){ //Replace with common break method + //TODO + //Server::getInstance()->api->entity->drop($this, Item::get($this->id)); + $this->level->setBlock($this, new Air(), false, false, true); + + return Level::BLOCK_UPDATE_NORMAL; + } + } + + return false; + } +} \ No newline at end of file diff --git a/src/pocketmine/block/DeadBush.php b/src/pocketmine/block/DeadBush.php new file mode 100644 index 000000000..9888ad663 --- /dev/null +++ b/src/pocketmine/block/DeadBush.php @@ -0,0 +1,45 @@ +isReplaceable = true; + $this->hardness = 0; + } + + public function onUpdate($type){ + if($type === Level::BLOCK_UPDATE_NORMAL){ + if($this->getSide(0)->isTransparent === true){ //Replace with common break method + $this->level->setBlock($this, new Air(), false, false, true); + + return Level::BLOCK_UPDATE_NORMAL; + } + } + + return false; + } + +} \ No newline at end of file diff --git a/src/pocketmine/block/Diamond.php b/src/pocketmine/block/Diamond.php new file mode 100644 index 000000000..53210dbf9 --- /dev/null +++ b/src/pocketmine/block/Diamond.php @@ -0,0 +1,52 @@ +hardness = 30; + } + + public function getBreakTime(Item $item){ + switch($item->isPickaxe()){ + case 5: + return 0.95; + case 4: + return 1.25; + default: + return 25; + } + } + + public function getDrops(Item $item){ + if($item->isPickaxe() >= 4){ + return array( + array(Item::DIAMOND_BLOCK, 0, 1), + ); + }else{ + return array(); + } + } +} \ No newline at end of file diff --git a/src/pocketmine/block/DiamondOre.php b/src/pocketmine/block/DiamondOre.php new file mode 100644 index 000000000..f9c09fd23 --- /dev/null +++ b/src/pocketmine/block/DiamondOre.php @@ -0,0 +1,52 @@ +hardness = 15; + } + + public function getBreakTime(Item $item){ + switch($item->isPickaxe()){ + case 5: + return 0.6; + case 4: + return 0.75; + default: + return 15; + } + } + + public function getDrops(Item $item){ + if($item->isPickaxe() >= 4){ + return array( + array(Item::DIAMOND, 0, 1), + ); + }else{ + return array(); + } + } +} \ No newline at end of file diff --git a/src/pocketmine/block/Dirt.php b/src/pocketmine/block/Dirt.php new file mode 100644 index 000000000..67e6ba1c9 --- /dev/null +++ b/src/pocketmine/block/Dirt.php @@ -0,0 +1,44 @@ +isActivable = true; + $this->hardness = 2.5; + } + + public function onActivate(Item $item, Player $player = null){ + if($item->isHoe()){ + $item->useOn($this); + $this->level->setBlock($this, Block::get(Item::FARMLAND, 0), true, false, true); + + return true; + } + + return false; + } +} \ No newline at end of file diff --git a/src/pocketmine/block/Door.php b/src/pocketmine/block/Door.php new file mode 100644 index 000000000..8ce237605 --- /dev/null +++ b/src/pocketmine/block/Door.php @@ -0,0 +1,138 @@ +isSolid = false; + } + + public function onUpdate($type){ + if($type === Level::BLOCK_UPDATE_NORMAL){ + if($this->getSide(0)->getID() === self::AIR){ //Replace with common break method + $this->level->setBlock($this, new Air(), false); + if($this->getSide(1) instanceof Door){ + $this->level->setBlock($this->getSide(1), new Air(), false); + } + + return Level::BLOCK_UPDATE_NORMAL; + } + } + + return false; + } + + public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ + if($face === 1){ + $blockUp = $this->getSide(1); + $blockDown = $this->getSide(0); + if($blockUp->isReplaceable === false or $blockDown->isTransparent === true){ + return false; + } + $direction = $player instanceof Player ? $player->getDirection() : 0; + $face = array( + 0 => 3, + 1 => 4, + 2 => 2, + 3 => 5, + ); + $next = $this->getSide($face[(($direction + 2) % 4)]); + $next2 = $this->getSide($face[$direction]); + $metaUp = 0x08; + if($next->getID() === $this->id or ($next2->isTransparent === false and $next->isTransparent === true)){ //Door hinge + $metaUp |= 0x01; + } + $this->level->setBlock($blockUp, Block::get($this->id, $metaUp), true, false, true); //Top + + $this->meta = $player->getDirection() & 0x03; + $this->level->setBlock($block, $this, true, false, true); //Bottom + return true; + } + + return false; + } + + public function onBreak(Item $item){ + if(($this->meta & 0x08) === 0x08){ + $down = $this->getSide(0); + if($down->getID() === $this->id){ + $this->level->setBlock($down, new Air(), true, false, true); + } + }else{ + $up = $this->getSide(1); + if($up->getID() === $this->id){ + $this->level->setBlock($up, new Air(), true, false, true); + } + } + $this->level->setBlock($this, new Air(), true, false, true); + + return true; + } + + public function onActivate(Item $item, Player $player = null){ + if(($this->meta & 0x08) === 0x08){ //Top + $down = $this->getSide(0); + if($down->getID() === $this->id){ + $meta = $down->getMetadata() ^ 0x04; + $this->level->setBlock($down, Block::get($this->id, $meta), true, false, true); + $players = $this->level->getUsingChunk($this->x >> 4, $this->z >> 4); + if($player instanceof Player){ + unset($players[$player->CID]); + } + $pk = new LevelEventPacket; + $pk->x = $this->x; + $pk->y = $this->y; + $pk->z = $this->z; + $pk->evid = 1003; + $pk->data = 0; + Player::broadcastPacket($players, $pk); + + return true; + } + + return false; + }else{ + $this->meta ^= 0x04; + $this->level->setBlock($this, $this, true, false, true); + $players = $this->level->getUsingChunk($this->x >> 4, $this->z >> 4); + if($player instanceof Player){ + unset($players[$player->CID]); + } + $pk = new LevelEventPacket; + $pk->x = $this->x; + $pk->y = $this->y; + $pk->z = $this->z; + $pk->evid = 1003; + $pk->data = 0; + Player::broadcastPacket($players, $pk); + } + + return true; + } +} \ No newline at end of file diff --git a/src/pocketmine/block/DoubleSlab.php b/src/pocketmine/block/DoubleSlab.php new file mode 100644 index 000000000..19acd361d --- /dev/null +++ b/src/pocketmine/block/DoubleSlab.php @@ -0,0 +1,69 @@ + "Stone", + 1 => "Sandstone", + 2 => "Wooden", + 3 => "Cobblestone", + 4 => "Brick", + 5 => "Stone Brick", + 6 => "Quartz", + ); + $this->name = "Double " . $names[$this->meta & 0x07] . " Slab"; + $this->hardness = 30; + } + + public function getBreakTime(Item $item){ + switch($item->isPickaxe()){ + case 5: + return 0.4; + case 4: + return 0.5; + case 3: + return 0.75; + case 2: + return 0.25; + case 1: + return 1.5; + default: + return 10; + } + } + + public function getDrops(Item $item){ + if($item->isPickaxe() >= 1){ + return array( + array(Item::SLAB, $this->meta & 0x07, 2), + ); + }else{ + return array(); + } + } + +} \ No newline at end of file diff --git a/src/pocketmine/block/DoubleWoodSlab.php b/src/pocketmine/block/DoubleWoodSlab.php new file mode 100644 index 000000000..ecd009f97 --- /dev/null +++ b/src/pocketmine/block/DoubleWoodSlab.php @@ -0,0 +1,62 @@ + "Oak", + 1 => "Spruce", + 2 => "Birch", + 3 => "Jungle", + ); + $this->name = "Double " . $names[$this->meta & 0x07] . " Wooden Slab"; + $this->hardness = 15; + } + + public function getBreakTime(Item $item){ + switch($item->isAxe()){ + case 5: + return 0.4; + case 4: + return 0.5; + case 3: + return 0.75; + case 2: + return 0.25; + case 1: + return 1.5; + default: + return 3; + } + } + + public function getDrops(Item $item){ + return array( + array(Item::WOOD_SLAB, $this->meta & 0x07, 2), + ); + } + +} \ No newline at end of file diff --git a/src/pocketmine/block/Fallable.php b/src/pocketmine/block/Fallable.php new file mode 100644 index 000000000..cfa2cf2c0 --- /dev/null +++ b/src/pocketmine/block/Fallable.php @@ -0,0 +1,42 @@ +hasPhysics = true; + } + + public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ + $ret = $this->level->setBlock($this, $this, true, false, true); + Server::getInstance()->api->block->blockUpdate(clone $this, Level::BLOCK_UPDATE_NORMAL); + + return $ret; + } +} \ No newline at end of file diff --git a/src/pocketmine/block/Farmland.php b/src/pocketmine/block/Farmland.php new file mode 100644 index 000000000..9e1e341f3 --- /dev/null +++ b/src/pocketmine/block/Farmland.php @@ -0,0 +1,37 @@ +hardness = 3; + } + + public function getDrops(Item $item){ + return array( + array(Item::DIRT, 0, 1), + ); + } +} \ No newline at end of file diff --git a/src/pocketmine/block/Fence.php b/src/pocketmine/block/Fence.php new file mode 100644 index 000000000..c82be5f47 --- /dev/null +++ b/src/pocketmine/block/Fence.php @@ -0,0 +1,32 @@ +isFullBlock = false; + $this->hardness = 15; + } + +} \ No newline at end of file diff --git a/src/pocketmine/block/FenceGate.php b/src/pocketmine/block/FenceGate.php new file mode 100644 index 000000000..6a30d95aa --- /dev/null +++ b/src/pocketmine/block/FenceGate.php @@ -0,0 +1,75 @@ +isActivable = true; + if(($this->meta & 0x04) === 0x04){ + $this->isFullBlock = true; + }else{ + $this->isFullBlock = false; + } + $this->hardness = 15; + } + + public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ + $faces = array( + 0 => 3, + 1 => 0, + 2 => 1, + 3 => 2, + ); + $this->meta = $faces[$player instanceof Player ? $player->getDirection() : 0] & 0x03; + $this->level->setBlock($block, $this, true, false, true); + + return true; + } + + public function getDrops(Item $item){ + return array( + array($this->id, 0, 1), + ); + } + + public function onActivate(Item $item, Player $player = null){ + $faces = array( + 0 => 3, + 1 => 0, + 2 => 1, + 3 => 2, + ); + $this->meta = ($faces[$player instanceof Player ? $player->getDirection() : 0] & 0x03) | ((~$this->meta) & 0x04); + if(($this->meta & 0x04) === 0x04){ + $this->isFullBlock = true; + }else{ + $this->isFullBlock = false; + } + $this->level->setBlock($this, $this, true, false, true); + + return true; + } +} \ No newline at end of file diff --git a/src/pocketmine/block/Fire.php b/src/pocketmine/block/Fire.php new file mode 100644 index 000000000..a070e1905 --- /dev/null +++ b/src/pocketmine/block/Fire.php @@ -0,0 +1,62 @@ +isReplaceable = true; + $this->breakable = false; + $this->isFullBlock = true; + $this->hardness = 0; + } + + public function getDrops(Item $item){ + return array(); + } + + public function onUpdate($type){ + if($type === Level::BLOCK_UPDATE_NORMAL){ + for($s = 0; $s <= 5; ++$s){ + $side = $this->getSide($s); + if($side->getID() !== self::AIR and !($side instanceof Liquid)){ + return false; + } + } + $this->level->setBlock($this, new Air(), true, false, true); + + return Level::BLOCK_UPDATE_NORMAL; + }elseif($type === Level::BLOCK_UPDATE_RANDOM){ + if($this->getSide(0)->getID() !== self::NETHERRACK){ + $this->level->setBlock($this, new Air(), true, false, true); + + return Level::BLOCK_UPDATE_NORMAL; + } + } + + return false; + } + +} \ No newline at end of file diff --git a/src/pocketmine/block/Flowable.php b/src/pocketmine/block/Flowable.php new file mode 100644 index 000000000..0a4a7aa29 --- /dev/null +++ b/src/pocketmine/block/Flowable.php @@ -0,0 +1,32 @@ +isFlowable = true; + $this->isFullBlock = false; + $this->isSolid = false; + } +} \ No newline at end of file diff --git a/src/pocketmine/block/Furnace.php b/src/pocketmine/block/Furnace.php new file mode 100644 index 000000000..29bd2b69d --- /dev/null +++ b/src/pocketmine/block/Furnace.php @@ -0,0 +1,32 @@ +id = self::FURNACE; + $this->name = "Furnace"; + $this->isActivable = true; + } +} \ No newline at end of file diff --git a/src/pocketmine/block/Generic.php b/src/pocketmine/block/Generic.php new file mode 100644 index 000000000..d18b472ea --- /dev/null +++ b/src/pocketmine/block/Generic.php @@ -0,0 +1,79 @@ +level->setBlock($this, $this, true, false, true); + } + + public function isBreakable(Item $item){ + return $this->breakable; + } + + public function onBreak(Item $item){ + return $this->level->setBlock($this, new Air(), true, false, true); + } + + public function onUpdate($type){ + if($this->hasPhysics === true and $type === Level::BLOCK_UPDATE_NORMAL){ + $down = $this->getSide(0); + if($down->getID() === self::AIR or ($down instanceof Liquid)){ + $data = array( + "x" => $this->x + 0.5, + "y" => $this->y + 0.5, + "z" => $this->z + 0.5, + "Tile" => $this->id, + ); + $server = Server::getInstance(); + $this->level->setBlock($this, new Air(), false, false, true); + //TODO + //$e = $server->api->entity->add($this->level, ENTITY_FALLING, FALLING_SAND, $data); + //$e->spawnToAll(); + $server->api->block->blockUpdateAround(clone $this, Level::BLOCK_UPDATE_NORMAL, 1); + } + + return false; + } + + return false; + } + + public function onActivate(Item $item, Player $player = null){ + return $this->isActivable; + } +} \ No newline at end of file diff --git a/src/pocketmine/block/Glass.php b/src/pocketmine/block/Glass.php new file mode 100644 index 000000000..88f2de244 --- /dev/null +++ b/src/pocketmine/block/Glass.php @@ -0,0 +1,35 @@ +hardness = 1.5; + } + + public function getDrops(Item $item){ + return array(); + } +} \ No newline at end of file diff --git a/src/pocketmine/block/GlassPane.php b/src/pocketmine/block/GlassPane.php new file mode 100644 index 000000000..5620c4dd9 --- /dev/null +++ b/src/pocketmine/block/GlassPane.php @@ -0,0 +1,32 @@ +isFullBlock = false; + $this->isSolid = false; + } + +} \ No newline at end of file diff --git a/src/pocketmine/block/GlowingObsidian.php b/src/pocketmine/block/GlowingObsidian.php new file mode 100644 index 000000000..90fea22db --- /dev/null +++ b/src/pocketmine/block/GlowingObsidian.php @@ -0,0 +1,30 @@ +hardness = 15; + } + + public function onUpdate($type){ + if($type === Level::BLOCK_UPDATE_SCHEDULED or $type === Level::BLOCK_UPDATE_RANDOM){ + $this->level->setBlock($this, Block::get(Item::REDSTONE_ORE, $this->meta), false, false, true); + + return Level::BLOCK_UPDATE_WEAK; + } + + return false; + } + + + public function getBreakTime(Item $item){ + switch($item->isPickaxe()){ + case 5: + return 0.6; + case 4: + return 0.75; + default: + return 15; + } + } + + public function getDrops(Item $item){ + if($item->isPickaxe() >= 4){ + return array( + array(Item::REDSTONE_DUST, 0, mt_rand(4, 5)), + ); + }else{ + return array(); + } + } + +} \ No newline at end of file diff --git a/src/pocketmine/block/Glowstone.php b/src/pocketmine/block/Glowstone.php new file mode 100644 index 000000000..f7d3a1863 --- /dev/null +++ b/src/pocketmine/block/Glowstone.php @@ -0,0 +1,37 @@ +hardness = 1.5; + } + + public function getDrops(Item $item){ + return array( + array(Item::GLOWSTONE_DUST, 0, mt_rand(2, 4)), + ); + } +} \ No newline at end of file diff --git a/src/pocketmine/block/Gold.php b/src/pocketmine/block/Gold.php new file mode 100644 index 000000000..4c9dafe52 --- /dev/null +++ b/src/pocketmine/block/Gold.php @@ -0,0 +1,52 @@ +hardness = 30; + } + + public function getBreakTime(Item $item){ + switch($item->isPickaxe()){ + case 5: + return 0.6; + case 4: + return 0.75; + default: + return 15; + } + } + + public function getDrops(Item $item){ + if($item->isPickaxe() >= 4){ + return array( + array(Item::GOLD_BLOCK, 0, 1), + ); + }else{ + return array(); + } + } +} \ No newline at end of file diff --git a/src/pocketmine/block/GoldOre.php b/src/pocketmine/block/GoldOre.php new file mode 100644 index 000000000..7353dc71b --- /dev/null +++ b/src/pocketmine/block/GoldOre.php @@ -0,0 +1,52 @@ +hardness = 15; + } + + public function getBreakTime(Item $item){ + switch($item->isPickaxe()){ + case 5: + return 0.6; + case 4: + return 0.75; + default: + return 15; + } + } + + public function getDrops(Item $item){ + if($item->isPickaxe() >= 4){ + return array( + array(Item::GOLD_ORE, 0, 1), + ); + }else{ + return array(); + } + } +} \ No newline at end of file diff --git a/src/pocketmine/block/Grass.php b/src/pocketmine/block/Grass.php new file mode 100644 index 000000000..9f1d7296b --- /dev/null +++ b/src/pocketmine/block/Grass.php @@ -0,0 +1,57 @@ +isActivable = true; + $this->hardness = 3; + } + + public function getDrops(Item $item){ + return array( + array(Item::DIRT, 0, 1), + ); + } + + public function onActivate(Item $item, Player $player = null){ + if($item->getID() === Item::DYE and $item->getMetadata() === 0x0F){ + $item->count--; + TallGrass::growGrass($this->level, $this, new Random(), 8, 2); + + return true; + }elseif($item->isHoe()){ + $item->useOn($this); + $this->level->setBlock($this, new Farmland()); + + return true; + } + + return false; + } +} \ No newline at end of file diff --git a/src/pocketmine/block/Gravel.php b/src/pocketmine/block/Gravel.php new file mode 100644 index 000000000..2ffc98bde --- /dev/null +++ b/src/pocketmine/block/Gravel.php @@ -0,0 +1,44 @@ +hardness = 3; + } + + public function getDrops(Item $item){ + if(mt_rand(1, 10) === 1){ + return array( + array(Item::FLINT, 0, 1), + ); + } + + return array( + array(Item::GRAVEL, 0, 1), + ); + } + +} \ No newline at end of file diff --git a/src/pocketmine/block/HayBale.php b/src/pocketmine/block/HayBale.php new file mode 100644 index 000000000..44f21e5d2 --- /dev/null +++ b/src/pocketmine/block/HayBale.php @@ -0,0 +1,55 @@ +hardness = 10; + } + + public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ + $faces = array( + 0 => 0, + 1 => 0, + 2 => 0b1000, + 3 => 0b1000, + 4 => 0b0100, + 5 => 0b0100, + ); + + $this->meta = ($this->meta & 0x03) | $faces[$face]; + $this->level->setBlock($block, $this, true, false, true); + + return true; + } + + public function getDrops(Item $item){ + return array( + array($this->id, 0, 1), + ); + } + +} \ No newline at end of file diff --git a/src/pocketmine/block/Ice.php b/src/pocketmine/block/Ice.php new file mode 100644 index 000000000..b9b0d3a02 --- /dev/null +++ b/src/pocketmine/block/Ice.php @@ -0,0 +1,58 @@ +hardness = 2.5; + } + + public function onBreak(Item $item){ + $this->level->setBlock($this, new Water(), true, false, true); + + return true; + } + + public function getBreakTime(Item $item){ + switch($item->isPickaxe()){ + case 5: + return 0.1; + case 4: + return 0.15; + case 3: + return 0.2; + case 2: + return 0.1; + case 1: + return 0.4; + default: + return 0.75; + } + } + + public function getDrops(Item $item){ + return array(); + } +} \ No newline at end of file diff --git a/src/pocketmine/block/Iron.php b/src/pocketmine/block/Iron.php new file mode 100644 index 000000000..b31273244 --- /dev/null +++ b/src/pocketmine/block/Iron.php @@ -0,0 +1,54 @@ +hardness = 30; + } + + public function getBreakTime(Item $item){ + switch($item->isPickaxe()){ + case 5: + return 0.95; + case 4: + return 1.25; + case 3: + return 1.9; + default: + return 25; + } + } + + public function getDrops(Item $item){ + if($item->isPickaxe() >= 3){ + return array( + array(Item::IRON_BLOCK, 0, 1), + ); + }else{ + return array(); + } + } +} \ No newline at end of file diff --git a/src/pocketmine/block/IronBars.php b/src/pocketmine/block/IronBars.php new file mode 100644 index 000000000..2b2b53152 --- /dev/null +++ b/src/pocketmine/block/IronBars.php @@ -0,0 +1,32 @@ +isFullBlock = false; + $this->isSolid = false; + } + +} \ No newline at end of file diff --git a/src/pocketmine/block/IronDoor.php b/src/pocketmine/block/IronDoor.php new file mode 100644 index 000000000..f8ff9c12a --- /dev/null +++ b/src/pocketmine/block/IronDoor.php @@ -0,0 +1,59 @@ +isActivable = true; + $this->hardness = 25; + } + + public function getBreakTime(Item $item){ + switch($item->isPickaxe()){ + case 5: + return 0.95; + case 4: + return 1.25; + case 3: + return 1.9; + case 2: + return 0.65; + case 1: + return 3.75; + default: + return 25; + } + } + + public function getDrops(Item $item){ + if($item->isPickaxe() >= 1){ + return array( + array(Item::IRON_DOOR, 0, 1), + ); + }else{ + return array(); + } + } +} \ No newline at end of file diff --git a/src/pocketmine/block/IronOre.php b/src/pocketmine/block/IronOre.php new file mode 100644 index 000000000..c3c378616 --- /dev/null +++ b/src/pocketmine/block/IronOre.php @@ -0,0 +1,54 @@ +hardness = 15; + } + + public function getBreakTime(Item $item){ + switch($item->isPickaxe()){ + case 5: + return 0.6; + case 4: + return 0.75; + case 3: + return 1.15; + default: + return 15; + } + } + + public function getDrops(Item $item){ + if($item->isPickaxe() >= 3){ + return array( + array(Item::IRON_ORE, 0, 1), + ); + }else{ + return array(); + } + } +} \ No newline at end of file diff --git a/src/pocketmine/block/JungleWoodStairs.php b/src/pocketmine/block/JungleWoodStairs.php new file mode 100644 index 000000000..3706548e8 --- /dev/null +++ b/src/pocketmine/block/JungleWoodStairs.php @@ -0,0 +1,36 @@ +id, 0, 1), + ); + } +} \ No newline at end of file diff --git a/src/pocketmine/block/Ladder.php b/src/pocketmine/block/Ladder.php new file mode 100644 index 000000000..f1d7b24d0 --- /dev/null +++ b/src/pocketmine/block/Ladder.php @@ -0,0 +1,72 @@ +isSolid = false; + $this->isFullBlock = false; + $this->hardness = 2; + } + + public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ + if($target->isTransparent === false){ + $faces = array( + 2 => 2, + 3 => 3, + 4 => 4, + 5 => 5, + ); + if(isset($faces[$face])){ + $this->meta = $faces[$face]; + $this->level->setBlock($block, $this, true, false, true); + + return true; + } + } + + return false; + } + + public function onUpdate($type){ + if($type === Level::BLOCK_UPDATE_NORMAL){ + /*if($this->getSide(0)->getID() === self::AIR){ //Replace with common break method + Server::getInstance()->api->entity->drop($this, Item::get(LADDER, 0, 1)); + $this->level->setBlock($this, new Air(), true, true, true); + return Level::BLOCK_UPDATE_NORMAL; + }*/ + } + + return false; + } + + public function getDrops(Item $item){ + return array( + array($this->id, 0, 1), + ); + } +} \ No newline at end of file diff --git a/src/pocketmine/block/Lapis.php b/src/pocketmine/block/Lapis.php new file mode 100644 index 000000000..fc1844b25 --- /dev/null +++ b/src/pocketmine/block/Lapis.php @@ -0,0 +1,55 @@ +hardness = 15; + } + + public function getBreakTime(Item $item){ + switch($item->isPickaxe()){ + case 5: + return 0.6; + case 4: + return 0.75; + case 3: + return 1.15; + default: + return 15; + } + } + + public function getDrops(Item $item){ + if($item->isPickaxe() >= 3){ + return array( + array(Item::LAPIS_BLOCK, 0, 1), + ); + }else{ + return array(); + } + } + +} \ No newline at end of file diff --git a/src/pocketmine/block/LapisOre.php b/src/pocketmine/block/LapisOre.php new file mode 100644 index 000000000..c8c079078 --- /dev/null +++ b/src/pocketmine/block/LapisOre.php @@ -0,0 +1,56 @@ +hardness = 15; + } + + public function getBreakTime(Item $item){ + + switch($item->isPickaxe()){ + case 5: + return 0.6; + case 4: + return 0.75; + case 3: + return 1.15; + default: + return 15; + } + } + + public function getDrops(Item $item){ + if($item->isPickaxe() >= 3){ + return array( + array(Item::DYE, 4, mt_rand(4, 8)), + ); + }else{ + return array(); + } + } + +} \ No newline at end of file diff --git a/src/pocketmine/block/Lava.php b/src/pocketmine/block/Lava.php new file mode 100644 index 000000000..d32b84dae --- /dev/null +++ b/src/pocketmine/block/Lava.php @@ -0,0 +1,154 @@ +hardness = 0; + } + + public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ + $ret = $this->level->setBlock($this, $this, true, false, true); + Server::getInstance()->api->block->scheduleBlockUpdate(clone $this, 40, Level::BLOCK_UPDATE_NORMAL); + + return $ret; + } + + public function getSourceCount(){ + $count = 0; + for($side = 2; $side <= 5; ++$side){ + if($this->getSide($side) instanceof Lava){ + $b = $this->getSide($side); + $level = $b->meta & 0x07; + if($level == 0x00){ + $count++; + } + } + } + + return $count; + } + + public function checkWater(){ + for($side = 1; $side <= 5; ++$side){ + $b = $this->getSide($side); + if($b instanceof Water){ + $level = $this->meta & 0x07; + if($level == 0x00){ + $this->level->setBlock($this, new Obsidian(), false, false, true); + }else{ + $this->level->setBlock($this, new Cobblestone(), false, false, true); + } + } + } + } + + public function getFrom(){ + for($side = 0; $side <= 5; ++$side){ + $b = $this->getSide($side); + if($b instanceof Lava){ + $tlevel = $b->meta & 0x07; + $level = $this->meta & 0x07; + if(($tlevel + 2) == $level || ($side == 0x01 && $level == 0x01) || ($tlevel == 6 && $level == 7)){ + return $b; + } + } + } + + return null; + } + + public function onUpdate($type){ + //return false; + $newId = $this->id; + $level = $this->meta & 0x07; + if($type !== Level::BLOCK_UPDATE_NORMAL){ + return false; + } + + if($this->checkWater()){ + return; + } + + $falling = $this->meta >> 3; + $down = $this->getSide(0); + + $from = $this->getFrom(); + if($from !== null || $level == 0x00){ + if($level !== 0x07){ + if($down instanceof Air || $down instanceof Lava){ + $this->level->setBlock($down, new Lava(0x01), false, false, true); + Server::getInstance()->api->block->scheduleBlockUpdate(new Position($down, 0, 0, $this->level), 40, Level::BLOCK_UPDATE_NORMAL); + }else{ + for($side = 2; $side <= 5; ++$side){ + $b = $this->getSide($side); + if($b instanceof Lava){ + + }elseif($b->isFlowable === true){ + $this->level->setBlock($b, new Lava(min($level + 2, 7)), false, false, true); + Server::getInstance()->api->block->scheduleBlockUpdate(Position::fromObject($b, $this->level), 40, Level::BLOCK_UPDATE_NORMAL); + } + } + } + } + }else{ + //Extend Remove for Left Lavas + for($side = 2; $side <= 5; ++$side){ + $sb = $this->getSide($side); + if($sb instanceof Lava){ + $tlevel = $sb->meta & 0x07; + if($tlevel != 0x00){ + for($s = 0; $s <= 5; $s++){ + $ssb = $sb->getSide($s); + Server::getInstance()->api->block->scheduleBlockUpdate(Position::fromObject($ssb, $this->level), 40, Level::BLOCK_UPDATE_NORMAL); + } + $this->level->setBlock($sb, new Air(), false, false, true); + } + } + $b = $this->getSide(0)->getSide($side); + if($b instanceof Lava){ + $tlevel = $b->meta & 0x07; + if($tlevel != 0x00){ + for($s = 0; $s <= 5; $s++){ + $ssb = $sb->getSide($s); + Server::getInstance()->api->block->scheduleBlockUpdate(Position::fromObject($ssb, $this->level), 40, Level::BLOCK_UPDATE_NORMAL); + } + $this->level->setBlock($b, new Air(), false, false, true); + } + } + //Server::getInstance()->api->block->scheduleBlockUpdate(Position::fromObject($b, $this->level), 10, Level::BLOCK_UPDATE_NORMAL); + } + + $this->level->setBlock($this, new Air(), false, false, true); + } + + return false; + } + +} diff --git a/src/pocketmine/block/Leaves.php b/src/pocketmine/block/Leaves.php new file mode 100644 index 000000000..b2f5f5119 --- /dev/null +++ b/src/pocketmine/block/Leaves.php @@ -0,0 +1,163 @@ + "Oak Leaves", + self::SPRUCE => "Spruce Leaves", + self::BIRCH => "Birch Leaves", + self::JUNGLE => "Jungle Leaves", + ); + $this->name = $names[$this->meta & 0x03]; + $this->hardness = 1; + } + + private function findLog(Block $pos, array $visited, $distance, &$check, $fromSide = null){ + ++$check; + $index = $pos->x . "." . $pos->y . "." . $pos->z; + if(isset($visited[$index])){ + return false; + } + if($pos->getID() === self::WOOD){ + return true; + }elseif($pos->getID() === self::LEAVES and $distance < 3){ + $visited[$index] = true; + $down = $pos->getSide(0)->getID(); + if($down === Item::WOOD){ + return true; + } + if($fromSide === null){ + for($side = 2; $side <= 5; ++$side){ + if($this->findLog($pos->getSide($side), $visited, $distance + 1, $check, $side) === true){ + return true; + } + } + }else{ //No more loops + switch($fromSide){ + case 2: + if($this->findLog($pos->getSide(2), $visited, $distance + 1, $check, $fromSide) === true){ + return true; + }elseif($this->findLog($pos->getSide(4), $visited, $distance + 1, $check, $fromSide) === true){ + return true; + }elseif($this->findLog($pos->getSide(5), $visited, $distance + 1, $check, $fromSide) === true){ + return true; + } + break; + case 3: + if($this->findLog($pos->getSide(3), $visited, $distance + 1, $check, $fromSide) === true){ + return true; + }elseif($this->findLog($pos->getSide(4), $visited, $distance + 1, $check, $fromSide) === true){ + return true; + }elseif($this->findLog($pos->getSide(5), $visited, $distance + 1, $check, $fromSide) === true){ + return true; + } + break; + case 4: + if($this->findLog($pos->getSide(2), $visited, $distance + 1, $check, $fromSide) === true){ + return true; + }elseif($this->findLog($pos->getSide(3), $visited, $distance + 1, $check, $fromSide) === true){ + return true; + }elseif($this->findLog($pos->getSide(4), $visited, $distance + 1, $check, $fromSide) === true){ + return true; + } + break; + case 5: + if($this->findLog($pos->getSide(2), $visited, $distance + 1, $check, $fromSide) === true){ + return true; + }elseif($this->findLog($pos->getSide(3), $visited, $distance + 1, $check, $fromSide) === true){ + return true; + }elseif($this->findLog($pos->getSide(5), $visited, $distance + 1, $check, $fromSide) === true){ + return true; + } + break; + } + } + } + + return false; + } + + public function onUpdate($type){ + if($type === Level::BLOCK_UPDATE_NORMAL){ + if(($this->meta & 0b00001100) === 0){ + $this->meta |= 0x08; + $this->level->setBlock($this, $this, false, false, true); + } + }elseif($type === Level::BLOCK_UPDATE_RANDOM){ + if(($this->meta & 0b00001100) === 0x08){ + $this->meta &= 0x03; + $visited = array(); + $check = 0; + if($this->findLog($this, $visited, 0, $check) === true){ + $this->level->setBlock($this, $this, false, false, true); + }else{ + $this->level->setBlock($this, new Air(), false, false, true); + if(mt_rand(1, 20) === 1){ //Saplings + //TODO + //Server::getInstance()->api->entity->drop($this, Item::get(Item::SAPLING, $this->meta & 0x03, 1)); + } + if(($this->meta & 0x03) === self::OAK and mt_rand(1, 200) === 1){ //Apples + //TODO + //Server::getInstance()->api->entity->drop($this, Item::get(Item::APPLE, 0, 1)); + } + + return Level::BLOCK_UPDATE_NORMAL; + } + } + } + + return false; + } + + public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ + $this->meta |= 0x04; + $this->level->setBlock($this, $this, true, false, true); + } + + public function getDrops(Item $item){ + $drops = array(); + if($item->isShears()){ + $drops[] = array(Item::LEAVES, $this->meta & 0x03, 1); + }else{ + if(mt_rand(1, 20) === 1){ //Saplings + $drops[] = array(Item::SAPLING, $this->meta & 0x03, 1); + } + if(($this->meta & 0x03) === self::OAK and mt_rand(1, 200) === 1){ //Apples + $drops[] = array(Item::APPLE, 0, 1); + } + } + + return $drops; + } +} \ No newline at end of file diff --git a/src/pocketmine/block/Liquid.php b/src/pocketmine/block/Liquid.php new file mode 100644 index 000000000..7125d2e99 --- /dev/null +++ b/src/pocketmine/block/Liquid.php @@ -0,0 +1,34 @@ +isLiquid = true; + $this->breakable = false; + $this->isReplaceable = true; + $this->isSolid = false; + $this->isFullBlock = true; + } +} \ No newline at end of file diff --git a/src/pocketmine/block/LitPumpkin.php b/src/pocketmine/block/LitPumpkin.php new file mode 100644 index 000000000..9f8ae8652 --- /dev/null +++ b/src/pocketmine/block/LitPumpkin.php @@ -0,0 +1,45 @@ +hardness = 5; + } + + public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ + $faces = array( + 0 => 4, + 1 => 2, + 2 => 5, + 3 => 3, + ); + $this->meta = $faces[$player->getDirection()]; + $this->level->setBlock($block, $this, true, false, true); + + return true; + } +} \ No newline at end of file diff --git a/src/pocketmine/block/Melon.php b/src/pocketmine/block/Melon.php new file mode 100644 index 000000000..8a9bc5d56 --- /dev/null +++ b/src/pocketmine/block/Melon.php @@ -0,0 +1,37 @@ +hardness = 5; + } + + public function getDrops(Item $item){ + return array( + array(Item::MELON_SLICE, 0, mt_rand(3, 7)), + ); + } +} \ No newline at end of file diff --git a/src/pocketmine/block/MelonStem.php b/src/pocketmine/block/MelonStem.php new file mode 100644 index 000000000..7ea5d2852 --- /dev/null +++ b/src/pocketmine/block/MelonStem.php @@ -0,0 +1,102 @@ +isActivable = true; + $this->hardness = 0; + } + + public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ + $down = $this->getSide(0); + if($down->getID() === self::FARMLAND){ + $this->level->setBlock($block, $this, true, false, true); + + return true; + } + + return false; + } + + public function onUpdate($type){ + if($type === Level::BLOCK_UPDATE_NORMAL){ + if($this->getSide(0)->isTransparent === true){ //Replace with common break method + //TODO + //Server::getInstance()->api->entity->drop($this, Item::get(MELON_SEEDS, 0, mt_rand(0, 2))); + $this->level->setBlock($this, new Air(), false, false, true); + + return Level::BLOCK_UPDATE_NORMAL; + } + }elseif($type === Level::BLOCK_UPDATE_RANDOM){ + if(mt_rand(0, 2) == 1){ + if($this->meta < 0x07){ + ++$this->meta; + $this->level->setBlock($this, $this, true, false, true); + + return Level::BLOCK_UPDATE_RANDOM; + }else{ + for($side = 2; $side <= 5; ++$side){ + $b = $this->getSide($side); + if($b->getID() === self::MELON_BLOCK){ + return Level::BLOCK_UPDATE_RANDOM; + } + } + $side = $this->getSide(mt_rand(2, 5)); + $d = $side->getSide(0); + if($side->getID() === self::AIR and ($d->getID() === self::FARMLAND or $d->getID() === self::GRASS or $d->getID() === self::DIRT)){ + $this->level->setBlock($side, new Melon(), true, false, true); + } + } + } + + return Level::BLOCK_UPDATE_RANDOM; + } + + return false; + } + + public function onActivate(Item $item, Player $player = null){ + if($item->getID() === Item::DYE and $item->getMetadata() === 0x0F){ //Bonemeal + $this->meta = 0x07; + $this->level->setBlock($this, $this, true, false, true); + if(($player->gamemode & 0x01) === 0){ + $item->count--; + } + + return true; + } + + return false; + } + + public function getDrops(Item $item){ + return array( + array(Item::MELON_SEEDS, 0, mt_rand(0, 2)), + ); + } +} \ No newline at end of file diff --git a/src/pocketmine/block/MossStone.php b/src/pocketmine/block/MossStone.php new file mode 100644 index 000000000..36c062109 --- /dev/null +++ b/src/pocketmine/block/MossStone.php @@ -0,0 +1,59 @@ +hardness = 30; + } + + public function getBreakTime(Item $item){ + + switch($item->isPickaxe()){ + case 5: + return 0.4; + case 4: + return 0.5; + case 3: + return 0.75; + case 2: + return 0.25; + case 1: + return 1.5; + default: + return 10; + } + } + + public function getDrops(Item $item){ + if($item->isPickaxe() >= 1){ + return array( + array(Item::MOSS_STONE, $this->meta, 1), + ); + }else{ + return array(); + } + } +} \ No newline at end of file diff --git a/src/pocketmine/block/NetherBrick.php b/src/pocketmine/block/NetherBrick.php new file mode 100644 index 000000000..737889219 --- /dev/null +++ b/src/pocketmine/block/NetherBrick.php @@ -0,0 +1,59 @@ +hardness = 30; + } + + public function getBreakTime(Item $item){ + + switch($item->isPickaxe()){ + case 5: + return 0.4; + case 4: + return 0.5; + case 3: + return 0.75; + case 2: + return 0.25; + case 1: + return 1.5; + default: + return 10; + } + } + + public function getDrops(Item $item){ + if($item->isPickaxe() >= 1){ + return array( + array(Item::NETHER_BRICKS, 0, 1), + ); + }else{ + return array(); + } + } +} \ No newline at end of file diff --git a/src/pocketmine/block/NetherBrickStairs.php b/src/pocketmine/block/NetherBrickStairs.php new file mode 100644 index 000000000..b9f4955cd --- /dev/null +++ b/src/pocketmine/block/NetherBrickStairs.php @@ -0,0 +1,30 @@ +isActivable = true; + } + +} \ No newline at end of file diff --git a/src/pocketmine/block/Netherrack.php b/src/pocketmine/block/Netherrack.php new file mode 100644 index 000000000..93d54c4dd --- /dev/null +++ b/src/pocketmine/block/Netherrack.php @@ -0,0 +1,59 @@ +hardness = 2; + } + + public function getBreakTime(Item $item){ + + switch($item->isPickaxe()){ + case 5: + return 0.1; + case 4: + return 0.1; + case 3: + return 0.15; + case 2: + return 0.05; + case 1: + return 0.3; + default: + return 2; + } + } + + public function getDrops(Item $item){ + if($item->isPickaxe() >= 1){ + return array( + array(Item::NETHERRACK, 0, 1), + ); + }else{ + return array(); + } + } +} \ No newline at end of file diff --git a/src/pocketmine/block/Obsidian.php b/src/pocketmine/block/Obsidian.php new file mode 100644 index 000000000..9227b3c7e --- /dev/null +++ b/src/pocketmine/block/Obsidian.php @@ -0,0 +1,50 @@ +hardness = 6000; + } + + public function getBreakTime(Item $item){ + + if($item->isPickaxe() >= 5){ + return 9.4; + }else{ + return 250; + } + } + + public function getDrops(Item $item){ + if($item->isPickaxe() >= 5){ + return array( + array(Item::OBSIDIAN, 0, 1), + ); + }else{ + return array(); + } + } +} \ No newline at end of file diff --git a/src/pocketmine/block/Planks.php b/src/pocketmine/block/Planks.php new file mode 100644 index 000000000..431b9aecc --- /dev/null +++ b/src/pocketmine/block/Planks.php @@ -0,0 +1,38 @@ + "Oak Wooden Planks", + Wood::SPRUCE => "Spruce Wooden Planks", + Wood::BIRCH => "Birch Wooden Planks", + Wood::JUNGLE => "Jungle Wooden Planks", + ); + $this->name = $names[$this->meta & 0x03]; + $this->hardness = 15; + } + +} \ No newline at end of file diff --git a/src/pocketmine/block/Potato.php b/src/pocketmine/block/Potato.php new file mode 100644 index 000000000..c11b3532f --- /dev/null +++ b/src/pocketmine/block/Potato.php @@ -0,0 +1,95 @@ +isActivable = true; + $this->hardness = 0; + } + + public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ + $down = $this->getSide(0); + if($down->getID() === self::FARMLAND){ + $this->level->setBlock($block, $this, true, false, true); + + return true; + } + + return false; + } + + public function onActivate(Item $item, Player $player = null){ + if($item->getID() === Item::DYE and $item->getMetadata() === 0x0F){ //Bonemeal + $this->meta = 0x07; + $this->level->setBlock($this, $this, true, false, true); + if(($player->gamemode & 0x01) === 0){ + $item->count--; + } + + return true; + } + + return false; + } + + public function onUpdate($type){ + if($type === Level::BLOCK_UPDATE_NORMAL){ + if($this->getSide(0)->isTransparent === true){ //Replace with common break method + //TODO + //Server::getInstance()->api->entity->drop($this, Item::get(POTATO, 0, 1)); + $this->level->setBlock($this, new Air(), false, false, true); + + return Level::BLOCK_UPDATE_NORMAL; + } + }elseif($type === Level::BLOCK_UPDATE_RANDOM){ + if(mt_rand(0, 2) == 1){ + if($this->meta < 0x07){ + ++$this->meta; + $this->level->setBlock($this, $this, true, false, true); + + return Level::BLOCK_UPDATE_RANDOM; + } + }else{ + return Level::BLOCK_UPDATE_RANDOM; + } + } + + return false; + } + + public function getDrops(Item $item){ + $drops = array(); + if($this->meta >= 0x07){ + $drops[] = array(Item::POTATO, 0, mt_rand(1, 4)); + }else{ + $drops[] = array(Item::POTATO, 0, 1); + } + + return $drops; + } +} \ No newline at end of file diff --git a/src/pocketmine/block/Pumpkin.php b/src/pocketmine/block/Pumpkin.php new file mode 100644 index 000000000..fd179f062 --- /dev/null +++ b/src/pocketmine/block/Pumpkin.php @@ -0,0 +1,31 @@ +hardness = 5; + } + +} \ No newline at end of file diff --git a/src/pocketmine/block/PumpkinStem.php b/src/pocketmine/block/PumpkinStem.php new file mode 100644 index 000000000..d85536b89 --- /dev/null +++ b/src/pocketmine/block/PumpkinStem.php @@ -0,0 +1,102 @@ +isActivable = true; + $this->hardness = 0; + } + + public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ + $down = $this->getSide(0); + if($down->getID() === self::FARMLAND){ + $this->level->setBlock($block, $this, true, false, true); + + return true; + } + + return false; + } + + public function onUpdate($type){ + if($type === Level::BLOCK_UPDATE_NORMAL){ + if($this->getSide(0)->isTransparent === true){ //Replace with common break method + //TODO + //Server::getInstance()->api->entity->drop($this, Item::get(PUMPKIN_SEEDS, 0, mt_rand(0, 2))); + $this->level->setBlock($this, new Air(), false, false, true); + + return Level::BLOCK_UPDATE_NORMAL; + } + }elseif($type === Level::BLOCK_UPDATE_RANDOM){ + if(mt_rand(0, 2) == 1){ + if($this->meta < 0x07){ + ++$this->meta; + $this->level->setBlock($this, $this, true, false, true); + + return Level::BLOCK_UPDATE_RANDOM; + }else{ + for($side = 2; $side <= 5; ++$side){ + $b = $this->getSide($side); + if($b->getID() === self::PUMPKIN){ + return Level::BLOCK_UPDATE_RANDOM; + } + } + $side = $this->getSide(mt_rand(2, 5)); + $d = $side->getSide(0); + if($side->getID() === self::AIR and ($d->getID() === self::FARMLAND or $d->getID() === self::GRASS or $d->getID() === self::DIRT)){ + $this->level->setBlock($side, new Pumpkin(), true, false, true); + } + } + } + + return Level::BLOCK_UPDATE_RANDOM; + } + + return false; + } + + public function onActivate(Item $item, Player $player = null){ + if($item->getID() === Item::DYE and $item->getMetadata() === 0x0F){ //Bonemeal + $this->meta = 0x07; + $this->level->setBlock($this, $this, true, false, true); + if(($player->gamemode & 0x01) === 0){ + $item->count--; + } + + return true; + } + + return false; + } + + public function getDrops(Item $item){ + return array( + array(Item::PUMPKIN_SEEDS, 0, mt_rand(0, 2)), + ); + } +} \ No newline at end of file diff --git a/src/pocketmine/block/Quartz.php b/src/pocketmine/block/Quartz.php new file mode 100644 index 000000000..db5aa2ac3 --- /dev/null +++ b/src/pocketmine/block/Quartz.php @@ -0,0 +1,65 @@ + "Quartz Block", + 1 => "Chiseled Quartz Block", + 2 => "Quartz Pillar", + 3 => "Quartz Pillar", + ); + $this->name = $names[$this->meta & 0x03]; + } + + public function getBreakTime(Item $item){ + + switch($item->isPickaxe()){ + case 5: + return 0.15; + case 4: + return 0.2; + case 3: + return 0.3; + case 2: + return 0.1; + case 1: + return 0.6; + default: + return 4; + } + } + + public function getDrops(Item $item){ + if($item->isPickaxe() >= 1){ + return array( + array(Item::QUARTZ_BLOCK, $this->meta & 0x03, 1), + ); + }else{ + return array(); + } + } +} \ No newline at end of file diff --git a/src/pocketmine/block/QuartzStairs.php b/src/pocketmine/block/QuartzStairs.php new file mode 100644 index 000000000..7fa1f37ed --- /dev/null +++ b/src/pocketmine/block/QuartzStairs.php @@ -0,0 +1,30 @@ +hardness = 0; + } + + public function onUpdate($type){ + if($type === Level::BLOCK_UPDATE_NORMAL){ + if($this->getSide(0)->isTransparent === true){ //Replace with common break method + //TODO + //Server::getInstance()->api->entity->drop($this, Item::get($this->id)); + $this->level->setBlock($this, new Air(), false); + + return Level::BLOCK_UPDATE_NORMAL; + } + } + + return false; + } + + public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ + $down = $this->getSide(0); + if($down->isTransparent === false){ + $this->level->setBlock($block, $this, true, false, true); + + return true; + } + + return false; + } +} \ No newline at end of file diff --git a/src/pocketmine/block/RedstoneOre.php b/src/pocketmine/block/RedstoneOre.php new file mode 100644 index 000000000..d6272cf36 --- /dev/null +++ b/src/pocketmine/block/RedstoneOre.php @@ -0,0 +1,52 @@ +hardness = 15; + } + + public function onUpdate($type){ + if($type === Level::BLOCK_UPDATE_NORMAL or $type === Level::BLOCK_UPDATE_TOUCH){ + $this->level->setBlock($this, Block::get(Item::GLOWING_REDSTONE_ORE, $this->meta), false, false, true); + + return Level::BLOCK_UPDATE_WEAK; + } + + return false; + } + + public function getDrops(Item $item){ + if($item->isPickaxe() >= 2){ + return array( + array(Item::REDSTONE_DUST, 0, mt_rand(4, 5)), + ); + }else{ + return array(); + } + } +} \ No newline at end of file diff --git a/src/pocketmine/block/Sand.php b/src/pocketmine/block/Sand.php new file mode 100644 index 000000000..6963273b7 --- /dev/null +++ b/src/pocketmine/block/Sand.php @@ -0,0 +1,31 @@ +hardness = 2.5; + } + +} \ No newline at end of file diff --git a/src/pocketmine/block/Sandstone.php b/src/pocketmine/block/Sandstone.php new file mode 100644 index 000000000..f9f83ffc4 --- /dev/null +++ b/src/pocketmine/block/Sandstone.php @@ -0,0 +1,66 @@ + "Sandstone", + 1 => "Chiseled Sandstone", + 2 => "Smooth Sandstone", + ); + $this->name = $names[$this->meta & 0x03]; + $this->hardness = 4; + } + + public function getBreakTime(Item $item){ + + switch($item->isPickaxe()){ + case 5: + return 0.15; + case 4: + return 0.2; + case 3: + return 0.3; + case 2: + return 0.1; + case 1: + return 0.6; + default: + return 4; + } + } + + public function getDrops(Item $item){ + if($item->isPickaxe() >= 1){ + return array( + array(Item::SANDSTONE, $this->meta & 0x03, 1), + ); + }else{ + return array(); + } + } + +} \ No newline at end of file diff --git a/src/pocketmine/block/SandstoneStairs.php b/src/pocketmine/block/SandstoneStairs.php new file mode 100644 index 000000000..a8496a0f6 --- /dev/null +++ b/src/pocketmine/block/SandstoneStairs.php @@ -0,0 +1,30 @@ +isActivable = true; + $names = array( + 0 => "Oak Sapling", + 1 => "Spruce Sapling", + 2 => "Birch Sapling", + 3 => "Jungle Sapling", + ); + $this->name = $names[$this->meta & 0x03]; + $this->hardness = 0; + } + + public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ + $down = $this->getSide(0); + if($down->getID() === self::GRASS or $down->getID() === self::DIRT or $down->getID() === self::FARMLAND){ + $this->level->setBlock($block, $this, true, false, true); + + return true; + } + + return false; + } + + public function onActivate(Item $item, Player $player = null){ + if($item->getID() === Item::DYE and $item->getMetadata() === 0x0F){ //Bonemeal + Tree::growTree($this->level, $this, new Random(), $this->meta & 0x03); + if(($player->gamemode & 0x01) === 0){ + $item->count--; + } + + return true; + } + + return false; + } + + public function onUpdate($type){ + if($type === Level::BLOCK_UPDATE_NORMAL){ + if($this->getSide(0)->isTransparent === true){ //Replace with common break method + //TODO + //Server::getInstance()->api->entity->drop($this, Item::get($this->id)); + $this->level->setBlock($this, new Air(), false, false, true); + + return Level::BLOCK_UPDATE_NORMAL; + } + }elseif($type === Level::BLOCK_UPDATE_RANDOM){ //Growth + if(mt_rand(1, 7) === 1){ + if(($this->meta & 0x08) === 0x08){ + Tree::growTree($this->level, $this, new Random(), $this->meta & 0x03); + }else{ + $this->meta |= 0x08; + $this->level->setBlock($this, $this, true, false, true); + + return Level::BLOCK_UPDATE_RANDOM; + } + }else{ + return Level::BLOCK_UPDATE_RANDOM; + } + } + + return false; + } + + public function getDrops(Item $item){ + return array( + array($this->id, $this->meta & 0x03, 1), + ); + } +} \ No newline at end of file diff --git a/src/pocketmine/block/SignPost.php b/src/pocketmine/block/SignPost.php new file mode 100644 index 000000000..d325507ae --- /dev/null +++ b/src/pocketmine/block/SignPost.php @@ -0,0 +1,85 @@ +isSolid = false; + $this->isFullBlock = false; + $this->hardness = 5; + } + + public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ + if($face !== 0){ + $faces = array( + 2 => 2, + 3 => 3, + 4 => 4, + 5 => 5, + ); + if(!isset($faces[$face])){ + $this->meta = floor((($player->yaw + 180) * 16 / 360) + 0.5) & 0x0F; + $this->level->setBlock($block, Block::get(Item::SIGN_POST, $this->meta), true, false, true); + + return true; + }else{ + $this->meta = $faces[$face]; + $this->level->setBlock($block, Block::get(Item::WALL_SIGN, $this->meta), true, false, true); + + return true; + } + } + + return false; + } + + public function onUpdate($type){ + if($type === Level::BLOCK_UPDATE_NORMAL){ + if($this->getSide(0)->getID() === self::AIR){ //Replace with common break method + //TODO + //Server::getInstance()->api->entity->drop($this, Item::get(SIGN, 0, 1)); + $this->level->setBlock($this, new Air(), true, true, true); + + return Level::BLOCK_UPDATE_NORMAL; + } + } + + return false; + } + + public function onBreak(Item $item){ + $this->level->setBlock($this, new Air(), true, true, true); + + return true; + } + + public function getDrops(Item $item){ + return array( + array(Item::SIGN, 0, 1), + ); + } +} \ No newline at end of file diff --git a/src/pocketmine/block/Slab.php b/src/pocketmine/block/Slab.php new file mode 100644 index 000000000..59e7da69a --- /dev/null +++ b/src/pocketmine/block/Slab.php @@ -0,0 +1,125 @@ + "Stone", + 1 => "Sandstone", + 2 => "Wooden", + 3 => "Cobblestone", + 4 => "Brick", + 5 => "Stone Brick", + 6 => "Quartz", + 7 => "", + ); + $this->name = (($this->meta & 0x08) === 0x08 ? "Upper " : "") . $names[$this->meta & 0x07] . " Slab"; + if(($this->meta & 0x08) === 0x08){ + $this->isFullBlock = true; + }else{ + $this->isFullBlock = false; + } + $this->hardness = 30; + } + + public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ + $this->meta &= 0x07; + if($face === 0){ + if($target->getID() === self::SLAB and ($target->getMetadata() & 0x08) === 0x08 and ($target->getMetadata() & 0x07) === ($this->meta & 0x07)){ + $this->level->setBlock($target, Block::get(Item::DOUBLE_SLAB, $this->meta), true, false, true); + + return true; + }elseif($block->getID() === self::SLAB and ($block->getMetadata() & 0x07) === ($this->meta & 0x07)){ + $this->level->setBlock($block, Block::get(Item::DOUBLE_SLAB, $this->meta), true, false, true); + + return true; + }else{ + $this->meta |= 0x08; + } + }elseif($face === 1){ + if($target->getID() === self::SLAB and ($target->getMetadata() & 0x08) === 0 and ($target->getMetadata() & 0x07) === ($this->meta & 0x07)){ + $this->level->setBlock($target, Block::get(Item::DOUBLE_SLAB, $this->meta), true, false, true); + + return true; + }elseif($block->getID() === self::SLAB and ($block->getMetadata() & 0x07) === ($this->meta & 0x07)){ + $this->level->setBlock($block, Block::get(Item::DOUBLE_SLAB, $this->meta), true, false, true); + + return true; + } + }elseif(!($player instanceof Player) or !$player->inBlock($block)){ + if($block->getID() === self::SLAB){ + if(($block->getMetadata() & 0x07) === ($this->meta & 0x07)){ + $this->level->setBlock($block, Block::get(Item::DOUBLE_SLAB, $this->meta), true, false, true); + + return true; + } + + return false; + }else{ + if($fy > 0.5){ + $this->meta |= 0x08; + } + } + }else{ + return false; + } + if($block->getID() === self::SLAB and ($target->getMetadata() & 0x07) !== ($this->meta & 0x07)){ + return false; + } + $this->level->setBlock($block, $this, true, false, true); + + return true; + } + + public function getBreakTime(Item $item){ + + switch($item->isPickaxe()){ + case 5: + return 0.4; + case 4: + return 0.5; + case 3: + return 0.75; + case 2: + return 0.25; + case 1: + return 1.5; + default: + return 10; + } + } + + public function getDrops(Item $item){ + if($item->isPickaxe() >= 1){ + return array( + array($this->id, $this->meta & 0x07, 1), + ); + }else{ + return array(); + } + } +} \ No newline at end of file diff --git a/src/pocketmine/block/Snow.php b/src/pocketmine/block/Snow.php new file mode 100644 index 000000000..e841621d7 --- /dev/null +++ b/src/pocketmine/block/Snow.php @@ -0,0 +1,31 @@ +hardness = 1; + } + +} \ No newline at end of file diff --git a/src/pocketmine/block/SnowLayer.php b/src/pocketmine/block/SnowLayer.php new file mode 100644 index 000000000..8ebe11dc6 --- /dev/null +++ b/src/pocketmine/block/SnowLayer.php @@ -0,0 +1,69 @@ +isReplaceable = true; + $this->isSolid = false; + $this->isFullBlock = false; + $this->hardness = 0.5; + } + + public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ + $down = $this->getSide(0); + if($down instanceof Solid){ + $this->level->setBlock($block, $this, true, false, true); + + return true; + } + + return false; + } + + public function onUpdate($type){ + if($type === Level::BLOCK_UPDATE_NORMAL){ + if($this->getSide(0)->getID() === self::AIR){ //Replace with common break method + $this->level->setBlock($this, new Air(), true, false, true); + + return Level::BLOCK_UPDATE_NORMAL; + } + } + + return false; + } + + public function getDrops(Item $item){ + if($item->isShovel() !== false){ + return array( + array(Item::SNOWBALL, 0, 1), + ); + } + + return array(); + } +} \ No newline at end of file diff --git a/src/pocketmine/block/Solid.php b/src/pocketmine/block/Solid.php new file mode 100644 index 000000000..ed3d7af6e --- /dev/null +++ b/src/pocketmine/block/Solid.php @@ -0,0 +1,32 @@ +isSolid = true; + $this->isFullBlock = true; + } +} \ No newline at end of file diff --git a/src/pocketmine/block/SoulSand.php b/src/pocketmine/block/SoulSand.php new file mode 100644 index 000000000..17932de93 --- /dev/null +++ b/src/pocketmine/block/SoulSand.php @@ -0,0 +1,31 @@ +hardness = 2.5; + } + +} \ No newline at end of file diff --git a/src/pocketmine/block/Sponge.php b/src/pocketmine/block/Sponge.php new file mode 100644 index 000000000..fcc482f77 --- /dev/null +++ b/src/pocketmine/block/Sponge.php @@ -0,0 +1,31 @@ +hardness = 3; + } + +} \ No newline at end of file diff --git a/src/pocketmine/block/SpruceWoodStairs.php b/src/pocketmine/block/SpruceWoodStairs.php new file mode 100644 index 000000000..22bb4f2fe --- /dev/null +++ b/src/pocketmine/block/SpruceWoodStairs.php @@ -0,0 +1,36 @@ +id, 0, 1), + ); + } +} \ No newline at end of file diff --git a/src/pocketmine/block/Stair.php b/src/pocketmine/block/Stair.php new file mode 100644 index 000000000..d8d3a8971 --- /dev/null +++ b/src/pocketmine/block/Stair.php @@ -0,0 +1,64 @@ +meta & 0x04) === 0x04){ + $this->isFullBlock = true; + }else{ + $this->isFullBlock = false; + } + $this->hardness = 30; + } + + public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ + $faces = array( + 0 => 0, + 1 => 2, + 2 => 1, + 3 => 3, + ); + $this->meta = $faces[$player->getDirection()] & 0x03; + if(($fy > 0.5 and $face !== 1) or $face === 0){ + $this->meta |= 0x04; //Upside-down stairs + } + $this->level->setBlock($block, $this, true, false, true); + + return true; + } + + public function getDrops(Item $item){ + if($item->isPickaxe() >= 1){ + return array( + array($this->id, 0, 1), + ); + }else{ + return array(); + } + } +} \ No newline at end of file diff --git a/src/pocketmine/block/StillLava.php b/src/pocketmine/block/StillLava.php new file mode 100644 index 000000000..33808bcdd --- /dev/null +++ b/src/pocketmine/block/StillLava.php @@ -0,0 +1,31 @@ +hardness = 500; + } + +} \ No newline at end of file diff --git a/src/pocketmine/block/StillWater.php b/src/pocketmine/block/StillWater.php new file mode 100644 index 000000000..1ef791042 --- /dev/null +++ b/src/pocketmine/block/StillWater.php @@ -0,0 +1,31 @@ +hardness = 500; + } + +} \ No newline at end of file diff --git a/src/pocketmine/block/Stone.php b/src/pocketmine/block/Stone.php new file mode 100644 index 000000000..987ee5207 --- /dev/null +++ b/src/pocketmine/block/Stone.php @@ -0,0 +1,60 @@ +hardness = 30; + } + + public function getBreakTime(Item $item){ + + switch($item->isPickaxe()){ + case 5: + return 0.4; + case 4: + return 0.5; + case 3: + return 0.75; + case 2: + return 0.25; + case 1: + return 1.5; + default: + return 7.5; + } + } + + public function getDrops(Item $item){ + if($item->isPickaxe() >= 1){ + return array( + array(Item::COBBLESTONE, 0, 1), + ); + }else{ + return array(); + } + } + +} \ No newline at end of file diff --git a/src/pocketmine/block/StoneBrickStairs.php b/src/pocketmine/block/StoneBrickStairs.php new file mode 100644 index 000000000..ede6833fc --- /dev/null +++ b/src/pocketmine/block/StoneBrickStairs.php @@ -0,0 +1,30 @@ + "Stone Bricks", + 1 => "Mossy Stone Bricks", + 2 => "Cracked Stone Bricks", + 3 => "Chiseled Stone Bricks", + ); + $this->name = $names[$this->meta & 0x03]; + $this->hardness = 30; + } + + public function getBreakTime(Item $item){ + + switch($item->isPickaxe()){ + case 5: + return 0.4; + case 4: + return 0.5; + case 3: + return 0.75; + case 2: + return 0.25; + case 1: + return 1.5; + default: + return 7.5; + } + } + + public function getDrops(Item $item){ + if($item->isPickaxe() >= 1){ + return array( + array(Item::STONE_BRICKS, $this->meta & 0x03, 1), + ); + }else{ + return array(); + } + } + +} \ No newline at end of file diff --git a/src/pocketmine/block/StoneWall.php b/src/pocketmine/block/StoneWall.php new file mode 100644 index 000000000..2e6bda3a2 --- /dev/null +++ b/src/pocketmine/block/StoneWall.php @@ -0,0 +1,37 @@ +name = "Mossy Cobblestone Wall"; + } + $this->isFullBlock = false; + $this->isSolid = false; + $this->hardness = 30; + } + +} \ No newline at end of file diff --git a/src/pocketmine/block/Stonecutter.php b/src/pocketmine/block/Stonecutter.php new file mode 100644 index 000000000..31a19a9c7 --- /dev/null +++ b/src/pocketmine/block/Stonecutter.php @@ -0,0 +1,44 @@ +isActivable = true; + } + + public function onActivate(Item $item, Player $player = null){ + $player->toCraft[-1] = 2; + + return true; + } + + public function getDrops(Item $item){ + return array( + array($this->id, 0, 1), + ); + } +} \ No newline at end of file diff --git a/src/pocketmine/block/Sugarcane.php b/src/pocketmine/block/Sugarcane.php new file mode 100644 index 000000000..aefbe8b10 --- /dev/null +++ b/src/pocketmine/block/Sugarcane.php @@ -0,0 +1,118 @@ +hardness = 0; + } + + public function getDrops(Item $item){ + return array( + array(Item::SUGARCANE, 0, 1), + ); + } + + public function onActivate(Item $item, Player $player = null){ + if($item->getID() === Item::DYE and $item->getMetadata() === 0x0F){ //Bonemeal + if($this->getSide(0)->getID() !== self::SUGARCANE_BLOCK){ + for($y = 1; $y < 3; ++$y){ + $b = $this->level->getBlock(new Vector3($this->x, $this->y + $y, $this->z)); + if($b->getID() === self::AIR){ + $this->level->setBlock($b, new Sugarcane(), true, false, true); + break; + } + } + $this->meta = 0; + $this->level->setBlock($this, $this, true, false, true); + } + if(($player->gamemode & 0x01) === 0){ + $item->count--; + } + + return true; + } + + return false; + } + + public function onUpdate($type){ + if($type === Level::BLOCK_UPDATE_NORMAL){ + $down = $this->getSide(0); + if($down->isTransparent === true and $down->getID() !== self::SUGARCANE_BLOCK){ //Replace with common break method + //TODO + //Server::getInstance()->api->entity->drop($this, Item::get(SUGARCANE)); + $this->level->setBlock($this, new Air(), false, false, true); + + return Level::BLOCK_UPDATE_NORMAL; + } + }elseif($type === Level::BLOCK_UPDATE_RANDOM){ + if($this->getSide(0)->getID() !== self::SUGARCANE_BLOCK){ + if($this->meta === 0x0F){ + for($y = 1; $y < 3; ++$y){ + $b = $this->level->getBlock(new Vector3($this->x, $this->y + $y, $this->z)); + if($b->getID() === self::AIR){ + $this->level->setBlock($b, new Sugarcane(), true, false, true); + break; + } + } + $this->meta = 0; + $this->level->setBlock($this, $this, true, false, true); + }else{ + ++$this->meta; + $this->level->setBlock($this, $this, true, false, true); + } + + return Level::BLOCK_UPDATE_RANDOM; + } + } + + return false; + } + + public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ + $down = $this->getSide(0); + if($down->getID() === self::SUGARCANE_BLOCK){ + $this->level->setBlock($block, new Sugarcane(), true, false, true); + + return true; + }elseif($down->getID() === self::GRASS or $down->getID() === self::DIRT or $down->getID() === self::SAND){ + $block0 = $down->getSide(2); + $block1 = $down->getSide(3); + $block2 = $down->getSide(4); + $block3 = $down->getSide(5); + if(($block0 instanceof Water) or ($block1 instanceof Water) or ($block2 instanceof Water) or ($block3 instanceof Water)){ + $this->level->setBlock($block, new Sugarcane(), true, false, true); + + return true; + } + } + + return false; + } +} \ No newline at end of file diff --git a/src/pocketmine/block/TNT.php b/src/pocketmine/block/TNT.php new file mode 100644 index 000000000..c10efaeda --- /dev/null +++ b/src/pocketmine/block/TNT.php @@ -0,0 +1,56 @@ +hardness = 0; + $this->isActivable = true; + } + + public function onActivate(Item $item, Player $player = null){ + if($item->getID() === Item::FLINT_STEEL){ + if(($player->gamemode & 0x01) === 0){ + $item->useOn($this); + } + $data = array( + "x" => $this->x + 0.5, + "y" => $this->y + 0.5, + "z" => $this->z + 0.5, + "power" => 4, + "fuse" => 20 * 4, //4 seconds + ); + $this->level->setBlock($this, new Air(), false, false, true); + //TODO + //$e = Server::getInstance()->api->entity->add($this->level, ENTITY_OBJECT, OBJECT_PRIMEDTNT, $data); + //$e->spawnToAll(); + + return true; + } + + return false; + } +} \ No newline at end of file diff --git a/src/pocketmine/block/TallGrass.php b/src/pocketmine/block/TallGrass.php new file mode 100644 index 000000000..fdc35983d --- /dev/null +++ b/src/pocketmine/block/TallGrass.php @@ -0,0 +1,77 @@ +isReplaceable = true; + $names = array( + 0 => "Dead Shrub", + 1 => "Tall Grass", + 2 => "Fern", + ); + $this->name = $names[$this->meta & 0x03]; + $this->hardness = 0; + } + + public function onUpdate($type){ + if($type === Level::BLOCK_UPDATE_NORMAL){ + if($this->getSide(0)->isTransparent === true){ //Replace with common break method + $this->level->setBlock($this, new Air(), false, false, true); + + return Level::BLOCK_UPDATE_NORMAL; + } + } + + return false; + } + + public function getDrops(Item $item){ + $drops = array(); + $possibleDrops = array( + array(Item::WHEAT_SEEDS, 0, 1), + array(Item::CARROT, 0, 1), + array(Item::POTATO, 0, 1), + array(Item::BEETROOT_SEEDS, 0, 1), + array(Item::MELON_SEEDS, 0, 1), + array(Item::PUMPKIN_SEEDS, 0, 1), + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ); + if(($item = $possibleDrops[mt_rand(0, count($possibleDrops) - 1)]) !== 0){ + $drops[] = $item; + } + + return $drops; + } + +} \ No newline at end of file diff --git a/src/pocketmine/block/Torch.php b/src/pocketmine/block/Torch.php new file mode 100644 index 000000000..9c7595f4f --- /dev/null +++ b/src/pocketmine/block/Torch.php @@ -0,0 +1,87 @@ +hardness = 0; + } + + public function onUpdate($type){ + if($type === Level::BLOCK_UPDATE_NORMAL){ + $side = $this->getMetadata(); + $faces = array( + 1 => 4, + 2 => 5, + 3 => 2, + 4 => 3, + 5 => 0, + 6 => 0, + 0 => 0, + ); + + if($this->getSide($faces[$side])->isTransparent === true and !($side === 0 and $this->getSide(0)->getID() === self::FENCE)){ //Replace with common break method + //TODO + //Server::getInstance()->api->entity->drop($this, Item::get($this->id, 0, 1)); + $this->level->setBlock($this, new Air(), true, false, true); + + return Level::BLOCK_UPDATE_NORMAL; + } + } + + return false; + } + + public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ + if($target->isTransparent === false and $face !== 0){ + $faces = array( + 1 => 5, + 2 => 4, + 3 => 3, + 4 => 2, + 5 => 1, + ); + $this->meta = $faces[$face]; + $this->level->setBlock($block, $this, true, false, true); + + return true; + }elseif($this->getSide(0)->isTransparent === false or $this->getSide(0)->getID() === self::FENCE){ + $this->meta = 0; + $this->level->setBlock($block, $this, true, false, true); + + return true; + } + + return false; + } + + public function getDrops(Item $item){ + return array( + array($this->id, 0, 1), + ); + } +} \ No newline at end of file diff --git a/src/pocketmine/block/Transparent.php b/src/pocketmine/block/Transparent.php new file mode 100644 index 000000000..c2b33b24e --- /dev/null +++ b/src/pocketmine/block/Transparent.php @@ -0,0 +1,37 @@ +isActivable = false; + $this->breakable = true; + $this->isFlowable = false; + $this->isTransparent = true; + $this->isReplaceable = false; + $this->isPlaceable = true; + $this->isSolid = true; + } +} \ No newline at end of file diff --git a/src/pocketmine/block/Trapdoor.php b/src/pocketmine/block/Trapdoor.php new file mode 100644 index 000000000..2dcb9f4c5 --- /dev/null +++ b/src/pocketmine/block/Trapdoor.php @@ -0,0 +1,71 @@ +isActivable = true; + if(($this->meta & 0x04) === 0x04){ + $this->isFullBlock = false; + }else{ + $this->isFullBlock = true; + } + $this->hardness = 15; + } + + public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ + if(($target->isTransparent === false or $target->getID() === self::SLAB) and $face !== 0 and $face !== 1){ + $faces = array( + 2 => 0, + 3 => 1, + 4 => 2, + 5 => 3, + ); + $this->meta = $faces[$face] & 0x03; + if($fy > 0.5){ + $this->meta |= 0x08; + } + $this->level->setBlock($block, $this, true, false, true); + + return true; + } + + return false; + } + + public function getDrops(Item $item){ + return array( + array($this->id, 0, 1), + ); + } + + public function onActivate(Item $item, Player $player = null){ + $this->meta ^= 0x04; + $this->level->setBlock($this, $this, true, false, true); + + return true; + } +} \ No newline at end of file diff --git a/src/pocketmine/block/WallSign.php b/src/pocketmine/block/WallSign.php new file mode 100644 index 000000000..76c535533 --- /dev/null +++ b/src/pocketmine/block/WallSign.php @@ -0,0 +1,33 @@ +hardness = 500; + } + + public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ + $ret = $this->level->setBlock($this, $this, true, false, true); + Server::getInstance()->api->block->scheduleBlockUpdate(clone $this, 10, Level::BLOCK_UPDATE_NORMAL); + + return $ret; + } + + public function getSourceCount(){ + $count = 0; + for($side = 2; $side <= 5; ++$side){ + if($this->getSide($side) instanceof Water){ + $b = $this->getSide($side); + $level = $b->meta & 0x07; + if($level == 0x00){ + $count++; + } + } + } + + return $count; + } + + public function checkLava(){ + for($side = 0; $side <= 5; ++$side){ + if($side == 1){ + continue; + } + $b = $this->getSide($side); + if($b instanceof Lava){ + $level = $b->meta & 0x07; + if($level == 0x00){ + $this->level->setBlock($b, new Obsidian(), false, false, true); + }else{ + $this->level->setBlock($b, new Cobblestone(), false, false, true); + } + + return true; + } + } + + return false; + } + + public function getFrom(){ + for($side = 0; $side <= 5; ++$side){ + $b = $this->getSide($side); + if($b instanceof Water){ + $tlevel = $b->meta & 0x07; + $level = $this->meta & 0x07; + if(($tlevel + 1) == $level || ($side == 0x01 && $level == 0x01)){ + return $b; + } + } + } + + return null; + } + + public function onUpdate($type){ + //return false; + $newId = $this->id; + $level = $this->meta & 0x07; + if($type !== Level::BLOCK_UPDATE_NORMAL){ + return false; + } + + $this->checkLava(); + + $falling = $this->meta >> 3; + $down = $this->getSide(0); + + $from = $this->getFrom(); + //Has Source or Its Source + if($from !== null || $level == 0x00){ + if($level !== 0x07){ + if($down instanceof Air || $down instanceof Water){ + $this->level->setBlock($down, new Water(0x01), false, false, true); + Server::getInstance()->api->block->scheduleBlockUpdate(Position::fromObject($down, $this->level), 10, Level::BLOCK_UPDATE_NORMAL); + }else{ + for($side = 2; $side <= 5; ++$side){ + $b = $this->getSide($side); + if($b instanceof Water){ + if($this->getSourceCount() >= 2 && $level != 0x00){ + $this->level->setBlock($this, new Water(0), false, false, true); + } + }elseif($b->isFlowable === true){ + $this->level->setBlock($b, new Water($level + 1), false, false, true); + Server::getInstance()->api->block->scheduleBlockUpdate(Position::fromObject($b, $this->level), 10, Level::BLOCK_UPDATE_NORMAL); + } + } + } + } + }else{ + //Extend Remove for Left Waters + for($side = 2; $side <= 5; ++$side){ + $sb = $this->getSide($side); + if($sb instanceof Water){ + $tlevel = $sb->meta & 0x07; + if($tlevel != 0x00){ + for($s = 0; $s <= 5; $s++){ + $ssb = $sb->getSide($s); + Server::getInstance()->api->block->scheduleBlockUpdate(Position::fromObject($ssb, $this->level), 10, Level::BLOCK_UPDATE_NORMAL); + } + $this->level->setBlock($sb, new Air(), false, false, true); + } + } + $b = $this->getSide(0)->getSide($side); + if($b instanceof Water){ + $tlevel = $b->meta & 0x07; + if($tlevel != 0x00){ + for($s = 0; $s <= 5; $s++){ + $ssb = $sb->getSide($s); + Server::getInstance()->api->block->scheduleBlockUpdate(Position::fromObject($ssb, $this->level), 10, Level::BLOCK_UPDATE_NORMAL); + } + $this->level->setBlock($b, new Air(), false, false, true); + } + } + //Server::getInstance()->api->block->scheduleBlockUpdate(Position::fromObject($b, $this->level), 10, Level::BLOCK_UPDATE_NORMAL); + } + $this->level->setBlock($this, new Air(), false, false, true); + } + + return false; + } +} diff --git a/src/pocketmine/block/Wheat.php b/src/pocketmine/block/Wheat.php new file mode 100644 index 000000000..0f6119047 --- /dev/null +++ b/src/pocketmine/block/Wheat.php @@ -0,0 +1,92 @@ +isActivable = true; + $this->hardness = 0; + } + + public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ + $down = $this->getSide(0); + if($down->getID() === self::FARMLAND){ + $this->level->setBlock($block, $this, true, false, true); + + return true; + } + + return false; + } + + public function onActivate(Item $item, Player $player = null){ + if($item->getID() === Item::DYE and $item->getMetadata() === 0x0F){ //Bonemeal + $this->meta = 0x07; + $this->level->setBlock($this, $this, true, false, true); + if(($player->gamemode & 0x01) === 0){ + $item->count--; + } + + return true; + } + + return false; + } + + public function onUpdate($type){ + if($type === Level::BLOCK_UPDATE_NORMAL){ + if($this->getSide(0)->isTransparent === true){ //Replace with common break method + //TODO + //Server::getInstance()->api->entity->drop($this, Item::get(WHEAT_SEEDS, 0, 1)); + $this->level->setBlock($this, new Air(), false, false, true); + + return Level::BLOCK_UPDATE_NORMAL; + } + }elseif($type === Level::BLOCK_UPDATE_RANDOM){ + if(mt_rand(0, 2) == 1){ + if($this->meta < 0x07){ + ++$this->meta; + $this->level->setBlock($this, $this, true, false, true); + } + } + } + + return false; + } + + public function getDrops(Item $item){ + $drops = array(); + if($this->meta >= 0x07){ + $drops[] = array(Item::WHEAT, 0, 1); + $drops[] = array(Item::WHEAT_SEEDS, 0, mt_rand(0, 3)); + }else{ + $drops[] = array(Item::WHEAT_SEEDS, 0, 1); + } + + return $drops; + } +} \ No newline at end of file diff --git a/src/pocketmine/block/Wood.php b/src/pocketmine/block/Wood.php new file mode 100644 index 000000000..b3229fcfe --- /dev/null +++ b/src/pocketmine/block/Wood.php @@ -0,0 +1,66 @@ + "Oak Wood", + self::SPRUCE => "Spruce Wood", + self::BIRCH => "Birch Wood", + self::JUNGLE => "Jungle Wood", + ); + $this->name = $names[$this->meta & 0x03]; + $this->hardness = 10; + } + + public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ + $faces = array( + 0 => 0, + 1 => 0, + 2 => 0b1000, + 3 => 0b1000, + 4 => 0b0100, + 5 => 0b0100, + ); + + $this->meta = ($this->meta & 0x03) | $faces[$face]; + $this->level->setBlock($block, $this, true, false, true); + + return true; + } + + public function getDrops(Item $item){ + return array( + array($this->id, $this->meta & 0x03, 1), + ); + } +} \ No newline at end of file diff --git a/src/pocketmine/block/WoodDoor.php b/src/pocketmine/block/WoodDoor.php new file mode 100644 index 000000000..2041be6b5 --- /dev/null +++ b/src/pocketmine/block/WoodDoor.php @@ -0,0 +1,38 @@ +isActivable = true; + $this->hardness = 15; + } + + public function getDrops(Item $item){ + return array( + array(Item::WOODEN_DOOR, 0, 1), + ); + } +} \ No newline at end of file diff --git a/src/pocketmine/block/WoodSlab.php b/src/pocketmine/block/WoodSlab.php new file mode 100644 index 000000000..2167b6108 --- /dev/null +++ b/src/pocketmine/block/WoodSlab.php @@ -0,0 +1,117 @@ + "Oak", + 1 => "Spruce", + 2 => "Birch", + 3 => "Jungle", + ); + $this->name = (($this->meta & 0x08) === 0x08 ? "Upper " : "") . $names[$this->meta & 0x07] . " Wooden Slab"; + if(($this->meta & 0x08) === 0x08){ + $this->isFullBlock = true; + }else{ + $this->isFullBlock = false; + } + $this->hardness = 15; + } + + public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ + $this->meta &= 0x07; + if($face === 0){ + if($target->getID() === self::WOOD_SLAB and ($target->getMetadata() & 0x08) === 0x08 and ($target->getMetadata() & 0x07) === ($this->meta & 0x07)){ + $this->level->setBlock($target, Block::get(Item::DOUBLE_WOOD_SLAB, $this->meta), true, false, true); + + return true; + }elseif($block->getID() === self::WOOD_SLAB and ($block->getMetadata() & 0x07) === ($this->meta & 0x07)){ + $this->level->setBlock($block, Block::get(Item::DOUBLE_WOOD_SLAB, $this->meta), true, false, true); + + return true; + }else{ + $this->meta |= 0x08; + } + }elseif($face === 1){ + if($target->getID() === self::WOOD_SLAB and ($target->getMetadata() & 0x08) === 0 and ($target->getMetadata() & 0x07) === ($this->meta & 0x07)){ + $this->level->setBlock($target, Block::get(Item::DOUBLE_WOOD_SLAB, $this->meta), true, false, true); + + return true; + }elseif($block->getID() === self::WOOD_SLAB and ($block->getMetadata() & 0x07) === ($this->meta & 0x07)){ + $this->level->setBlock($block, Block::get(Item::DOUBLE_WOOD_SLAB, $this->meta), true, false, true); + + return true; + } + }elseif(!($player instanceof Player) or !$player->inBlock($block)){ + if($block->getID() === self::WOOD_SLAB){ + if(($block->getMetadata() & 0x07) === ($this->meta & 0x07)){ + $this->level->setBlock($block, Block::get(Item::DOUBLE_WOOD_SLAB, $this->meta), true, false, true); + + return true; + } + + return false; + }else{ + if($fy > 0.5){ + $this->meta |= 0x08; + } + } + }else{ + return false; + } + if($block->getID() === self::WOOD_SLAB and ($target->getMetadata() & 0x07) !== ($this->meta & 0x07)){ + return false; + } + $this->level->setBlock($block, $this, true, false, true); + + return true; + } + + public function getBreakTime(Item $item){ + + switch($item->isAxe()){ + case 5: + return 0.4; + case 4: + return 0.5; + case 3: + return 0.75; + case 2: + return 0.25; + case 1: + return 1.5; + default: + return 3; + } + } + + public function getDrops(Item $item){ + return array( + array($this->id, $this->meta & 0x07, 1), + ); + } +} \ No newline at end of file diff --git a/src/pocketmine/block/WoodStairs.php b/src/pocketmine/block/WoodStairs.php new file mode 100644 index 000000000..4efe0e701 --- /dev/null +++ b/src/pocketmine/block/WoodStairs.php @@ -0,0 +1,54 @@ +isAxe()){ + case 5: + return 0.4; + case 4: + return 0.5; + case 3: + return 0.75; + case 2: + return 0.25; + case 1: + return 1.5; + default: + return 3; + } + } + + public function getDrops(Item $item){ + return array( + array($this->id, 0, 1), + ); + } +} \ No newline at end of file diff --git a/src/pocketmine/block/Wool.php b/src/pocketmine/block/Wool.php new file mode 100644 index 000000000..0285e0ef0 --- /dev/null +++ b/src/pocketmine/block/Wool.php @@ -0,0 +1,50 @@ + "White Wool", + 1 => "Orange Wool", + 2 => "Magenta Wool", + 3 => "Light Blue Wool", + 4 => "Yellow Wool", + 5 => "Lime Wool", + 6 => "Pink Wool", + 7 => "Gray Wool", + 8 => "Light Gray Wool", + 9 => "Cyan Wool", + 10 => "Purple Wool", + 11 => "Blue Wool", + 12 => "Brown Wool", + 13 => "Green Wool", + 14 => "Red Wool", + 15 => "Black Wool", + ); + $this->name = $names[$this->meta]; + $this->hardness = 4; + } + +} \ No newline at end of file diff --git a/src/pocketmine/block/Workbench.php b/src/pocketmine/block/Workbench.php new file mode 100644 index 000000000..da5ffda3d --- /dev/null +++ b/src/pocketmine/block/Workbench.php @@ -0,0 +1,45 @@ +isActivable = true; + $this->hardness = 15; + } + + public function onActivate(Item $item, Player $player = null){ + $player->toCraft[-1] = 1; + + return true; + } + + public function getDrops(Item $item){ + return array( + array($this->id, 0, 1), + ); + } +} \ No newline at end of file diff --git a/src/pocketmine/command/Command.php b/src/pocketmine/command/Command.php new file mode 100644 index 000000000..e04959bcb --- /dev/null +++ b/src/pocketmine/command/Command.php @@ -0,0 +1,304 @@ +name = $name; + $this->nextLabel = $name; + $this->label = $name; + $this->description = $description; + $this->usageMessage = $usageMessage === null ? "/" . $name : $usageMessage; + $this->aliases = $aliases; + $this->activeAliases = (array) $aliases; + } + + /** + * @param CommandSender $sender + * @param string $commandLabel + * @param string[] $args + * + * @return mixed + */ + public abstract function execute(CommandSender $sender, $commandLabel, array $args); + + /** + * @return string + */ + public function getName(){ + return $this->name; + } + + /** + * @return string + */ + public function getPermission(){ + return $this->permission; + } + + /** + * @param string|null $permission + */ + public function setPermission($permission){ + $this->permission = $permission; + } + + /** + * @param CommandSender $target + * + * @return bool + */ + public function testPermission(CommandSender $target){ + if($this->testPermissionSilent($target)){ + return true; + } + + if($this->permissionMessage === null){ + $target->sendMessage(TextFormat::RED . "You don't have permissions to use this command."); + }elseif($this->permissionMessage !== ""){ + $target->sendMessage(str_replace("", $this->permission, $this->permissionMessage)); + } + + return false; + } + + /** + * @param CommandSender $target + * + * @return bool + */ + public function testPermissionSilent(CommandSender $target){ + if($this->permission === null or $this->permission === ""){ + return true; + } + + foreach(explode(";", $this->permission) as $permission){ + if($target->hasPermission($permission)){ + return true; + } + } + + return false; + } + + /** + * @return string + */ + public function getLabel(){ + return $this->label; + } + + public function setLabel($name){ + $this->nextLabel = $name; + if(!$this->isRegistered()){ + $this->label = $name; + + return true; + } + + return false; + } + + /** + * Registers the command into a Command map + * + * @param CommandMap $commandMap + * + * @return bool + */ + public function register(CommandMap $commandMap){ + if($this->allowChangesFrom($commandMap)){ + $this->commandMap = $commandMap; + + return true; + } + + return false; + } + + /** + * @param CommandMap $commandMap + * + * @return bool + */ + public function unregister(CommandMap $commandMap){ + if($this->allowChangesFrom($commandMap)){ + $this->commandMap = null; + $this->activeAliases = $this->aliases; + $this->label = $this->nextLabel; + + return true; + } + + return false; + } + + /** + * @param CommandMap $commandMap + * + * @return bool + */ + private function allowChangesFrom(CommandMap $commandMap){ + return $this->commandMap === null or $this->commandMap === $commandMap; + } + + /** + * @return bool + */ + public function isRegistered(){ + return $this->commandMap !== null; + } + + /** + * @return string[] + */ + public function getAliases(){ + return $this->activeAliases; + } + + /** + * @return string + */ + public function getPermissionMessage(){ + return $this->permissionMessage; + } + + /** + * @return string + */ + public function getDescription(){ + return $this->description; + } + + /** + * @return string + */ + public function getUsage(){ + return $this->usageMessage; + } + + /** + * @param string[] $aliases + */ + public function setAliases(array $aliases){ + $this->aliases = $aliases; + if(!$this->isRegistered()){ + $this->activeAliases = (array) $aliases; + } + } + + /** + * @param string $description + */ + public function setDescription($description){ + $this->description = $description; + } + + /** + * @param string $permissionMessage + */ + public function setPermissionMessage($permissionMessage){ + $this->permissionMessage = $permissionMessage; + } + + /** + * @param string $usage + */ + public function setUsage($usage){ + $this->usageMessage = $usage; + } + + /** + * TODO: static::broadcastCommandMessage() + * + * @param CommandSender $source + * @param string $message + * @param bool $sendToSource + */ + public static function broadcastCommandMessage(CommandSender $source, $message, $sendToSource = true){ + $result = $source->getName() . ": " . $message; + + //Command minecarts or command blocks are not implemented + + $users = Server::getInstance()->getPluginManager()->getPermissionSubscriptions(Server::BROADCAST_CHANNEL_ADMINISTRATIVE); + $colored = TextFormat::GRAY . TextFormat::ITALIC . "[$result" . TextFormat::GRAY . TextFormat::ITALIC . "]"; + if($sendToSource === true and !($source instanceof ConsoleCommandSender)){ + $source->sendMessage($message); + } + + foreach($users as $user){ + if($user instanceof CommandSender){ + if($user instanceof ConsoleCommandSender){ + $user->sendMessage($result); + }elseif($user !== $source){ + $user->sendMessage($colored); + } + } + } + } +} \ No newline at end of file diff --git a/src/pocketmine/command/CommandExecutor.php b/src/pocketmine/command/CommandExecutor.php new file mode 100644 index 000000000..4e7dcabc6 --- /dev/null +++ b/src/pocketmine/command/CommandExecutor.php @@ -0,0 +1,37 @@ +stream = $stream; + $this->start(PTHREADS_INHERIT_ALL & ~PTHREADS_INHERIT_CLASSES); + } + + private function readLine(){ + if(!$this->readline){ + $line = trim(fgets($this->fp)); + }else{ + $line = trim(readline("> ")); + if($line != ""){ + readline_add_history($line); + } + } + + return $line; + } + + /** + * Reads a line from console, if available. Returns null if not available + * + * @return string|null + */ + public function getLine(){ + if($this->buffer->count() !== 0){ + return $this->buffer->synchronized(function (){ + return $this->buffer->shift(); + }); + } + + return null; + } + + public function run(){ + $this->buffer = new \Threaded; + if(extension_loaded("readline") and $this->stream === "php://stdin"){ + $this->readline = true; + }else{ + $this->readline = false; + $this->fp = fopen($this->stream, "r"); + stream_set_blocking($this->fp, 1); //Non-blocking STDIN won't work on Windows + } + + $lastLine = microtime(true); + while(true){ + if(($line = $this->readLine()) !== ""){ + $this->buffer->synchronized(function (\Threaded $buffer, $line){ + $buffer[] = preg_replace("#\\x1b\\x5b([^\\x1b]*\\x7e|[\\x40-\\x50])#", "", $line); + }, $this->buffer, $line); + $lastLine = microtime(true); + }elseif((microtime(true) - $lastLine) <= 0.1){ //Non blocking! Sleep to save CPU + usleep(40000); + } + } + } +} \ No newline at end of file diff --git a/src/pocketmine/command/CommandSender.php b/src/pocketmine/command/CommandSender.php new file mode 100644 index 000000000..3b03cb063 --- /dev/null +++ b/src/pocketmine/command/CommandSender.php @@ -0,0 +1,44 @@ +perm = new PermissibleBase($this); + } + + /** + * @param \pocketmine\permission\Permission|string $name + * + * @return bool + */ + public function isPermissionSet($name){ + return $this->perm->isPermissionSet($name); + } + + /** + * @param \pocketmine\permission\Permission|string $name + * + * @return bool + */ + public function hasPermission($name){ + return $this->perm->hasPermission($name); + } + + /** + * @param Plugin $plugin + * @param string $name + * @param bool $value + * + * @return \pocketmine\permission\PermissionAttachment + */ + public function addAttachment(Plugin $plugin, $name = null, $value = null){ + return $this->perm->addAttachment($plugin, $name, $value); + } + + /** + * @param PermissionAttachment $attachment + * + * @return void + */ + public function removeAttachment(PermissionAttachment $attachment){ + $this->perm->removeAttachment($attachment); + } + + public function recalculatePermissions(){ + $this->perm->recalculatePermissions(); + } + + /** + * @return \pocketmine\permission\PermissionAttachmentInfo[] + */ + public function getEffectivePermissions(){ + return $this->perm->getEffectivePermissions(); + } + + /** + * @return bool + */ + public function isPlayer(){ + return false; + } + + /** + * @return \pocketmine\Server + */ + public function getServer(){ + return Server::getInstance(); + } + + /** + * @param string $message + */ + public function sendMessage($message){ + foreach(explode("\n", trim($message)) as $line){ + $line = trim($line); + console($line); + } + } + + /** + * @return string + */ + public function getName(){ + return "CONSOLE"; + } + + /** + * @return bool + */ + public function isOp(){ + return true; + } + + /** + * @param bool $value + */ + public function setOp($value){ + + } + +} \ No newline at end of file diff --git a/src/pocketmine/command/PluginCommand.php b/src/pocketmine/command/PluginCommand.php new file mode 100644 index 000000000..daa7a2c79 --- /dev/null +++ b/src/pocketmine/command/PluginCommand.php @@ -0,0 +1,71 @@ +owningPlugin = $owner; + $this->executor = $owner; + $this->usageMessage = ""; + } + + public function execute(CommandSender $sender, $commandLabel, array $args){ + + if(!$this->owningPlugin->isEnabled()){ + return false; + } + + if(!$this->testPermission($sender)){ + return false; + } + + $success = $this->executor->onCommand($sender, $this, $commandLabel, $args); + + if(!$success and $this->usageMessage !== ""){ + $sender->sendMessage(TextFormat::RED . "Usage: ". $this->usageMessage); + } + + return $success; + } + + /** + * @return Plugin + */ + public function getPlugin(){ + return $this->owningPlugin; + } +} \ No newline at end of file diff --git a/src/pocketmine/command/RemoteConsoleCommandSender.php b/src/pocketmine/command/RemoteConsoleCommandSender.php new file mode 100644 index 000000000..8d537a0b7 --- /dev/null +++ b/src/pocketmine/command/RemoteConsoleCommandSender.php @@ -0,0 +1,27 @@ +server = $server; + $this->setDefaultCommands(); + } + + private function setDefaultCommands(){ + $this->register("pocketmine", new VersionCommand("version")); + $this->register("pocketmine", new PluginsCommand("plugins")); + $this->register("pocketmine", new SeedCommand("seed")); + $this->register("pocketmine", new HelpCommand("help")); + $this->register("pocketmine", new StopCommand("stop")); + $this->register("pocketmine", new TellCommand("tell")); + $this->register("pocketmine", new DefaultGamemodeCommand("defaultgamemode")); + $this->register("pocketmine", new BanCommand("ban")); + $this->register("pocketmine", new BanIpCommand("ban-ip")); + $this->register("pocketmine", new BanListCommand("banlist")); + $this->register("pocketmine", new PardonCommand("pardon")); + $this->register("pocketmine", new PardonIpCommand("pardon-ip")); + $this->register("pocketmine", new SayCommand("say")); + $this->register("pocketmine", new MeCommand("me")); + $this->register("pocketmine", new ListCommand("list")); + $this->register("pocketmine", new DifficultyCommand("difficulty")); + } + + + public function registerAll($fallbackPrefix, array $commands){ + foreach($commands as $command){ + $this->register($fallbackPrefix, $command); + } + } + + public function register($fallbackPrefix, Command $command, $label = null){ + if($label === null){ + $label = $command->getName(); + } + $label = strtolower(trim($label)); + $fallbackPrefix = strtolower(trim($fallbackPrefix)); + + $registered = $this->registerAlias($command, false, $fallbackPrefix, $label); + + $aliases = $command->getAliases(); + foreach($aliases as $index => $alias){ + if(!$this->registerAlias($command, true, $fallbackPrefix, $alias)){ + unset($aliases[$index]); + } + } + $command->setAliases($aliases); + + if(!$registered){ + $command->setLabel($fallbackPrefix . ":" . $label); + } + + $command->register($this); + + return $registered; + } + + private function registerAlias(Command $command, $isAlias, $fallbackPrefix, $label){ + $this->knownCommands[$fallbackPrefix . ":" . $label] = $command; + if(($command instanceof VanillaCommand or $isAlias) and isset($this->knownCommands[$label])){ + return false; + } + + if(isset($this->knownCommands[$label]) and $this->knownCommands[$label]->getLabel() === $label){ + return false; + } + + if(!$isAlias){ + $command->setLabel($label); + } + + $this->knownCommands[$label] = $command; + + return true; + } + + public function dispatch(CommandSender $sender, $commandLine){ + $args = explode(" ", $commandLine); + + if(count($args) === 0){ + return false; + } + + $sentCommandLabel = strtolower(array_shift($args)); + $target = $this->getCommand($sentCommandLabel); + + if($target === null){ + return false; + } + + $target->execute($sender, $sentCommandLabel, $args); + + return true; + } + + public function clearCommands(){ + foreach($this->knownCommands as $command){ + $command->unregister($this); + } + $this->knownCommands = array(); + $this->setDefaultCommands(); + } + + public function getCommand($name){ + if(isset($this->knownCommands[$name])){ + return $this->knownCommands[$name]; + } + + return null; + } + + /** + * @return Command[] + */ + public function getCommands(){ + return $this->knownCommands; + } + + + /** + * @return void + */ + public function registerServerAliases(){ + //TODO + } + + +} \ No newline at end of file diff --git a/src/pocketmine/command/defaults/BanCommand.php b/src/pocketmine/command/defaults/BanCommand.php new file mode 100644 index 000000000..2367a3667 --- /dev/null +++ b/src/pocketmine/command/defaults/BanCommand.php @@ -0,0 +1,65 @@ + [reason...]" + ); + $this->setPermission("pocketmine.command.ban.player"); + } + + public function execute(CommandSender $sender, $currentAlias, array $args){ + if(!$this->testPermission($sender)){ + return true; + } + + if(count($args) === 0){ + $sender->sendMessage(TextFormat::RED . "Usage: " . $this->usageMessage); + + return false; + } + + $name = array_shift($args); + $reason = implode(" ", $args); + + Server::getInstance()->getNameBans()->addBan($name, $reason, null, $sender->getName()); + + if(($player = Player::get($name, true)) instanceof Player){ + $player->kick("Banned by admin."); + } + + Command::broadcastCommandMessage($sender, "Banned player " . $name); + + return true; + } +} \ No newline at end of file diff --git a/src/pocketmine/command/defaults/BanIpCommand.php b/src/pocketmine/command/defaults/BanIpCommand.php new file mode 100644 index 000000000..5aaa7e92f --- /dev/null +++ b/src/pocketmine/command/defaults/BanIpCommand.php @@ -0,0 +1,81 @@ + [reason...]" + ); + $this->setPermission("pocketmine.command.ban.ip"); + } + + public function execute(CommandSender $sender, $currentAlias, array $args){ + if(!$this->testPermission($sender)){ + return true; + } + + if(count($args) === 0){ + $sender->sendMessage(TextFormat::RED . "Usage: " . $this->usageMessage); + + return false; + } + + $value = array_shift($args); + $reason = implode(" ", $args); + + if(preg_match("/^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])$/", $value)){ + $this->processIPBan($value, $sender, $reason); + }else{ + if(($player = Player::get($value, true)) instanceof Player){ + $this->processIPBan($player->getAddress(), $sender, $reason); + }else{ + $sender->sendMessage(TextFormat::RED . "Usage: " . $this->usageMessage); + + return false; + } + } + + return true; + } + + private function processIPBan($ip, CommandSender $sender, $reason){ + Server::getInstance()->getIPBans()->addBan($ip, $reason, null, $sender->getName()); + + foreach(Player::getAll() as $player){ + if($player->getAddress() === $ip){ + $player->kick("You have been IP banned."); + } + } + + Command::broadcastCommandMessage($sender, "Banned IP Address " . $ip); + } +} \ No newline at end of file diff --git a/src/pocketmine/command/defaults/BanListCommand.php b/src/pocketmine/command/defaults/BanListCommand.php new file mode 100644 index 000000000..3a7472b8a --- /dev/null +++ b/src/pocketmine/command/defaults/BanListCommand.php @@ -0,0 +1,63 @@ +setPermission("pocketmine.command.ban.list"); + } + + public function execute(CommandSender $sender, $currentAlias, array $args){ + if(!$this->testPermission($sender)){ + return true; + } + $list = Server::getInstance()->getNameBans(); + if(isset($args[0])){ + $args[0] = strtolower($args[0]); + if($args[0] === "ips"){ + $list = Server::getInstance()->getIPBans(); + }elseif($args[0] === "players"){ + $list = Server::getInstance()->getNameBans(); + } + } + + $message = ""; + $list = $list->getEntries(); + foreach($list as $entry){ + $message .= $entry->getName() . ", "; + } + + $sender->sendMessage("There are " . count($list) . " total banned players:"); + $sender->sendMessage(substr($message, 0, -2)); + + return true; + } +} \ No newline at end of file diff --git a/src/pocketmine/command/defaults/DefaultGamemodeCommand.php b/src/pocketmine/command/defaults/DefaultGamemodeCommand.php new file mode 100644 index 000000000..7c20da1cf --- /dev/null +++ b/src/pocketmine/command/defaults/DefaultGamemodeCommand.php @@ -0,0 +1,61 @@ +" + ); + $this->setPermission("pocketmine.command.defaultgamemode"); + } + + public function execute(CommandSender $sender, $currentAlias, array $args){ + if(!$this->testPermission($sender)){ + return true; + } + + if(count($args) === 0){ + $sender->sendMessage(TextFormat::RED . "Usage: " . $this->usageMessage); + + return false; + } + + $gameMode = Server::getGamemodeFromString($args[0]); + + if($gameMode !== -1){ + Server::getInstance()->setConfigInt("gamemode", $gameMode); + $sender->sendMessage("Default game mode set to " . strtolower(Server::getGamemodeString($gameMode))); + }else{ + $sender->sendMessage("Unknown game mode"); + } + + return true; + } +} \ No newline at end of file diff --git a/src/pocketmine/command/defaults/DifficultyCommand.php b/src/pocketmine/command/defaults/DifficultyCommand.php new file mode 100644 index 000000000..c907f1a18 --- /dev/null +++ b/src/pocketmine/command/defaults/DifficultyCommand.php @@ -0,0 +1,65 @@ +" + ); + $this->setPermission("pocketmine.command.difficulty"); + } + + public function execute(CommandSender $sender, $currentAlias, array $args){ + if(!$this->testPermission($sender)){ + return true; + } + + if(count($args) !== 1){ + $sender->sendMessage(TextFormat::RED . "Usage: " . $this->usageMessage); + + return false; + } + + $difficulty = Server::getDifficultyFromString($args[0]); + + if(Server::getInstance()->isHardcore()){ + $difficulty = 3; + } + + if($difficulty !== -1){ + Server::getInstance()->setConfigInt("difficulty", $difficulty); + $sender->sendMessage("Set difficulty to " . $difficulty); + }else{ + $sender->sendMessage("Unknown difficulty"); + } + + return true; + } +} \ No newline at end of file diff --git a/src/pocketmine/command/defaults/HelpCommand.php b/src/pocketmine/command/defaults/HelpCommand.php new file mode 100644 index 000000000..ca0e98daf --- /dev/null +++ b/src/pocketmine/command/defaults/HelpCommand.php @@ -0,0 +1,106 @@ + [pageNumber]", + ["?"] + ); + $this->setPermission("pocketmine.command.help"); + } + + public function execute(CommandSender $sender, $currentAlias, array $args){ + if(!$this->testPermission($sender)){ + return true; + } + + if(count($args) === 0){ + $command = ""; + $pageNumber = 1; + }elseif(is_numeric($args[count($args) - 1])){ + $pageNumber = (int) array_pop($args); + if($pageNumber <= 0){ + $pageNumber = 1; + } + $command = implode(" ", $args); + }else{ + $command = implode(" ", $args); + $pageNumber = 1; + } + + if($sender instanceof ConsoleCommandSender){ + $pageHeight = PHP_INT_MAX; + }else{ + $pageHeight = 5; + } + + if($command === ""){ + $commands = array(); + foreach(Server::getInstance()->getCommandMap()->getCommands() as $command){ + if($command->testPermissionSilent($sender)){ + $commands[$command->getName()] = $command; + } + } + ksort($commands, SORT_NATURAL | SORT_FLAG_CASE); + $commands = array_chunk($commands, $pageHeight); + $pageNumber = (int) min(count($commands), $pageNumber); + if($pageNumber < 1){ + $pageNumber = 1; + } + $message = TextFormat::RED . "-" . TextFormat::RESET . " Showing help page " . $pageNumber . " of " . count($commands) . " (/help ) " . TextFormat::RED . "-" . TextFormat::RESET . "\n"; + if(isset($commands[$pageNumber - 1])){ + foreach($commands[$pageNumber - 1] as $command){ + $message .= TextFormat::DARK_GREEN . "/" . $command->getName() . ": " . TextFormat::WHITE . $command->getDescription() . "\n"; + } + } + $sender->sendMessage($message); + + return true; + }else{ + if(($command = Server::getInstance()->getCommandMap()->getCommand(strtolower($command))) instanceof Command){ + if($command->testPermissionSilent($sender)){ + $message = TextFormat::YELLOW . "--------- " . TextFormat::WHITE . " Help: /" . $command->getName() . TextFormat::YELLOW . " ---------\n"; + $message .= TextFormat::GOLD . "Description: " . TextFormat::WHITE . $command->getDescription() . "\n"; + $message .= TextFormat::GOLD . "Usage: " . TextFormat::WHITE . implode("\n" . TextFormat::WHITE, explode("\n", $command->getUsage())) . "\n"; + $sender->sendMessage($message); + + return true; + } + } + $sender->sendMessage(TextFormat::RED . "No help for " . strtolower($command)); + + return true; + } + } + +} \ No newline at end of file diff --git a/src/pocketmine/command/defaults/ListCommand.php b/src/pocketmine/command/defaults/ListCommand.php new file mode 100644 index 000000000..0db5fdc8e --- /dev/null +++ b/src/pocketmine/command/defaults/ListCommand.php @@ -0,0 +1,58 @@ +setPermission("pocketmine.command.list"); + } + + public function execute(CommandSender $sender, $currentAlias, array $args){ + if(!$this->testPermission($sender)){ + return true; + } + + $online = ""; + + foreach(Player::getAll() as $player){ + if($player->isOnline() and (!($sender instanceof Player) or $sender->canSee($player))){ + $online .= $player->getDisplayName() . ", "; + } + } + + $sender->sendMessage("There are ".count(Player::getAll())."/".Server::getInstance()->getMaxPlayers()." players online:\n" . substr($online, 0, -2)); + + return true; + } +} \ No newline at end of file diff --git a/src/pocketmine/command/defaults/MeCommand.php b/src/pocketmine/command/defaults/MeCommand.php new file mode 100644 index 000000000..232511c42 --- /dev/null +++ b/src/pocketmine/command/defaults/MeCommand.php @@ -0,0 +1,62 @@ +" + ); + $this->setPermission("pocketmine.command.me"); + } + + public function execute(CommandSender $sender, $currentAlias, array $args){ + if(!$this->testPermission($sender)){ + return true; + } + + if(count($args) === 0){ + $sender->sendMessage(TextFormat::RED . "Usage: " . $this->usageMessage); + + return false; + } + + $message = "* "; + if($sender instanceof Player){ + $message .= $sender->getDisplayName(); + }else{ + $message .= $sender->getName(); + } + + Server::getInstance()->broadcastMessage($message . " " . implode(" ", $args)); + + return true; + } +} \ No newline at end of file diff --git a/src/pocketmine/command/defaults/PardonCommand.php b/src/pocketmine/command/defaults/PardonCommand.php new file mode 100644 index 000000000..914290c55 --- /dev/null +++ b/src/pocketmine/command/defaults/PardonCommand.php @@ -0,0 +1,57 @@ +" + ); + $this->setPermission("pocketmine.command.unban.player"); + } + + public function execute(CommandSender $sender, $currentAlias, array $args){ + if(!$this->testPermission($sender)){ + return true; + } + + if(count($args) !== 1){ + $sender->sendMessage(TextFormat::RED . "Usage: " . $this->usageMessage); + + return false; + } + + Server::getInstance()->getNameBans()->remove($args[0]); + + Command::broadcastCommandMessage($sender, "Pardoned " . $name); + + return true; + } +} \ No newline at end of file diff --git a/src/pocketmine/command/defaults/PardonIpCommand.php b/src/pocketmine/command/defaults/PardonIpCommand.php new file mode 100644 index 000000000..44f693a57 --- /dev/null +++ b/src/pocketmine/command/defaults/PardonIpCommand.php @@ -0,0 +1,60 @@ +" + ); + $this->setPermission("pocketmine.command.unban.ip"); + } + + public function execute(CommandSender $sender, $currentAlias, array $args){ + if(!$this->testPermission($sender)){ + return true; + } + + if(count($args) !== 1){ + $sender->sendMessage(TextFormat::RED . "Usage: " . $this->usageMessage); + + return false; + } + + if(preg_match("/^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])$/", $args[0])){ + Server::getInstance()->getIPBans()->remove($args[0]); + Command::broadcastCommandMessage($sender, "Pardoned IP " . $args[0]); + }else{ + $sender->sendMessage("Invalid IP"); + } + + return true; + } +} \ No newline at end of file diff --git a/src/pocketmine/command/defaults/PluginsCommand.php b/src/pocketmine/command/defaults/PluginsCommand.php new file mode 100644 index 000000000..6b97c7fe4 --- /dev/null +++ b/src/pocketmine/command/defaults/PluginsCommand.php @@ -0,0 +1,62 @@ +setPermission("pocketmine.command.plugins"); + } + + public function execute(CommandSender $sender, $currentAlias, array $args){ + if(!$this->testPermission($sender)){ + return true; + } + + $sender->sendMessage("Plugins " . $this->getPluginList()); + + return true; + } + + private function getPluginList(){ + $list = ""; + foreach(($plugins = Server::getInstance()->getPluginManager()->getPlugins()) as $plugin){ + if(strlen($list) > 0){ + $list .= TextFormat::WHITE . ", "; + } + $list .= $plugin->isEnabled() ? TextFormat::DARK_GREEN : TextFormat::RED; + $list .= $plugin->getDescription()->getName(); + } + + return "(" . count($plugins) . "): $list"; + } +} \ No newline at end of file diff --git a/src/pocketmine/command/defaults/SayCommand.php b/src/pocketmine/command/defaults/SayCommand.php new file mode 100644 index 000000000..710aac1f9 --- /dev/null +++ b/src/pocketmine/command/defaults/SayCommand.php @@ -0,0 +1,65 @@ +" + ); + $this->setPermission("pocketmine.command.seed"); + } + + public function execute(CommandSender $sender, $currentAlias, array $args){ + if(!$this->testPermission($sender)){ + return true; + } + + if(count($args) === 0){ + $sender->sendMessage(TextFormat::RED . "Usage: " . $this->usageMessage); + + return false; + } + + $message = TextFormat::LIGHT_PURPLE . "["; + if($sender instanceof ConsoleCommandSender){ + $message .= "Server"; + }elseif($sender instanceof Player){ + $message .= $sender->getDisplayName(); + }else{ + $message .= $sender->getName(); + } + $message .= TextFormat::LIGHT_PURPLE . "] " . implode(" ", $args); + Server::getInstance()->broadcastMessage($message); + + return true; + } +} \ No newline at end of file diff --git a/src/pocketmine/command/defaults/SeedCommand.php b/src/pocketmine/command/defaults/SeedCommand.php new file mode 100644 index 000000000..2cfd38f4e --- /dev/null +++ b/src/pocketmine/command/defaults/SeedCommand.php @@ -0,0 +1,53 @@ +setPermission("pocketmine.command.seed"); + } + + public function execute(CommandSender $sender, $currentAlias, array $args){ + if(!$this->testPermission($sender)){ + return true; + } + + if($sender instanceof Player){ + $seed = $sender->getLevel()->getSeed(); + }else{ + $seed = Level::getDefault()->getSeed(); + } + $sender->sendMessage("Seed: " . $seed); + + return true; + } +} \ No newline at end of file diff --git a/src/pocketmine/command/defaults/StopCommand.php b/src/pocketmine/command/defaults/StopCommand.php new file mode 100644 index 000000000..ff867749d --- /dev/null +++ b/src/pocketmine/command/defaults/StopCommand.php @@ -0,0 +1,58 @@ +setPermission("pocketmine.command.stop"); + } + + public function execute(CommandSender $sender, $currentAlias, array $args){ + if(!$this->testPermission($sender)){ + return true; + } + + Command::broadcastCommandMessage($sender, "Stopping the server..."); + + $reason = implode(" ", $args); + if($reason !== ""){ + foreach(Player::getAll() as $player){ + $player->kick($reason); + } + } + + Server::getInstance()->shutdown(); + + return true; + } +} \ No newline at end of file diff --git a/src/pocketmine/command/defaults/TellCommand.php b/src/pocketmine/command/defaults/TellCommand.php new file mode 100644 index 000000000..7b5825ef5 --- /dev/null +++ b/src/pocketmine/command/defaults/TellCommand.php @@ -0,0 +1,64 @@ + ", + ["w", "msg"] + ); + $this->setPermission("pocketmine.command.tell"); + } + + public function execute(CommandSender $sender, $currentAlias, array $args){ + if(!$this->testPermission($sender)){ + return true; + } + + if(count($args) < 2){ + $sender->sendMessage(TextFormat::RED . "Usage: " . $this->usageMessage); + + return false; + } + + $name = strtolower(array_shift($args)); + + $player = Player::get($name, true, false); + + if($player instanceof Player){ + $sender->sendMessage("[me -> " . $player->getName() . "] " . implode($args)); + $player->sendMessage("[" . $sender->getName() . " -> me] " . implode($args)); + }else{ + $sender->sendMessage("There's no player by that name online."); + } + + return true; + } +} \ No newline at end of file diff --git a/src/pocketmine/command/defaults/VanillaCommand.php b/src/pocketmine/command/defaults/VanillaCommand.php new file mode 100644 index 000000000..2c5546872 --- /dev/null +++ b/src/pocketmine/command/defaults/VanillaCommand.php @@ -0,0 +1,69 @@ + $max){ + $i = $max; + } + + return $i; + } + + protected function getRelativeDouble($original, CommandSender $sender, $input){ + if($input{0} === "~"){ + $value = $this->getDouble($sender, substr($input, 1)); + + return $original + $value; + } + + return $this->getDouble($input); + } + + protected function getDouble(CommandSender $sender, $value, $min = self::MIN_COORD, $max = self::MAX_COORD){ + $i = (double) $value; + + if($i < $min){ + $i = $min; + }elseif($i > $max){ + $i = $max; + } + + return $i; + } +} \ No newline at end of file diff --git a/src/pocketmine/command/defaults/VersionCommand.php b/src/pocketmine/command/defaults/VersionCommand.php new file mode 100644 index 000000000..d92e36b83 --- /dev/null +++ b/src/pocketmine/command/defaults/VersionCommand.php @@ -0,0 +1,100 @@ +setPermission("pocketmine.command.version"); + } + + public function execute(CommandSender $sender, $currentAlias, array $args){ + if(!$this->testPermission($sender)){ + return true; + } + + if(count($args) === 0){ + $output = "This server is running PocketMine-MP version " . Server::getInstance()->getPocketMineVersion() . " 「" . Server::getInstance()->getCodename() . "」 (Implementing API version " . Server::getInstance()->getApiVersion() . " for Minecraft: PE " . Server::getInstance()->getVersion() . " protocol version " . Info::CURRENT_PROTOCOL . ")"; + if(\pocketmine\GIT_COMMIT !== str_repeat("00", 20)){ + $output .= " [git " . \pocketmine\GIT_COMMIT . "]"; + } + $sender->sendMessage($output); + }else{ + $pluginName = implode(" ", $args); + $exactPlugin = Server::getInstance()->getPluginManager()->getPlugin($pluginName); + + if($exactPlugin instanceof Plugin){ + $this->describeToSender($exactPlugin, $sender); + + return true; + } + + $found = false; + $pluginName = strtolower($pluginName); + foreach(Server::getInstance()->getPluginManager()->getPlugins() as $plugin){ + if(stripos($plugin->getName(), $pluginName) !== false){ + $this->describeToSender($plugin, $sender); + $found = true; + } + } + + if(!$found){ + $sender->sendMessage("This server is not running any plugin by that name.\nUse /plugins to get a list of plugins."); + } + } + + return true; + } + + private function describeToSender(Plugin $plugin, CommandSender $sender){ + $desc = $plugin->getDescription(); + $sender->sendMessage(TextFormat::DARK_GREEN . $desc->getName() . TextFormat::WHITE . " version " . TextFormat::DARK_GREEN . $desc->getVersion()); + + if($desc->getDescription() != null){ + $sender->sendMessage($desc->getDescription()); + } + + if($desc->getWebsite() != null){ + $sender->sendMessage("Website: " . $desc->getWebsite()); + } + + if(count($authors = $desc->getAuthors()) > 0){ + if(count($authors) === 1){ + $sender->sendMessage("Author: " . implode(", ", $authors)); + }else{ + $sender->sendMessage("Authors: " . implode(", ", $authors)); + } + } + } +} \ No newline at end of file diff --git a/src/pocketmine/constants/GeneralConstants.php b/src/pocketmine/constants/GeneralConstants.php new file mode 100644 index 000000000..6f1544bf9 --- /dev/null +++ b/src/pocketmine/constants/GeneralConstants.php @@ -0,0 +1,47 @@ +id = Entity::$entityCount++; + $this->justCreated = true; + $this->closed = false; + $this->namedtag = $nbt; + $this->level = $level; + + $this->boundingBox = new AxisAlignedBB(0, 0, 0, 0, 0, 0); + $this->setPositionAndRotation(new Vector3($this->namedtag["Pos"][0], $this->namedtag["Pos"][1], $this->namedtag["Pos"][2]), $this->namedtag->Rotation[0], $this->namedtag->Rotation[1]); + $this->setMotion(new Vector3($this->namedtag["Motion"][0], $this->namedtag["Motion"][1], $this->namedtag["Motion"][2])); + + $this->fallDistance = $this->namedtag["FallDistance"]; + $this->fireTicks = $this->namedtag["Fire"]; + $this->airTicks = $this->namedtag["Air"]; + $this->onGround = $this->namedtag["OnGround"] > 0 ? true : false; + $this->invulnerable = $this->namedtag["Invulnerable"] > 0 ? true : false; + + $index = LevelFormat::getIndex($this->x >> 4, $this->z >> 4); + $this->chunkIndex = $index; + Entity::$list[$this->id] = $this; + $this->level->entities[$this->id] = $this; + $this->level->chunkEntities[$this->chunkIndex][$this->id] = $this; + $this->lastUpdate = microtime(true); + $this->initEntity(); + Server::getInstance()->getPluginManager()->callEvent(new EntitySpawnEvent($this)); + } + + public function saveNBT(){ + $this->namedtag["Pos"][0] = $this->x; + $this->namedtag["Pos"][1] = $this->y; + $this->namedtag["Pos"][2] = $this->z; + + $this->namedtag["Motion"][0] = $this->motionX; + $this->namedtag["Motion"][1] = $this->motionY; + $this->namedtag["Motion"][2] = $this->motionZ; + + $this->namedtag["Rotation"][0] = $this->yaw; + $this->namedtag["Rotation"][1] = $this->pitch; + + $this->namedtag["FallDistance"] = $this->fallDistance; + $this->namedtag["Fire"] = $this->fireTicks; + $this->namedtag["Air"] = $this->airTicks; + $this->namedtag["OnGround"] = $this->onGround == true ? 1 : 0; + $this->namedtag["Invulnerable"] = $this->invulnerable == true ? 1 : 0; + } + + protected abstract function initEntity(); + + public function spawnTo(Player $player){ + if(!isset($this->hasSpawned[$player->getID()]) and $player->chunksLoaded[$this->chunkIndex] !== 0xff){ + $this->hasSpawned[$player->getID()] = $player; + } + } + + public function despawnFrom(Player $player){ + if(isset($this->hasSpawned[$player->getID()])){ + $pk = new RemoveEntityPacket; + $pk->eid = $this->id; + $player->dataPacket($pk); + unset($this->hasSpawned[$player->getID()]); + } + } + + abstract function attack($damage, $source = "generic"); + + abstract function heal($amount, $source = "generic"); + + public function onUpdate(){ + if($this->closed !== false){ + return false; + } + + $timeNow = microtime(true); + $this->ticksLived += ($timeNow - $this->lastUpdate) * 20; + + if($this->handleWaterMovement()){ + $this->fallDistance = 0; + $this->inWater = true; + $this->extinguish(); + }else{ + $this->inWater = false; + } + + if($this->fireTicks > 0){ + if($this->fireProof === true){ + $this->fireTicks -= 4; + if($this->fireTicks < 0){ + $this->fireTicks = 0; + } + }else{ + if(($this->fireTicks % 20) === 0){ + $this->attackEntity(1, "onFire"); + } + --$this->fireTicks; + } + } + + if($this->handleLavaMovement()){ + $this->attackEntity(4, "lava"); + $this->setOnFire(15); + $this->fallDistance *= 0.5; + } + + if($this->y < -64){ + $this->kill(); + } + + if($this->x !== $this->lastX or $this->y !== $this->lastY or $this->z !== $this->lastZ or $this->yaw !== $this->lastYaw or $this->pitch !== $this->lastPitch){ + $this->lastX = $this->x; + $this->lastY = $this->y; + $this->lastZ = $this->z; + + $this->lastYaw = $this->yaw; + $this->lastPitch = $this->pitch; + + if($this instanceof Human){ + $pk = new MovePlayerPacket; + $pk->eid = $this->id; + $pk->x = $this->x; + $pk->y = $this->y; + $pk->z = $this->z; + $pk->yaw = $this->yaw; + $pk->pitch = $this->pitch; + $pk->bodyYaw = $this->yaw; + }else{ + $pk = new MoveEntityPacket_PosRot; + $pk->eid = $this->id; + $pk->x = $this->x; + $pk->y = $this->y; + $pk->z = $this->z; + $pk->yaw = $this->yaw; + $pk->pitch = $this->pitch; + } + Player::broadcastPacket($this->hasSpawned, $pk); + } + + if($this->motionChanged === true){ + $this->motionChanged = false; + + $pk = new SetEntityMotionPacket; + $pk->eid = $this->id; + $pk->speedX = $this->motionX; + $pk->speedY = $this->motionY; + $pk->speedZ = $this->motionZ; + Player::broadcastPacket($this->hasSpawned, $pk); + } + + $this->lastUpdate = $timeNow; + + return false; + } + + public final function scheduleUpdate(){ + Entity::$needUpdate[$this->id] = $this; + } + + public abstract function getMetadata(); + + public function setOnFire($seconds){ + $ticks = $seconds * 20; + if($ticks > $this->fireTicks){ + $this->fireTicks = $ticks; + } + } + + public function getDirection(){ + $rotation = ($this->yaw - 90) % 360; + if($rotation < 0){ + $rotation += 360.0; + } + if((0 <= $rotation and $rotation < 45) or (315 <= $rotation and $rotation < 360)){ + return 2; //North + }elseif(45 <= $rotation and $rotation < 135){ + return 3; //East + }elseif(135 <= $rotation and $rotation < 225){ + return 0; //South + }elseif(225 <= $rotation and $rotation < 315){ + return 1; //West + }else{ + return null; + } + } + + public function extinguish(){ + $this->fireTicks = 0; + } + + public function canTriggerWalking(){ + return true; + } + + protected function updateFallState($distanceThisTick, $onGround){ + if($onGround === true){ + if($this->fallDistance > 0){ + if($this instanceof Living){ + //TODO + } + + $this->fall($this->fallDistance); + $this->fallDistance = 0; + } + }elseif($distanceThisTick < 0){ + $this->fallDistance -= $distanceThisTick; + } + } + + public function getBoundingBox(){ + return $this->boundingBox; + } + + public function fall($fallDistance){ //TODO + + } + + public function handleWaterMovement(){ //TODO + + } + + public function handleLavaMovement(){ //TODO + + } + + public function getEyeHeight(){ + return 0; + } + + public function moveFlying(){ //TODO + + } + + public function onCollideWithPlayer(Human $entityPlayer){ + + } + + protected function switchLevel(Level $targetLevel){ + if($this->level instanceof Level){ + Server::getInstance()->getPluginManager()->callEvent($ev = new EntityLevelChangeEvent($this, $this->level, $targetLevel)); + if($ev->isCancelled()){ + return false; + } + unset($this->level->entities[$this->id]); + unset($this->level->chunkEntities[$this->chunkIndex][$this->id]); + $this->despawnFromAll(); + if($this instanceof Player){ + foreach($this->chunksLoaded as $index => $Yndex){ + if($Yndex !== 0xff){ + $X = null; + $Z = null; + LevelFormat::getXZ($index, $X, $Z); + foreach($this->level->getChunkEntities($X, $Z) as $entity){ + $entity->despawnFrom($this); + } + } + } + $this->level->freeAllChunks($this); + } + } + $this->level = $targetLevel; + $this->level->entities[$this->id] = $this; + if($this instanceof Player){ + $this->chunksLoaded = array(); + $pk = new SetTimePacket(); + $pk->time = $this->level->getTime(); + $pk->started = $this->level->stopTime == false; + $this->dataPacket($pk); + } + $this->spawnToAll(); + $this->chunkIndex = false; + } + + public function getPosition(){ + return new Position($this->x, $this->y, $this->z, $this->level); + } + + public function move(Vector3 $displacement){ + if($displacement->x == 0 and $displacement->y == 0 and $displacement->z == 0){ + return; + } + + $x = $this->x; + $y = $this->y; + $z = $this->z; + $this->scheduleUpdate(); + } + + public function setPositionAndRotation(Vector3 $pos, $yaw, $pitch){ + if($this->setPosition($pos) === true){ + $this->setRotation($yaw, $pitch); + + return true; + } + + return false; + } + + public function setRotation($yaw, $pitch){ + $this->yaw = $yaw; + $this->pitch = $pitch; + $this->scheduleUpdate(); + } + + public function setPosition(Vector3 $pos){ + if($pos instanceof Position and $pos->level instanceof Level and $pos->level !== $this->level){ + if($this->switchLevel($pos->level) === false){ + return false; + } + } + Server::getInstance()->getPluginManager()->callEvent($ev = new EntityMoveEvent($this, $pos)); + if($ev->isCancelled()){ + return false; + } + $this->x = $pos->x; + $this->y = $pos->y; + $this->z = $pos->z; + + $radius = $this->width / 2; + if(($index = LevelFormat::getIndex($this->x >> 4, $this->z >> 4)) !== $this->chunkIndex){ + if($this->chunkIndex !== false){ + unset($this->level->chunkEntities[$this->chunkIndex][$this->id]); + } + $this->chunkIndex = $index; + $this->level->loadChunk($this->x >> 4, $this->z >> 4); + + $newChunk = $this->level->getUsingChunk($this->x >> 4, $this->z >> 4); + foreach($this->hasSpawned as $player){ + if(!isset($newChunk[$player->CID])){ + $this->despawnFrom($player); + }else{ + unset($newChunk[$player->CID]); + } + } + foreach($newChunk as $player){ + $this->spawnTo($player); + } + + $this->level->chunkEntities[$this->chunkIndex][$this->id] = $this; + } + $this->boundingBox->setBounds($pos->x - $radius, $pos->y, $pos->z - $radius, $pos->x + $radius, $pos->y + $this->height, $pos->z + $radius); + + $this->scheduleUpdate(); + + return true; + } + + public function getMotion(){ + return new Vector3($this->motionX, $this->motionY, $this->motionZ); + } + + public function setMotion(Vector3 $motion){ + Server::getInstance()->getPluginManager()->callEvent($ev = new EntityMotionEvent($this, $motion)); + if($ev->isCancelled()){ + return false; + } + $this->motionX = $motion->x; + $this->motionY = $motion->y; + $this->motionZ = $motion->z; + $this->scheduleUpdate(); + } + + public function isOnGround(){ + return $this->onGround === true; + } + + public function kill(){ + $this->dead = true; + } + + public function getLevel(){ + return $this->level; + } + + public function teleport(Vector3 $pos, $yaw = false, $pitch = false){ + $this->setMotion(new Vector3(0, 0, 0)); + if($this->setPositionAndRotation($pos, $yaw === false ? $this->yaw : $yaw, $pitch === false ? $this->pitch : $pitch) !== false){ + if($this instanceof Player){ + $this->airTicks = 300; + $this->fallDistance = 0; + $this->orderChunks(); + $this->getNextChunk(true); + $this->forceMovement = $pos; + + $pk = new MovePlayerPacket; + $pk->eid = 0; + $pk->x = $this->x; + $pk->y = $this->y; + $pk->z = $this->z; + $pk->bodyYaw = $this->yaw; + $pk->pitch = $this->pitch; + $pk->yaw = $this->yaw; + $this->dataPacket($pk); + } + + return true; + } + + return false; + } + + public function getID(){ + return $this->id; + } + + public function spawnToAll(){ + foreach($this->level->getPlayers() as $player){ + if(isset($player->id) and $player->spawned === true){ + $this->spawnTo($player); + } + } + } + + public function despawnFromAll(){ + foreach($this->hasSpawned as $player){ + $this->despawnFrom($player); + } + } + + public function close(){ + if($this->closed === false){ + $this->closed = true; + unset(Entity::$needUpdate[$this->id]); + unset($this->level->entities[$this->id]); + unset($this->level->chunkEntities[$this->chunkIndex][$this->id]); + unset(Entity::$list[$this->id]); + $this->despawnFromAll(); + Server::getInstance()->getPluginManager()->callEvent(new EntityDespawnEvent($this)); + } + } + + public function __destruct(){ + $this->close(); + } + +} \ No newline at end of file diff --git a/src/pocketmine/entity/Explosive.php b/src/pocketmine/entity/Explosive.php new file mode 100644 index 000000000..d728861cc --- /dev/null +++ b/src/pocketmine/entity/Explosive.php @@ -0,0 +1,27 @@ +namedtag->NameTag)){ + $this->nameTag = $this->namedtag["NameTag"]; + } + $this->hotbar = array(-1, -1, -1, -1, -1, -1, -1, -1, -1); + $this->armor = array( + 0 => Item::get(Item::AIR, 0, 0), + 1 => Item::get(Item::AIR, 0, 0), + 2 => Item::get(Item::AIR, 0, 0), + 3 => Item::get(Item::AIR, 0, 0) + ); + + foreach($this->namedtag->Inventory as $item){ + if($item["Slot"] >= 0 and $item["Slot"] < 9){ //Hotbar + $this->hotbar[$item["Slot"]] = isset($item["TrueSlot"]) ? $item["TrueSlot"] : -1; + }elseif($item["Slot"] >= 100 and $item["Slot"] < 104){ //Armor + $this->armor[$item["Slot"] - 100] = Item::get($item["id"], $item["Damage"], $item["Count"]); + }else{ + $this->inventory[$item["Slot"] - 9] = Item::get($item["id"], $item["Damage"], $item["Count"]); + } + } + $this->slot = $this->hotbar[0]; + + $this->height = 1.8; //Or 1.62? + $this->width = 0.6; + } + + public function saveNBT(){ + parent::saveNBT(); + $this->namedtag->Inventory = new Enum("Inventory", array()); + $this->namedtag->Inventory->setTagType(NBT::TAG_Compound); + for($slot = 0; $slot < 9; ++$slot){ + if(isset($this->hotbar[$slot]) and $this->hotbar[$slot] !== -1){ + $item = $this->getSlot($this->hotbar[$slot]); + if($item->getID() !== 0 and $item->getCount() > 0){ + $this->namedtag->Inventory[$slot] = new Compound(false, array( + new Byte("Count", $item->getCount()), + new Short("Damage", $item->getMetadata()), + new Byte("Slot", $slot), + new Byte("TrueSlot", $this->hotbar[$slot]), + new Short("id", $item->getID()), + )); + continue; + } + } + $this->namedtag->Inventory[$slot] = new Compound(false, array( + new Byte("Count", 0), + new Short("Damage", 0), + new Byte("Slot", $slot), + new Byte("TrueSlot", -1), + new Short("id", 0), + )); + } + + //Normal inventory + $slotCount = Player::SURVIVAL_SLOTS + 9; + //$slotCount = (($this instanceof Player and ($this->gamemode & 0x01) === 1) ? Player::CREATIVE_SLOTS : Player::SURVIVAL_SLOTS) + 9; + for($slot = 9; $slot < $slotCount; ++$slot){ + $item = $this->getSlot($slot - 9); + $this->namedtag->Inventory[$slot] = new Compound(false, array( + new Byte("Count", $item->getCount()), + new Short("Damage", $item->getMetadata()), + new Byte("Slot", $slot), + new Short("id", $item->getID()), + )); + } + + //Armor + for($slot = 100; $slot < 104; ++$slot){ + $item = $this->armor[$slot - 100]; + if($item instanceof Item){ + $this->namedtag->Inventory[$slot] = new Compound(false, array( + new Byte("Count", $item->getCount()), + new Short("Damage", $item->getMetadata()), + new Byte("Slot", $slot), + new Short("id", $item->getID()), + )); + } + } + } + + public function spawnTo(Player $player){ + if($player !== $this and !isset($this->hasSpawned[$player->getID()])){ + $this->hasSpawned[$player->getID()] = $player; + + $pk = new AddPlayerPacket; + $pk->clientID = 0; + $pk->username = $this->nameTag; + $pk->eid = $this->id; + $pk->x = $this->x; + $pk->y = $this->y; + $pk->z = $this->z; + $pk->yaw = 0; + $pk->pitch = 0; + $pk->unknown1 = 0; + $pk->unknown2 = 0; + $pk->metadata = $this->getMetadata(); + $player->dataPacket($pk); + + $pk = new SetEntityMotionPacket; + $pk->eid = $this->id; + $pk->speedX = $this->motionX; + $pk->speedY = $this->motionY; + $pk->speedZ = $this->motionZ; + $player->dataPacket($pk); + + $this->sendCurrentEquipmentSlot($player); + + $this->sendArmor($player); + } + } + + public function despawnFrom(Player $player){ + if(isset($this->hasSpawned[$player->getID()])){ + $pk = new RemovePlayerPacket; + $pk->eid = $this->id; + $pk->clientID = 0; + $player->dataPacket($pk); + unset($this->hasSpawned[$player->getID()]); + } + } + + public function setEquipmentSlot($equipmentSlot, $inventorySlot){ + $this->hotbar[$equipmentSlot] = $inventorySlot; + if($equipmentSlot === $this->slot){ + foreach($this->hasSpawned as $p){ + $this->sendEquipmentSlot($p); + } + } + } + + public function setCurrentEquipmentSlot($slot){ + if(isset($this->hotbar[$slot])){ + $this->slot = (int) $slot; + foreach($this->hasSpawned as $p){ + $this->sendEquipmentSlot($p); + } + } + } + + public function sendCurrentEquipmentSlot(Player $player){ + $pk = new PlayerEquipmentPacket; + $pk->eid = $this->id; + $pk->item = $this->getSlot($this->slot)->getID(); + $pk->meta = $this->getSlot($this->slot)->getMetadata(); + $pk->slot = 0; + $player->dataPacket($pk); + } + + public function setArmorSlot($slot, Item $item){ + Server::getInstance()->getPluginManager()->callEvent($ev = new EntityArmorChangeEvent($this, $this->getArmorSlot($slot), $item, $slot)); + if($ev->isCancelled()){ + return false; + } + $this->armor[(int) $slot] = $ev->getNewItem(); + foreach($this->hasSpawned as $p){ + $this->sendArmor($p); + } + if($this instanceof Player){ + $this->sendArmor(); + } + + return true; + } + + public function getArmorSlot($slot){ + $slot = (int) $slot; + if(!isset($this->armor[$slot])){ + $this->armor[$slot] = Item::get(Item::AIR, 0, 0); + } + + return $this->armor[$slot]; + } + + public function sendArmor($player = null){ + $slots = array(); + for($i = 0; $i < 4; ++$i){ + if(isset($this->armor[$i]) and ($this->armor[$i] instanceof Item) and $this->armor[$i]->getID() > Item::AIR){ + $slots[$i] = $this->armor[$i]->getID() !== Item::AIR ? $this->armor[$i]->getID() - 256 : 0; + }else{ + $this->armor[$i] = Item::get(Item::AIR, 0, 0); + $slots[$i] = 255; + } + } + if($player instanceof Player){ + $pk = new PlayerArmorEquipmentPacket(); + $pk->eid = $this->id; + $pk->slots = $slots; + $player->dataPacket($pk); + }elseif($this instanceof Player){ + $pk = new ContainerSetContentPacket; + $pk->windowid = 0x78; //Armor window id + $pk->slots = $this->armor; + $this->dataPacket($pk); + } + } + + public function getMetadata(){ //TODO + $flags = 0; + $flags |= $this->fireTicks > 0 ? 1 : 0; + //$flags |= ($this->crouched === true ? 0b10:0) << 1; + //$flags |= ($this->inAction === true ? 0b10000:0); + $d = array( + 0 => array("type" => 0, "value" => $flags), + 1 => array("type" => 1, "value" => $this->airTicks), + 16 => array("type" => 0, "value" => 0), + 17 => array("type" => 6, "value" => array(0, 0, 0)), + ); + + /*if($this->class === ENTITY_MOB and $this->type === MOB_SHEEP){ + if(!isset($this->data["Sheared"])){ + $this->data["Sheared"] = 0; + $this->data["Color"] = mt_rand(0,15); + } + $d[16]["value"] = (($this->data["Sheared"] == 1 ? 1:0) << 4) | ($this->data["Color"] & 0x0F); + }elseif($this->type === OBJECT_PRIMEDTNT){ + $d[16]["value"] = (int) max(0, $this->data["fuse"] - (microtime(true) - $this->spawntime) * 20); + }elseif($this->class === ENTITY_PLAYER){ + if($this->player->sleeping !== false){ + $d[16]["value"] = 2; + $d[17]["value"] = array($this->player->sleeping->x, $this->player->sleeping->y, $this->player->sleeping->z); + } + }*/ + + return $d; + } + + public function attack($damage, $source = "generic"){ + + } + + public function heal($amount, $source = "generic"){ + + } + + public function hasItem(Item $item, $checkDamage = true){ + foreach($this->inventory as $s => $i){ + if($i->equals($item, $checkDamage)){ + return $i; + } + } + + return false; + } + + public function canAddItem(Item $item){ + $inv = $this->inventory; + while($item->getCount() > 0){ + $add = 0; + foreach($inv as $s => $i){ + if($i->getID() === Item::AIR){ + $add = min($i->getMaxStackSize(), $item->getCount()); + $inv[$s] = clone $item; + $inv[$s]->setCount($add); + break; + }elseif($i->equals($item)){ + $add = min($i->getMaxStackSize() - $i->getCount(), $item->getCount()); + if($add <= 0){ + continue; + } + $inv[$s] = clone $item; + $inv[$s]->setCount($i->getCount() + $add); + break; + } + } + if($add <= 0){ + return false; + } + $item->setCount($item->getCount() - $add); + } + + return true; + } + + public function addItem(Item $item){ + while($item->getCount() > 0){ + $add = 0; + foreach($this->inventory as $s => $i){ + if($i->getID() === Item::AIR){ + $add = min($i->getMaxStackSize(), $item->getCount()); + $i2 = clone $item; + $i2->setCount($add); + $this->setSlot($s, $i2); + break; + }elseif($i->equals($item)){ + $add = min($i->getMaxStackSize() - $i->getCount(), $item->getCount()); + if($add <= 0){ + continue; + } + $i2 = clone $item; + $i2->setCount($i->getCount() + $add); + $this->setSlot($s, $i2); + break; + } + } + if($add <= 0){ + return false; + } + $item->setCount($item->getCount() - $add); + } + + return true; + } + + public function canRemoveItem(Item $item, $checkDamage = true){ + return $this->hasItem($item, $checkDamage); + } + + public function removeItem(Item $item, $checkDamage = true){ + while($item->getCount() > 0){ + $remove = 0; + foreach($this->inventory as $s => $i){ + if($i->equals($item, $checkDamage)){ + $remove = min($item->getCount(), $i->getCount()); + if($item->getCount() < $i->getCount()){ + $i->setCount($i->getCount() - $item->getCount()); + $this->setSlot($s, $i); + }else{ + $this->setSlot($s, Item::get(Item::AIR, 0, 0)); + } + break; + } + } + if($remove <= 0){ + return false; + } + $item->setCount($item->getCount() - $remove); + } + + return true; + } + + public function setSlot($slot, Item $item){ + Server::getInstance()->getPluginManager()->callEvent($ev = new EntityInventoryChangeEvent($this, $this->getSlot($slot), $item, $slot)); + if($ev->isCancelled()){ + return false; + } + $this->inventory[(int) $slot] = $ev->getNewItem(); + + return true; + } + + /** + * @param int $slot + * + * @return Item + */ + public function getSlot($slot){ + $slot = (int) $slot; + if(!isset($this->inventory[$slot])){ + $this->inventory[$slot] = Item::get(Item::AIR, 0, 0); + } + + return $this->inventory[$slot]; + } + + public function getAllSlots(){ + return $this->inventory; + } + + public function getSlotCount(){ + return count($this->inventory); + } +} \ No newline at end of file diff --git a/src/pocketmine/entity/InventorySource.php b/src/pocketmine/entity/InventorySource.php new file mode 100644 index 000000000..d2fad5006 --- /dev/null +++ b/src/pocketmine/entity/InventorySource.php @@ -0,0 +1,58 @@ +eventName !== null ? get_class($this) : $this->eventName; + } + + /** + * @return bool + */ + public function isCancelled(){ + return $this->isCancelled === true; + } + + /** + * @param bool $value + * + * @return bool + */ + public function setCancelled($value = true){ + $this->isCancelled = (bool) $value; + } + + /** + * @return HandlerList + */ + public function getHandlers(){ + if(static::$handlerList === null){ + static::$handlerList = new HandlerList(); + } + + return static::$handlerList; + } + +} \ No newline at end of file diff --git a/src/pocketmine/event/EventPriority.php b/src/pocketmine/event/EventPriority.php new file mode 100644 index 000000000..5f453ea0c --- /dev/null +++ b/src/pocketmine/event/EventPriority.php @@ -0,0 +1,58 @@ +bake(); + } + } + + /** + * Unregisters all the listeners + * If a Plugin or Listener is passed, all the listeners with that object will be removed + * + * @param Plugin|Listener|null $object + */ + public static function unregisterAll($object = null){ + if($object instanceof Listener or $object instanceof Plugin){ + foreach(self::$allLists as $h){ + $h->unregister($object); + } + }else{ + foreach(self::$allLists as $h){ + foreach($h->handlerSlots as $key => $list){ + $h->handlerSlots[$key] = array(); + } + $h->handlers = null; + } + } + } + + public function __construct(){ + $this->handlerSlots = array( + EventPriority::MONITOR => array(), + EventPriority::HIGHEST => array(), + EventPriority::HIGH => array(), + EventPriority::NORMAL => array(), + EventPriority::LOW => array(), + EventPriority::LOWEST => array() + ); + self::$allLists[] = $this; + } + + public function register(RegisteredListener $listener){ + if($listener->getPriority() < EventPriority::MONITOR or $listener->getPriority() > EventPriority::LOWEST){ + return; + } + if(isset($this->handlerSlots[$listener->getPriority()][spl_object_hash($listener)])){ + trigger_error("This listener is already registered to priority " . $listener->getPriority(), E_USER_WARNING); + + return; + } + $this->handlers = null; + $this->handlerSlots[$listener->getPriority()][spl_object_hash($listener)] = $listener; + } + + /** + * @param RegisteredListener[] $listeners + */ + public function registerAll(array $listeners){ + foreach($listeners as $listener){ + $this->register($listener); + } + } + + /** + * @param RegisteredListener|Listener|Plugin $object + */ + public function unregister($object){ + if($object instanceof Plugin or $object instanceof Listener){ + $changed = false; + foreach($this->handlerSlots as $priority => $list){ + foreach($list as $hash => $listener){ + if(($object instanceof Plugin and $listener->getPlugin() === $object) + or ($object instanceof Listener and $listener->getListener() === $object) + ){ + unset($this->handlerSlots[$priority][$hash]); + $changed = true; + } + } + } + if($changed === true){ + $this->handlers = null; + } + }elseif($object instanceof RegisteredListener){ + if(isset($this->handlerSlots[$object->getPriority()][spl_object_hash($listener)])){ + unset($this->handlerSlots[$object->getPriority()][spl_object_hash($listener)]); + $this->handlers = null; + } + } + } + + public function bake(){ + if($this->handlers !== null){ + return; + } + $entries = array(); + foreach($this->handlerSlots as $list){ + foreach($list as $hash => $listener){ + $entries[$hash] = $listener; + } + } + $this->handlers = $entries; + } + + /** + * @param null|Plugin $plugin + * + * @return RegisteredListener[] + */ + public function getRegisteredListeners($plugin = null){ + if($plugin instanceof Plugin){ + $listeners = array(); + foreach($this->getRegisteredListeners(null) as $hash => $listener){ + if($listener->getPlugin() === $plugin){ + $listeners[$hash] = $plugin; + } + } + + return $listeners; + }else{ + while(($handlers = $this->handlers) === null){ + $this->bake(); + } + + return $handlers; + } + } + + /** + * @return HandlerList[] + */ + public static function getHandlerLists(){ + return self::$allLists; + } + +} \ No newline at end of file diff --git a/src/pocketmine/event/Listener.php b/src/pocketmine/event/Listener.php new file mode 100644 index 000000000..75361bd00 --- /dev/null +++ b/src/pocketmine/event/Listener.php @@ -0,0 +1,26 @@ +block = $block; + $this->item = $item; + $this->player = $player; + $this->instaBreak = (bool) $instaBreak; + } + + public function getPlayer(){ + return $this->player; + } + + public function getItem(){ + return $this->item; + } + + public function getInstaBreak(){ + return $this->instaBreak; + } + + /** + * @param boolean $instaBreak + */ + public function setInstaBreak($instaBreak){ + $this->instaBreak = (bool) $instaBreak; + } +} \ No newline at end of file diff --git a/src/pocketmine/event/block/BlockEvent.php b/src/pocketmine/event/block/BlockEvent.php new file mode 100644 index 000000000..e5bb1eb61 --- /dev/null +++ b/src/pocketmine/event/block/BlockEvent.php @@ -0,0 +1,36 @@ +block; + } +} \ No newline at end of file diff --git a/src/pocketmine/event/block/BlockPlaceEvent.php b/src/pocketmine/event/block/BlockPlaceEvent.php new file mode 100644 index 000000000..c57755e1c --- /dev/null +++ b/src/pocketmine/event/block/BlockPlaceEvent.php @@ -0,0 +1,73 @@ +block = $blockPlace; + $this->blockReplace = $blockReplace; + $this->blockAgainst = $blockAgainst; + $this->item = $item; + $this->player = $player; + } + + public function getPlayer(){ + return $this->player; + } + + /** + * Gets the item in hand + * + * @return mixed + */ + public function getItem(){ + return $this->item; + } + + public function getBlockReplaced(){ + return $this->blockReplace; + } + + public function getBlockAgainst(){ + return $this->blockAgainst; + } +} \ No newline at end of file diff --git a/src/pocketmine/event/entity/EntityArmorChangeEvent.php b/src/pocketmine/event/entity/EntityArmorChangeEvent.php new file mode 100644 index 000000000..7af762da5 --- /dev/null +++ b/src/pocketmine/event/entity/EntityArmorChangeEvent.php @@ -0,0 +1,60 @@ +entity = $entity; + $this->oldItem = $oldItem; + $this->newItem = $newItem; + $this->slot = (int) $slot; + } + + public function getSlot(){ + return $this->slot; + } + + public function getNewItem(){ + return $this->newItem; + } + + public function setNewItem(Item $item){ + $this->newItem = $item; + } + + public function getOldItem(){ + return $this->oldItem; + } + + +} \ No newline at end of file diff --git a/src/pocketmine/event/entity/EntityDespawnEvent.php b/src/pocketmine/event/entity/EntityDespawnEvent.php new file mode 100644 index 000000000..736578837 --- /dev/null +++ b/src/pocketmine/event/entity/EntityDespawnEvent.php @@ -0,0 +1,87 @@ +entity = $entity; + } + + /** + * @return int + */ + public function getType(){ + //TODO: implement Entity types + return -1; + } + + /** + * @return bool + */ + public function isCreature(){ + return $this->entity instanceof Creature; + } + + /** + * @return bool + */ + public function isHuman(){ + return $this->entity instanceof Human; + } + + /** + * @return bool + */ + public function isProjectile(){ + return $this->entity instanceof Projectile; + } + + /** + * @return bool + */ + public function isVehicle(){ + return $this->entity instanceof Vehicle; + } + + /** + * @return bool + */ + public function isItem(){ + return $this->entity instanceof DroppedItem; + } + +} \ No newline at end of file diff --git a/src/pocketmine/event/entity/EntityEvent.php b/src/pocketmine/event/entity/EntityEvent.php new file mode 100644 index 000000000..2cd83d454 --- /dev/null +++ b/src/pocketmine/event/entity/EntityEvent.php @@ -0,0 +1,36 @@ +entity; + } +} \ No newline at end of file diff --git a/src/pocketmine/event/entity/EntityExplodeEvent.php b/src/pocketmine/event/entity/EntityExplodeEvent.php new file mode 100644 index 000000000..46476a6eb --- /dev/null +++ b/src/pocketmine/event/entity/EntityExplodeEvent.php @@ -0,0 +1,94 @@ +entity = $entity; + $this->position = $position; + $this->blocks = $blocks; + $this->yield = $yield; + } + + /** + * @return Position + */ + public function getPosition(){ + return $this->position; + } + + /** + * @return Block[] + */ + public function getBlockList(){ + return $this->blocks; + } + + /** + * @param Block[] $blocks + */ + public function setBlockList(array $blocks){ + $this->blocks = $blocks; + } + + /** + * @return float + */ + public function getYield(){ + return $this->yield; + } + + /** + * @param float $yield + */ + public function setYield($yield){ + $this->yield = $yield; + } + +} \ No newline at end of file diff --git a/src/pocketmine/event/entity/EntityInventoryChangeEvent.php b/src/pocketmine/event/entity/EntityInventoryChangeEvent.php new file mode 100644 index 000000000..10e6f1434 --- /dev/null +++ b/src/pocketmine/event/entity/EntityInventoryChangeEvent.php @@ -0,0 +1,60 @@ +entity = $entity; + $this->oldItem = $oldItem; + $this->newItem = $newItem; + $this->slot = (int) $slot; + } + + public function getSlot(){ + return $this->slot; + } + + public function getNewItem(){ + return $this->newItem; + } + + public function setNewItem(Item $item){ + $this->newItem = $item; + } + + public function getOldItem(){ + return $this->oldItem; + } + + +} \ No newline at end of file diff --git a/src/pocketmine/event/entity/EntityLevelChangeEvent.php b/src/pocketmine/event/entity/EntityLevelChangeEvent.php new file mode 100644 index 000000000..3c875f6ec --- /dev/null +++ b/src/pocketmine/event/entity/EntityLevelChangeEvent.php @@ -0,0 +1,48 @@ +entity = $entity; + $this->originLevel = $originLevel; + $this->targetLevel = $targetLevel; + } + + public function getOrigin(){ + return $this->originLevel; + } + + public function getTarget(){ + return $this->targetLevel; + } +} \ No newline at end of file diff --git a/src/pocketmine/event/entity/EntityMotionEvent.php b/src/pocketmine/event/entity/EntityMotionEvent.php new file mode 100644 index 000000000..3e1887491 --- /dev/null +++ b/src/pocketmine/event/entity/EntityMotionEvent.php @@ -0,0 +1,44 @@ +entity = $entity; + $this->mot = $mot; + } + + public function getVector(){ + return $this->mot; + } + + +} \ No newline at end of file diff --git a/src/pocketmine/event/entity/EntityMoveEvent.php b/src/pocketmine/event/entity/EntityMoveEvent.php new file mode 100644 index 000000000..432d1945a --- /dev/null +++ b/src/pocketmine/event/entity/EntityMoveEvent.php @@ -0,0 +1,45 @@ +entity = $entity; + $this->pos = $pos; + } + + public function getVector(){ + return $this->pos; + } + + +} \ No newline at end of file diff --git a/src/pocketmine/event/entity/EntitySpawnEvent.php b/src/pocketmine/event/entity/EntitySpawnEvent.php new file mode 100644 index 000000000..490f19a73 --- /dev/null +++ b/src/pocketmine/event/entity/EntitySpawnEvent.php @@ -0,0 +1,94 @@ +entity = $entity; + } + + /** + * @return \pocketmine\level\Position + */ + public function getPosition(){ + return $this->entity->getPosition(); + } + + /** + * @return int + */ + public function getType(){ + //TODO: implement Entity types + return -1; + } + + /** + * @return bool + */ + public function isCreature(){ + return $this->entity instanceof Creature; + } + + /** + * @return bool + */ + public function isHuman(){ + return $this->entity instanceof Human; + } + + /** + * @return bool + */ + public function isProjectile(){ + return $this->entity instanceof Projectile; + } + + /** + * @return bool + */ + public function isVehicle(){ + return $this->entity instanceof Vehicle; + } + + /** + * @return bool + */ + public function isItem(){ + return $this->entity instanceof DroppedItem; + } + +} \ No newline at end of file diff --git a/src/pocketmine/event/inventory/InventoryEvent.php b/src/pocketmine/event/inventory/InventoryEvent.php new file mode 100644 index 000000000..afcfc1041 --- /dev/null +++ b/src/pocketmine/event/inventory/InventoryEvent.php @@ -0,0 +1,47 @@ +inventory; + } + + /** + * @return \pocketmine\entity\Human[] + */ + public function getViewers(){ + return $this->inventory->getViewers(); + } +} \ No newline at end of file diff --git a/src/pocketmine/event/player/PlayerAchievementAwardedEvent.php b/src/pocketmine/event/player/PlayerAchievementAwardedEvent.php new file mode 100644 index 000000000..671f391fc --- /dev/null +++ b/src/pocketmine/event/player/PlayerAchievementAwardedEvent.php @@ -0,0 +1,48 @@ +player = $player; + $this->achievement = $achievementId; + } + + public function getAchievement(){ + return $this->achievement; + } +} \ No newline at end of file diff --git a/src/pocketmine/event/player/PlayerChatEvent.php b/src/pocketmine/event/player/PlayerChatEvent.php new file mode 100644 index 000000000..31a1779b5 --- /dev/null +++ b/src/pocketmine/event/player/PlayerChatEvent.php @@ -0,0 +1,90 @@ + %s", array $recipients = null){ + $this->player = $player; + $this->message = $message; + $this->format = $format; + if($recipients === null){ + $this->recipients = Server::getInstance()->getPluginManager()->getPermissionSubscriptions(Player::BROADCAST_CHANNEL_USERS); + }else{ + $this->recipients = $recipients; + } + } + + public function getMessage(){ + return $this->message; + } + + public function setMessage($message){ + $this->message = $message; + } + + /** + * Changes the player that is sending the message + * + * @param Player $player + */ + public function setPlayer(Player $player){ + if($player instanceof Player){ + $this->player = $player; + } + } + + public function getFormat(){ + return $this->format; + } + + public function setFormat($format){ + $this->format = $format; + } + + public function getRecipients(){ + return $this->recipients; + } + + public function setRecipients(array $recipients){ + $this->recipients = $recipients; + } +} \ No newline at end of file diff --git a/src/pocketmine/event/player/PlayerCommandPreprocessEvent.php b/src/pocketmine/event/player/PlayerCommandPreprocessEvent.php new file mode 100644 index 000000000..bd54b7797 --- /dev/null +++ b/src/pocketmine/event/player/PlayerCommandPreprocessEvent.php @@ -0,0 +1,72 @@ +player = $player; + $this->message = $message; + } + + /** + * @return string + */ + public function getMessage(){ + return $this->message; + } + + /** + * @param string $message + */ + public function setMessage($message){ + $this->message = $message; + } + + /** + * @param Player $player + */ + public function setPlayer(Player $player){ + $this->player = $player; + } + +} \ No newline at end of file diff --git a/src/pocketmine/event/player/PlayerEvent.php b/src/pocketmine/event/player/PlayerEvent.php new file mode 100644 index 000000000..23d59f89f --- /dev/null +++ b/src/pocketmine/event/player/PlayerEvent.php @@ -0,0 +1,36 @@ +player; + } +} \ No newline at end of file diff --git a/src/pocketmine/event/player/PlayerGameModeChangeEvent.php b/src/pocketmine/event/player/PlayerGameModeChangeEvent.php new file mode 100644 index 000000000..dedce2fc9 --- /dev/null +++ b/src/pocketmine/event/player/PlayerGameModeChangeEvent.php @@ -0,0 +1,45 @@ +player = $player; + $this->gamemode = (int) $newGamemode; + } + + public function getNewGamemode(){ + return $this->gamemode; + } + +} \ No newline at end of file diff --git a/src/pocketmine/event/player/PlayerInteractEvent.php b/src/pocketmine/event/player/PlayerInteractEvent.php new file mode 100644 index 000000000..20a7c2f6a --- /dev/null +++ b/src/pocketmine/event/player/PlayerInteractEvent.php @@ -0,0 +1,64 @@ +blockTouched = $block; + $this->player = $player; + $this->item = $item; + $this->blockFace = (int) $face; + } + + public function getItem(){ + return $this->item; + } + + public function getBlock(){ + return $this->blockTouched; + } + + public function getFace(){ + return $this->blockFace; + } +} \ No newline at end of file diff --git a/src/pocketmine/event/player/PlayerItemHeldEvent.php b/src/pocketmine/event/player/PlayerItemHeldEvent.php new file mode 100644 index 000000000..2802e5492 --- /dev/null +++ b/src/pocketmine/event/player/PlayerItemHeldEvent.php @@ -0,0 +1,55 @@ +player = $player; + $this->item = $item; + $this->inventorySlot = (int) $inventorySlot; + $this->slot = (int) $slot; + } + + public function getSlot(){ + return $this->slot; + } + + public function getInventorySlot(){ + return $this->inventorySlot; + } + + public function getItem(){ + return $this->item; + } + +} \ No newline at end of file diff --git a/src/pocketmine/event/player/PlayerJoinEvent.php b/src/pocketmine/event/player/PlayerJoinEvent.php new file mode 100644 index 000000000..52f5a7bb7 --- /dev/null +++ b/src/pocketmine/event/player/PlayerJoinEvent.php @@ -0,0 +1,54 @@ +player = $player; + $this->joinMessage = $joinMessage; + } + + /** + * Sets the join message. This won't work on Minecraft: PE <= 0.8.1, since the join message is client-side + * We'll see if Mojang adds this in Minecraft: PE 0.9.0 ^.^ + * + * @param string $joinMessage + */ + public function setJoinMessage($joinMessage){ + $this->joinMessage = $joinMessage; + } + + public function getJoinMessage(){ + return $this->joinMessage; + } + +} \ No newline at end of file diff --git a/src/pocketmine/event/player/PlayerKickEvent.php b/src/pocketmine/event/player/PlayerKickEvent.php new file mode 100644 index 000000000..27f612fd3 --- /dev/null +++ b/src/pocketmine/event/player/PlayerKickEvent.php @@ -0,0 +1,57 @@ +player = $player; + $this->quitMessage = $quitMessage; + $this->reason = $reason; + } + + public function getReason(){ + return $this->reason; + } + + public function setQuitMessage($quitMessage){ + $this->quitMessage = $quitMessage; + } + + public function getQuitMessage(){ + return $this->quitMessage; + } + +} \ No newline at end of file diff --git a/src/pocketmine/event/player/PlayerLoginEvent.php b/src/pocketmine/event/player/PlayerLoginEvent.php new file mode 100644 index 000000000..6d05df41a --- /dev/null +++ b/src/pocketmine/event/player/PlayerLoginEvent.php @@ -0,0 +1,49 @@ +player = $player; + $this->kickMessage = $kickMessage; + } + + public function setKickMessage($kickMessage){ + $this->kickMessage = $kickMessage; + } + + public function getKickMessage(){ + return $this->kickMessage; + } + +} \ No newline at end of file diff --git a/src/pocketmine/event/player/PlayerPreLoginEvent.php b/src/pocketmine/event/player/PlayerPreLoginEvent.php new file mode 100644 index 000000000..b3df86a1f --- /dev/null +++ b/src/pocketmine/event/player/PlayerPreLoginEvent.php @@ -0,0 +1,49 @@ +player = $player; + $this->kickMessage = $kickMessage; + } + + public function setKickMessage($kickMessage){ + $this->kickMessage = $kickMessage; + } + + public function getKickMessage(){ + return $this->kickMessage; + } + +} \ No newline at end of file diff --git a/src/pocketmine/event/player/PlayerQuitEvent.php b/src/pocketmine/event/player/PlayerQuitEvent.php new file mode 100644 index 000000000..713426960 --- /dev/null +++ b/src/pocketmine/event/player/PlayerQuitEvent.php @@ -0,0 +1,48 @@ +player = $player; + $this->quitMessage = $quitMessage; + } + + public function setQuitMessage($quitMessage){ + $this->quitMessage = $quitMessage; + } + + public function getQuitMessage(){ + return $this->quitMessage; + } + +} \ No newline at end of file diff --git a/src/pocketmine/event/player/PlayerRespawnEvent.php b/src/pocketmine/event/player/PlayerRespawnEvent.php new file mode 100644 index 000000000..64cc2b6b0 --- /dev/null +++ b/src/pocketmine/event/player/PlayerRespawnEvent.php @@ -0,0 +1,58 @@ +player = $player; + $this->position = $position; + } + + /** + * @return Position + */ + public function getRespawnPosition(){ + return $this->position; + } + + /** + * @param Position $position + */ + public function setRespawnPosition(Position $position){ + $this->position = $position; + } +} \ No newline at end of file diff --git a/src/pocketmine/event/plugin/PluginDisableEvent.php b/src/pocketmine/event/plugin/PluginDisableEvent.php new file mode 100644 index 000000000..13f95f295 --- /dev/null +++ b/src/pocketmine/event/plugin/PluginDisableEvent.php @@ -0,0 +1,38 @@ +plugin = $plugin; + } +} \ No newline at end of file diff --git a/src/pocketmine/event/plugin/PluginEnableEvent.php b/src/pocketmine/event/plugin/PluginEnableEvent.php new file mode 100644 index 000000000..bcf72e11f --- /dev/null +++ b/src/pocketmine/event/plugin/PluginEnableEvent.php @@ -0,0 +1,38 @@ +plugin = $plugin; + } +} \ No newline at end of file diff --git a/src/pocketmine/event/plugin/PluginEvent.php b/src/pocketmine/event/plugin/PluginEvent.php new file mode 100644 index 000000000..066d4f8e6 --- /dev/null +++ b/src/pocketmine/event/plugin/PluginEvent.php @@ -0,0 +1,42 @@ +plugin; + } +} \ No newline at end of file diff --git a/src/pocketmine/event/server/DataPacketReceiveEvent.php b/src/pocketmine/event/server/DataPacketReceiveEvent.php new file mode 100644 index 000000000..b93101a35 --- /dev/null +++ b/src/pocketmine/event/server/DataPacketReceiveEvent.php @@ -0,0 +1,47 @@ +packet = $packet; + $this->player = $player; + } + + public function getPacket(){ + return $this->packet; + } + + public function getPlayer(){ + return $this->player; + } +} \ No newline at end of file diff --git a/src/pocketmine/event/server/DataPacketSendEvent.php b/src/pocketmine/event/server/DataPacketSendEvent.php new file mode 100644 index 000000000..3b211c311 --- /dev/null +++ b/src/pocketmine/event/server/DataPacketSendEvent.php @@ -0,0 +1,47 @@ +packet = $packet; + $this->player = $player; + } + + public function getPacket(){ + return $this->packet; + } + + public function getPlayer(){ + return $this->player; + } +} \ No newline at end of file diff --git a/src/pocketmine/event/server/PacketReceiveEvent.php b/src/pocketmine/event/server/PacketReceiveEvent.php new file mode 100644 index 000000000..d1b7d04cd --- /dev/null +++ b/src/pocketmine/event/server/PacketReceiveEvent.php @@ -0,0 +1,41 @@ +packet = $packet; + } + + public function getPacket(){ + return $this->packet; + } +} \ No newline at end of file diff --git a/src/pocketmine/event/server/PacketSendEvent.php b/src/pocketmine/event/server/PacketSendEvent.php new file mode 100644 index 000000000..ec3f88083 --- /dev/null +++ b/src/pocketmine/event/server/PacketSendEvent.php @@ -0,0 +1,41 @@ +packet = $packet; + } + + public function getPacket(){ + return $this->packet; + } +} \ No newline at end of file diff --git a/src/pocketmine/event/server/ServerCommandEvent.php b/src/pocketmine/event/server/ServerCommandEvent.php new file mode 100644 index 000000000..6b3ef2a98 --- /dev/null +++ b/src/pocketmine/event/server/ServerCommandEvent.php @@ -0,0 +1,67 @@ +sender = $sender; + $this->command = $command; + } + + /** + * @return string + */ + public function getCommand(){ + return $this->command; + } + + /** + * @param string $command + */ + public function setCommand($command){ + $this->command = $command; + } + +} \ No newline at end of file diff --git a/src/pocketmine/event/server/ServerEvent.php b/src/pocketmine/event/server/ServerEvent.php new file mode 100644 index 000000000..3806343f3 --- /dev/null +++ b/src/pocketmine/event/server/ServerEvent.php @@ -0,0 +1,31 @@ +tile; + } +} \ No newline at end of file diff --git a/src/pocketmine/event/tile/TileInventoryChangeEvent.php b/src/pocketmine/event/tile/TileInventoryChangeEvent.php new file mode 100644 index 000000000..30d7c21e1 --- /dev/null +++ b/src/pocketmine/event/tile/TileInventoryChangeEvent.php @@ -0,0 +1,60 @@ +tile = $tile; + $this->oldItem = $oldItem; + $this->newItem = $newItem; + $this->slot = (int) $slot; + } + + public function getSlot(){ + return $this->slot; + } + + public function getNewItem(){ + return $this->newItem; + } + + public function setNewItem(Item $item){ + $this->newItem = $item; + } + + public function getOldItem(){ + return $this->oldItem; + } + + +} \ No newline at end of file diff --git a/src/pocketmine/item/Apple.php b/src/pocketmine/item/Apple.php new file mode 100644 index 000000000..d54b34b83 --- /dev/null +++ b/src/pocketmine/item/Apple.php @@ -0,0 +1,30 @@ +block = Block::get(Item::BED_BLOCK); + parent::__construct(self::BED, 0, $count, "Bed"); + $this->maxStackSize = 1; + } +} \ No newline at end of file diff --git a/src/pocketmine/item/BeetrootSeeds.php b/src/pocketmine/item/BeetrootSeeds.php new file mode 100644 index 000000000..15a774449 --- /dev/null +++ b/src/pocketmine/item/BeetrootSeeds.php @@ -0,0 +1,31 @@ +block = Block::get(Item::BEETROOT_BLOCK); + parent::__construct(self::BEETROOT_SEEDS, 0, $count, "Beetroot Seeds"); + } +} \ No newline at end of file diff --git a/src/pocketmine/item/BeetrootSoup.php b/src/pocketmine/item/BeetrootSoup.php new file mode 100644 index 000000000..323ec397e --- /dev/null +++ b/src/pocketmine/item/BeetrootSoup.php @@ -0,0 +1,31 @@ +maxStackSize = 1; + } + +} \ No newline at end of file diff --git a/src/pocketmine/item/Block.php b/src/pocketmine/item/Block.php new file mode 100644 index 000000000..936121f44 --- /dev/null +++ b/src/pocketmine/item/Block.php @@ -0,0 +1,44 @@ +block = clone $block; + parent::__construct($block->getID(), $block->getMetadata(), $count, $block->getName()); + } + + public function setMetadata($meta){ + $this->meta = $meta & 0x0F; + $this->block->setMetadata($this->meta); + } + + public function getBlock(){ + return $this->block; + } + +} \ No newline at end of file diff --git a/src/pocketmine/item/Bowl.php b/src/pocketmine/item/Bowl.php new file mode 100644 index 000000000..76f7feb6e --- /dev/null +++ b/src/pocketmine/item/Bowl.php @@ -0,0 +1,30 @@ +isActivable = true; + $this->maxStackSize = 1; + } + + public function onActivate(Level $level, Player $player, Block $block, Block $target, $face, $fx, $fy, $fz){ + if($this->meta === Item::AIR){ + if($target instanceof Liquid){ + $level->setBlock($target, new Air(), true, false, true); + if(($player->gamemode & 0x01) === 0){ + $this->meta = ($target instanceof Water) ? Item::WATER : Item::LAVA; + } + + return true; + } + }elseif($this->meta === Item::WATER){ + //Support Make Non-Support Water to Support Water + if($block->getID() === self::AIR || ($block instanceof Water && ($block->getMetadata() & 0x07) != 0x00)){ + $water = new Water(); + $level->setBlock($block, $water, true, false, true); + $water->place(clone $this, $block, $target, $face, $fx, $fy, $fz, $player); + if(($player->gamemode & 0x01) === 0){ + $this->meta = 0; + } + + return true; + } + }elseif($this->meta === Item::LAVA){ + if($block->getID() === self::AIR){ + $level->setBlock($block, new Lava(), true, false, true); + if(($player->gamemode & 0x01) === 0){ + $this->meta = 0; + } + + return true; + } + } + + return false; + } +} \ No newline at end of file diff --git a/src/pocketmine/item/Cake.php b/src/pocketmine/item/Cake.php new file mode 100644 index 000000000..dc0710d97 --- /dev/null +++ b/src/pocketmine/item/Cake.php @@ -0,0 +1,32 @@ +block = Block::get(Item::CAKE_BLOCK); + $this->maxStackSize = 1; + parent::__construct(self::CAKE, 0, $count, "Cake"); + } +} \ No newline at end of file diff --git a/src/pocketmine/item/Carrot.php b/src/pocketmine/item/Carrot.php new file mode 100644 index 000000000..719f3a58c --- /dev/null +++ b/src/pocketmine/item/Carrot.php @@ -0,0 +1,31 @@ +block = Block::get(Item::CARROT_BLOCK); + parent::__construct(self::CARROT, 0, $count, "Carrot"); + } +} \ No newline at end of file diff --git a/src/pocketmine/item/Coal.php b/src/pocketmine/item/Coal.php new file mode 100644 index 000000000..8da79aeca --- /dev/null +++ b/src/pocketmine/item/Coal.php @@ -0,0 +1,33 @@ +meta === 1){ + $this->name = "Charcoal"; + } + } + +} \ No newline at end of file diff --git a/src/pocketmine/item/Diamond.php b/src/pocketmine/item/Diamond.php new file mode 100644 index 000000000..c70f68f6c --- /dev/null +++ b/src/pocketmine/item/Diamond.php @@ -0,0 +1,30 @@ +isActivable = true; + $this->maxStackSize = 1; + } + + public function onActivate(Level $level, Player $player, Block $block, Block $target, $face, $fx, $fy, $fz){ + if(($player->gamemode & 0x01) === 0 and $this->useOn($block) and $this->getMetadata() >= $this->getMaxDurability()){ + $player->setSlot($player->slot, new Item(Item::AIR, 0, 0)); + } + + if($block->getID() === self::AIR and ($target instanceof Solid)){ + $level->setBlock($block, new Fire(), true, false, true); + + return true; + } + + return false; + } +} \ No newline at end of file diff --git a/src/pocketmine/item/GoldIngot.php b/src/pocketmine/item/GoldIngot.php new file mode 100644 index 000000000..130745214 --- /dev/null +++ b/src/pocketmine/item/GoldIngot.php @@ -0,0 +1,30 @@ +block = Block::get(Item::IRON_DOOR_BLOCK); + parent::__construct(self::IRON_DOOR, 0, $count, "Iron Door"); + $this->maxStackSize = 1; + } +} \ No newline at end of file diff --git a/src/pocketmine/item/IronHoe.php b/src/pocketmine/item/IronHoe.php new file mode 100644 index 000000000..533d88915 --- /dev/null +++ b/src/pocketmine/item/IronHoe.php @@ -0,0 +1,30 @@ + new Sugarcane(), + self::WHEAT_SEEDS => new WheatSeeds(), + self::PUMPKIN_SEEDS => new PumpkinSeeds(), + self::MELON_SEEDS => new MelonSeeds(), + self::MUSHROOM_STEW => new MushroomStew(), + self::BEETROOT_SOUP => new BeetrootSoup(), + self::CARROT => new Carrot(), + self::POTATO => new Potato(), + self::BEETROOT_SEEDS => new BeetrootSeeds(), + self::SIGN => new Sign(), + self::WOODEN_DOOR => new WoodenDoor(), + self::BUCKET => new Bucket(), + self::IRON_DOOR => new IronDoor(), + self::CAKE => new Cake(), + self::BED => new Bed(), + self::PAINTING => new Painting(), + self::COAL => new Coal(), + self::APPLE => new Apple(), + self::SPAWN_EGG => new SpawnEgg(), + self::DIAMOND => new Diamond(), + self::STICK => new Stick(), + self::BOWL => new Bowl(), + self::FEATHER => new Feather(), + self::BRICK => new Brick(), + self::IRON_INGOT => new IronIngot(), + self::GOLD_INGOT => new GoldIngot(), + self::IRON_SHOVEL => new IronShovel(), + self::IRON_PICKAXE => new IronPickaxe(), + self::IRON_AXE => new IronAxe(), + self::IRON_HOE => new IronHoe(), + self::WOODEN_SWORD => new WoodenSword(), + self::WOODEN_SHOVEL => new WoodenShovel(), + self::WOODEN_PICKAXE => new WoodenPickaxe(), + self::WOODEN_AXE => new WoodenAxe(), + self::FLINT_STEEL => new FlintSteel(), + ); + foreach(Block::$list as $id => $class){ + self::$list[$id] = new ItemBlock($class); + } + + } + } + + public static function get($id, $meta = 0, $count = 1){ + if(isset(self::$list[$id])){ + $item = clone self::$list[$id]; + $item->setMetadata($meta); + $item->setCount($count); + }else{ + $item = new Item($id, $meta, $count); + } + + return $item; + } + + public static function fromString($str, $multiple = false){ + if($multiple === true){ + $blocks = array(); + foreach(explode(",", $str) as $b){ + $blocks[] = self::fromString($b, false); + } + + return $blocks; + }else{ + $b = explode(":", str_replace(" ", "_", trim($str))); + if(!isset($b[1])){ + $meta = 0; + }else{ + $meta = ((int) $b[1]) & 0xFFFF; + } + + if(defined("pocketmine\\item\\Item::" . strtoupper($b[0]))){ + $item = self::get(constant("pocketmine\\item\\Item::" . strtoupper($b[0])), $meta); + if($item->getID() === self::AIR and strtoupper($b[0]) !== "AIR"){ + $item = self::get(((int) $b[0]) & 0xFFFF, $meta); + } + }else{ + $item = self::get(((int) $b[0]) & 0xFFFF, $meta); + } + + return $item; + } + } + + public function __construct($id, $meta = 0, $count = 1, $name = "Unknown"){ + $this->id = (int) $id; + $this->meta = (int) $meta; + $this->count = (int) $count; + $this->name = $name; + if(!isset($this->block) and $this->id <= 0xff and isset(Block::$list[$this->id])){ + $this->block = Block::get($this->id, $this->meta); + $this->name = $this->block->getName(); + } + if($this->isTool() !== false){ + $this->maxStackSize = 1; + } + } + + + public function getCount(){ + return $this->count; + } + + public function setCount($count){ + $this->count = (int) $count; + } + + final public function getName(){ + return $this->name; + } + + final public function isPlaceable(){ + return (($this->block instanceof Block) and $this->block->isPlaceable === true); + } + + public function getBlock(){ + if($this->block instanceof Block){ + return $this->block; + }else{ + return Block::get(self::AIR); + } + } + + final public function getID(){ + return $this->id; + } + + final public function getMetadata(){ + return $this->meta; + } + + public function setMetadata($meta){ + $this->meta = $meta & 0xFFFF; + } + + final public function getMaxStackSize(){ + return $this->maxStackSize; + } + + final public function getFuelTime(){ + if(!isset(Fuel::$duration[$this->id])){ + return false; + } + if($this->id !== self::BUCKET or $this->meta === 10){ + return Fuel::$duration[$this->id]; + } + + return false; + } + + final public function getSmeltItem(){ + if(!isset(Smelt::$product[$this->id])){ + return false; + } + + if(isset(Smelt::$product[$this->id][0]) and !is_array(Smelt::$product[$this->id][0])){ + return self::get(Smelt::$product[$this->id][0], Smelt::$product[$this->id][1]); + } + + if(!isset(Smelt::$product[$this->id][$this->meta])){ + return false; + } + + return self::get(Smelt::$product[$this->id][$this->meta][0], Smelt::$product[$this->id][$this->meta][1]); + + } + + public function useOn($object, $force = false){ + if($this->isTool() or $force === true){ + if(($object instanceof Entity) and !$this->isSword()){ + $this->meta += 2; + }else{ + $this->meta++; + } + + return true; + }elseif($this->isHoe()){ + if(($object instanceof Block) and ($object->getID() === self::GRASS or $object->getID() === self::DIRT)){ + $this->meta++; + } + } + + return false; + } + + final public function isTool(){ + return ($this->id === self::FLINT_STEEL or $this->id === self::SHEARS or $this->isPickaxe() !== false or $this->isAxe() !== false or $this->isShovel() !== false or $this->isSword() !== false); + } + + final public function getMaxDurability(){ + if(!$this->isTool() and $this->isHoe() === false and $this->id !== self::BOW){ + return false; + } + + $levels = array( + 2 => 33, + 1 => 60, + 3 => 132, + 4 => 251, + 5 => 1562, + self::FLINT_STEEL => 65, + self::SHEARS => 239, + self::BOW => 385, + ); + + if(($type = $this->isPickaxe()) === false){ + if(($type = $this->isAxe()) === false){ + if(($type = $this->isSword()) === false){ + if(($type = $this->isShovel()) === false){ + if(($type = $this->isHoe()) === false){ + $type = $this->id; + } + } + } + } + } + + return $levels[$type]; + } + + final public function isPickaxe(){ //Returns false or level of the pickaxe + switch($this->id){ + case self::IRON_PICKAXE: + return 4; + case self::WOODEN_PICKAXE: + return 1; + case self::STONE_PICKAXE: + return 3; + case self::DIAMOND_PICKAXE: + return 5; + case self::GOLD_PICKAXE: + return 2; + default: + return false; + } + } + + final public function isAxe(){ + switch($this->id){ + case self::IRON_AXE: + return 4; + case self::WOODEN_AXE: + return 1; + case self::STONE_AXE: + return 3; + case self::DIAMOND_AXE: + return 5; + case self::GOLD_AXE: + return 2; + default: + return false; + } + } + + final public function isSword(){ + switch($this->id){ + case self::IRON_SWORD: + return 4; + case self::WOODEN_SWORD: + return 1; + case self::STONE_SWORD: + return 3; + case self::DIAMOND_SWORD: + return 5; + case self::GOLD_SWORD: + return 2; + default: + return false; + } + } + + final public function isShovel(){ + switch($this->id){ + case self::IRON_SHOVEL: + return 4; + case self::WOODEN_SHOVEL: + return 1; + case self::STONE_SHOVEL: + return 3; + case self::DIAMOND_SHOVEL: + return 5; + case self::GOLD_SHOVEL: + return 2; + default: + return false; + } + } + + public function isHoe(){ + switch($this->id){ + case self::IRON_HOE: + case self::WOODEN_HOE: + case self::STONE_HOE: + case self::DIAMOND_HOE: + case self::GOLD_HOE: + return true; + default: + return false; + } + } + + public function isShears(){ + return ($this->id === self::SHEARS); + } + + final public function __toString(){ + return "Item " . $this->name . " (" . $this->id . ":" . $this->meta . ")"; + } + + public function getDestroySpeed(Block $block, Player $player){ + return 1; + } + + public function onActivate(Level $level, Player $player, Block $block, Block $target, $face, $fx, $fy, $fz){ + return false; + } + + public final function equals(Item $item, $checkDamage = false){ + return $this->id === $item->getID() and ($checkDamage === false or $this->getMetadata() === $item->getMetadata()); + } + +} diff --git a/src/pocketmine/item/MelonSeeds.php b/src/pocketmine/item/MelonSeeds.php new file mode 100644 index 000000000..37b2e598f --- /dev/null +++ b/src/pocketmine/item/MelonSeeds.php @@ -0,0 +1,31 @@ +block = Block::get(Item::MELON_STEM); + parent::__construct(self::MELON_SEEDS, 0, $count, "Melon Seeds"); + } +} \ No newline at end of file diff --git a/src/pocketmine/item/MushroomStew.php b/src/pocketmine/item/MushroomStew.php new file mode 100644 index 000000000..61a9b610a --- /dev/null +++ b/src/pocketmine/item/MushroomStew.php @@ -0,0 +1,31 @@ +maxStackSize = 1; + } + +} \ No newline at end of file diff --git a/src/pocketmine/item/Painting.php b/src/pocketmine/item/Painting.php new file mode 100644 index 000000000..d7cd4b961 --- /dev/null +++ b/src/pocketmine/item/Painting.php @@ -0,0 +1,95 @@ +isActivable = true; + } + + public function onActivate(Level $level, Player $player, Block $block, Block $target, $face, $fx, $fy, $fz){ + if($target->isTransparent === false and $face > 1 and $block->isSolid === false){ + $server = Server::getInstance(); + $faces = array( + 2 => 1, + 3 => 3, + 4 => 0, + 5 => 2, + + ); + $motives = array( + // Motive Width Height + array("Kebab", 1, 1), + array("Aztec", 1, 1), + array("Alban", 1, 1), + array("Aztec2", 1, 1), + array("Bomb", 1, 1), + array("Plant", 1, 1), + array("Wasteland", 1, 1), + array("Wanderer", 1, 2), + array("Graham", 1, 2), + array("Pool", 2, 1), + array("Courbet", 2, 1), + array("Sunset", 2, 1), + array("Sea", 2, 1), + array("Creebet", 2, 1), + array("Match", 2, 2), + array("Bust", 2, 2), + array("Stage", 2, 2), + array("Void", 2, 2), + array("SkullAndRoses", 2, 2), + //array("Wither", 2, 2), + array("Fighters", 4, 2), + array("Skeleton", 4, 3), + array("DonkeyKong", 4, 3), + array("Pointer", 4, 4), + array("Pigscene", 4, 4), + array("Flaming Skull", 4, 4), + ); + $motive = $motives[mt_rand(0, count($motives) - 1)]; + $data = array( + "x" => $target->x, + "y" => $target->y, + "z" => $target->z, + "yaw" => $faces[$face] * 90, + "Motive" => $motive[0], + ); + //TODO + //$e = $server->api->entity->add($level, ENTITY_OBJECT, OBJECT_PAINTING, $data); + //$e->spawnToAll(); + if(($player->gamemode & 0x01) === 0x00){ + $player->removeItem(Item::get($this->getID(), $this->getMetadata(), 1)); + } + + return true; + } + + return false; + } + +} \ No newline at end of file diff --git a/src/pocketmine/item/Potato.php b/src/pocketmine/item/Potato.php new file mode 100644 index 000000000..d68f86f9e --- /dev/null +++ b/src/pocketmine/item/Potato.php @@ -0,0 +1,31 @@ +block = Block::get(Item::POTATO_BLOCK); + parent::__construct(self::POTATO, 0, $count, "Potato"); + } +} \ No newline at end of file diff --git a/src/pocketmine/item/PumpkinSeeds.php b/src/pocketmine/item/PumpkinSeeds.php new file mode 100644 index 000000000..e15546331 --- /dev/null +++ b/src/pocketmine/item/PumpkinSeeds.php @@ -0,0 +1,31 @@ +block = Block::get(Item::PUMPKIN_STEM); + parent::__construct(self::PUMPKIN_SEEDS, 0, $count, "Pumpkin Seeds"); + } +} \ No newline at end of file diff --git a/src/pocketmine/item/Sign.php b/src/pocketmine/item/Sign.php new file mode 100644 index 000000000..ed06c6a16 --- /dev/null +++ b/src/pocketmine/item/Sign.php @@ -0,0 +1,32 @@ +block = Block::get(Item::SIGN_POST); + $this->maxStackSize = 16; + parent::__construct(self::SIGN, 0, $count, "Sign"); + } +} \ No newline at end of file diff --git a/src/pocketmine/item/SpawnEgg.php b/src/pocketmine/item/SpawnEgg.php new file mode 100644 index 000000000..547be3082 --- /dev/null +++ b/src/pocketmine/item/SpawnEgg.php @@ -0,0 +1,58 @@ +meta = $meta; + $this->isActivable = true; + } + + public function onActivate(Level $level, Player $player, Block $block, Block $target, $face, $fx, $fy, $fz){ + switch($this->meta){ + //TODO: use entity constants + case 10: + case 11: + case 12: + case 13: + $data = array( + "x" => $block->x + 0.5, + "y" => $block->y, + "z" => $block->z + 0.5, + ); + //$e = Server::getInstance()->api->entity->add($block->level, ENTITY_MOB, $this->meta, $data); + //Server::getInstance()->api->entity->spawnToAll($e); + if(($player->gamemode & 0x01) === 0){ + --$this->count; + } + + return true; + } + return false; + } +} \ No newline at end of file diff --git a/src/pocketmine/item/Stick.php b/src/pocketmine/item/Stick.php new file mode 100644 index 000000000..a71c5cb6c --- /dev/null +++ b/src/pocketmine/item/Stick.php @@ -0,0 +1,30 @@ +block = Block::get(Item::SUGARCANE_BLOCK); + parent::__construct(self::SUGARCANE, 0, $count, "Sugar Cane"); + } +} \ No newline at end of file diff --git a/src/pocketmine/item/WheatSeeds.php b/src/pocketmine/item/WheatSeeds.php new file mode 100644 index 000000000..2746fde9a --- /dev/null +++ b/src/pocketmine/item/WheatSeeds.php @@ -0,0 +1,31 @@ +block = Block::get(Item::WHEAT_BLOCK); + parent::__construct(self::WHEAT_SEEDS, 0, $count, "Wheat Seeds"); + } +} \ No newline at end of file diff --git a/src/pocketmine/item/WoodenAxe.php b/src/pocketmine/item/WoodenAxe.php new file mode 100644 index 000000000..59b7d3d7c --- /dev/null +++ b/src/pocketmine/item/WoodenAxe.php @@ -0,0 +1,30 @@ +block = Block::get(Item::WOODEN_DOOR_BLOCK); + parent::__construct(self::WOODEN_DOOR, 0, $count, "Wooden Door"); + $this->maxStackSize = 1; + } +} \ No newline at end of file diff --git a/src/pocketmine/item/WoodenPickaxe.php b/src/pocketmine/item/WoodenPickaxe.php new file mode 100644 index 000000000..599d255c1 --- /dev/null +++ b/src/pocketmine/item/WoodenPickaxe.php @@ -0,0 +1,30 @@ + Item::DIRT, + Item::STONE => Item::COBBLESTONE, + Item::COAL_ORE => Item::COAL, + Item::DIAMOND_ORE => Item::DIAMOND, + Item::REDSTONE_ORE => Item::REDSTONE, + ); + private $rays = 16; //Rays + public $level; + public $source; + public $size; + /** + * @var Block[] + */ + public $affectedBlocks = array(); + public $stepLen = 0.3; + private $what; + + public function __construct(Position $center, $size, $what = null){ + $this->level = $center->level; + $this->source = $center; + $this->size = max($size, 0); + $this->what = $what; + } + + public function explode(){ + if($this->size < 0.1){ + return false; + } + + $mRays = $this->rays - 1; + for($i = 0; $i < $this->rays; ++$i){ + for($j = 0; $j < $this->rays; ++$j){ + for($k = 0; $k < $this->rays; ++$k){ + if($i == 0 or $i == $mRays or $j == 0 or $j == $mRays or $k == 0 or $k == $mRays){ + $vector = new Vector3($i / $mRays * 2 - 1, $j / $mRays * 2 - 1, $k / $mRays * 2 - 1); //($i / $mRays) * 2 - 1 + $vector = $vector->normalize()->multiply($this->stepLen); + $pointer = clone $this->source; + + for($blastForce = $this->size * (mt_rand(700, 1300) / 1000); $blastForce > 0; $blastForce -= $this->stepLen * 0.75){ + $vBlock = $pointer->floor(); + $blockID = $this->level->level->getBlockID($vBlock->x, $vBlock->y, $vBlock->z); + + if($blockID > 0){ + $block = Block::get($blockID, 0); + $block->x = $vBlock->x; + $block->y = $vBlock->y; + $block->z = $vBlock->z; + $blastForce -= ($block->getHardness() / 5 + 0.3) * $this->stepLen; + if($blastForce > 0){ + $index = ($block->x << 15) + ($block->z << 7) + $block->y; + if(!isset($this->affectedBlocks[$index])){ + $this->affectedBlocks[$index] = $block; + } + } + } + $pointer = $pointer->add($vector); + } + } + } + } + } + + $send = array(); + $source = $this->source->floor(); + $radius = 2 * $this->size; + $yield = (1 / $this->size) * 100; + + if($this->what instanceof Entity){ + Server::getInstance()->getPluginManager()->callEvent($ev = new EntityExplodeEvent($this->what, $this->source, $this->affectedBlocks, $yield)); + if($ev->isCancelled()){ + return false; + }else{ + $yield = $ev->getYield(); + $this->affectedBlocks = $ev->getBlockList(); + } + } + + //TODO + /*foreach($server->api->entity->getRadius($this->source, $radius) as $entity){ + $impact = (1 - $this->source->distance($entity) / $radius) * 0.5; //placeholder, 0.7 should be exposure + $damage = (int) (($impact * $impact + $impact) * 8 * $this->size + 1); + $entity->harm($damage, "explosion"); + }*/ + + + foreach($this->affectedBlocks as $block){ + if($block instanceof TNT){ + $data = array( + "x" => $block->x + 0.5, + "y" => $block->y + 0.5, + "z" => $block->z + 0.5, + "power" => 4, + "fuse" => mt_rand(10, 30), //0.5 to 1.5 seconds + ); + //TODO + //$e = $server->api->entity->add($this->level, ENTITY_OBJECT, OBJECT_PRIMEDTNT, $data); + //$e->spawnToAll(); + }elseif(mt_rand(0, 100) < $yield){ + if(isset(self::$specialDrops[$block->getID()])){ + //TODO + //$server->api->entity->drop(new Position($block->x + 0.5, $block->y, $block->z + 0.5, $this->level), Item::get(self::$specialDrops[$block->getID()], 0)); + }else{ + //TODO + //$server->api->entity->drop(new Position($block->x + 0.5, $block->y, $block->z + 0.5, $this->level), Item::get($block->getID(), $this->level->level->getBlockDamage($block->x, $block->y, $block->z))); + } + } + $this->level->level->setBlockID($block->x, $block->y, $block->z, 0); + $send[] = new Vector3($block->x - $source->x, $block->y - $source->y, $block->z - $source->z); + } + $pk = new ExplodePacket; + $pk->x = $this->source->x; + $pk->y = $this->source->y; + $pk->z = $this->source->z; + $pk->radius = $this->size; + $pk->records = $send; + Player::broadcastPacket($this->level->getPlayers(), $pk); + + } +} diff --git a/src/pocketmine/level/Level.php b/src/pocketmine/level/Level.php new file mode 100644 index 000000000..da021e4dc --- /dev/null +++ b/src/pocketmine/level/Level.php @@ -0,0 +1,1384 @@ +getConfigString("level-name", null); + if($default == ""){ + trigger_error("level-name cannot be null", E_USER_ERROR); + + return; + } + if(self::loadLevel($default) === false){ + self::generateLevel($default, 0); //TODO: Server->getSeed(); + self::loadLevel($default); + } + self::$default = self::get($default); + } + } + + /** + * Saves all the levels + * + * @return void + */ + public static function saveAll(){ + foreach(self::$list as $level){ + $level->save(); + } + } + + /** + * Returns an array of all the loaded Levels + * + * @return Level[] + */ + public static function getAll(){ + return self::$list; + } + + /** + * Gets the default Level on the Server + * + * @return Level + */ + public static function getDefault(){ + return self::$default; + } + + /** + * Gets a loaded Level + * + * @param $name string Level name + * + * @return bool|Level + */ + public static function get($name){ + if($name !== "" and isset(self::$list[$name])){ + return self::$list[$name]; + } + + return false; + } + + /** + * Loads a level from the data directory + * + * @param $name + * + * @return bool + */ + public static function loadLevel($name){ + if(self::get($name) !== false){ + return true; + }elseif(self::levelExists($name) === false){ + console("[NOTICE] Level \"" . $name . "\" not found"); + + return false; + } + $path = \pocketmine\DATA . "worlds/" . $name . "/"; + console("[INFO] Preparing level \"" . $name . "\""); + $level = new LevelFormat($path . "level.pmf"); + if(!$level->isLoaded){ + console("[ERROR] Could not load level \"" . $name . "\""); + + return false; + } + //$entities = new Config($path."entities.yml", Config::YAML); + if(file_exists($path . "tileEntities.yml")){ + @rename($path . "tileEntities.yml", $path . "tiles.yml"); + } + $blockUpdates = new Config($path . "bupdates.yml", Config::YAML); + $level = new Level($level, $name); + /*foreach($entities->getAll() as $entity){ + if(!isset($entity["id"])){ + break; + } + if($entity["id"] === 64){ //Item Drop + $e = $this->server->api->entity->add($this->levels[$name], ENTITY_ITEM, $entity["Item"]["id"], array( + "meta" => $entity["Item"]["Damage"], + "stack" => $entity["Item"]["Count"], + "x" => $entity["Pos"][0], + "y" => $entity["Pos"][1], + "z" => $entity["Pos"][2], + "yaw" => $entity["Rotation"][0], + "pitch" => $entity["Rotation"][1], + )); + }elseif($entity["id"] === FALLING_SAND){ + $e = $this->server->api->entity->add($this->levels[$name], ENTITY_FALLING, $entity["id"], $entity); + $e->setPosition(new Vector3($entity["Pos"][0], $entity["Pos"][1], $entity["Pos"][2]), $entity["Rotation"][0], $entity["Rotation"][1]); + $e->setHealth($entity["Health"]); + }elseif($entity["id"] === OBJECT_PAINTING or $entity["id"] === OBJECT_ARROW){ //Painting + $e = $this->server->api->entity->add($this->levels[$name], ENTITY_OBJECT, $entity["id"], $entity); + $e->setPosition(new Vector3($entity["Pos"][0], $entity["Pos"][1], $entity["Pos"][2]), $entity["Rotation"][0], $entity["Rotation"][1]); + $e->setHealth(1); + }else{ + $e = $this->server->api->entity->add($this->levels[$name], ENTITY_MOB, $entity["id"], $entity); + $e->setPosition(new Vector3($entity["Pos"][0], $entity["Pos"][1], $entity["Pos"][2]), $entity["Rotation"][0], $entity["Rotation"][1]); + $e->setHealth($entity["Health"]); + } + }*/ + + if(file_exists($path . "tiles.yml")){ + $tiles = new Config($path . "tiles.yml", Config::YAML); + foreach($tiles->getAll() as $tile){ + if(!isset($tile["id"])){ + continue; + } + $level->loadChunk($tile["x"] >> 4, $tile["z"] >> 4); + + $nbt = new Compound(false, array()); + foreach($tile as $index => $data){ + switch($index){ + case "Items": + $tag = new Enum("Items", array()); + $tag->setTagType(NBT::TAG_Compound); + foreach($data as $slot => $fields){ + $tag[(int) $slot] = new Compound(false, array( + "Count" => new Byte("Count", $fields["Count"]), + "Slot" => new Short("Slot", $fields["Slot"]), + "Damage" => new Short("Damage", $fields["Damage"]), + "id" => new String("id", $fields["id"]) + )); + } + $nbt["Items"] = $tag; + break; + + case "id": + case "Text1": + case "Text2": + case "Text3": + case "Text4": + $nbt[$index] = new String($index, $data); + break; + + case "x": + case "y": + case "z": + case "pairx": + case "pairz": + $nbt[$index] = new Int($index, $data); + break; + + case "BurnTime": + case "CookTime": + case "MaxTime": + $nbt[$index] = new Short($index, $data); + break; + } + } + switch($tile["id"]){ + case Tile::FURNACE: + new Furnace($level, $nbt); + break; + case Tile::CHEST: + new Chest($level, $nbt); + break; + case Tile::SIGN: + new Sign($level, $nbt); + break; + } + } + unlink($path . "tiles.yml"); + $level->save(true, true); + } + + //TODO + /*foreach($blockUpdates->getAll() as $bupdate){ + Server::getInstance()->api->block->scheduleBlockUpdate(new Position((int) $bupdate["x"], (int) $bupdate["y"], (int) $bupdate["z"], $level), (float) $bupdate["delay"], (int) $bupdate["type"]); + }*/ + + return true; + } + + /** + * Generates a new level + * + * @param string $name + * @param bool $seed + * @param bool $generator + * @param bool|array $options + * + * @return bool + */ + public static function generateLevel($name, $seed = false, $generator = false, $options = false){ + if($name == "" or self::levelExists($name)){ + return false; + } + $options = array(); + if($options === false and trim(Server::getInstance()->getConfigString("generator-settings", "")) !== ""){ + $options["preset"] = Server::getInstance()->getConfigString("generator-settings", ""); + } + + if($generator !== false and class_exists($generator)){ + $generator = new $generator($options); + }else{ + if(strtoupper(Server::getInstance()->getLevelType()) == "FLAT"){ + $generator = new Flat($options); + }else{ + $generator = new Normal($options); + } + } + $gen = new WorldGenerator($generator, $name, $seed === false ? Utils::readInt(Utils::getRandomBytes(4, false)) : (int) $seed); + $gen->generate(); + $gen->close(); + + return true; + } + + /** + * Searches if a level exists on file + * + * @param string $name + * + * @return bool + */ + public static function levelExists($name){ + if($name === ""){ + return false; + } + $path = \pocketmine\DATA . "worlds/" . $name . "/"; + if(self::get($name) === false and !file_exists($path . "level.pmf")){ + if(file_exists($path)){ + $level = new LevelImport($path); + if($level->import() === false){ + return false; + } + }else{ + return false; + } + } + + return true; + } + + public function __construct(LevelFormat $level, $name){ + $this->server = Server::getInstance(); + $this->level = $level; + $this->level->level = $this; + $this->startTime = $this->time = (int) $this->level->getData("time"); + $this->nextSave = $this->startCheck = microtime(true); + $this->nextSave += 90; + $this->stopTime = false; + $this->name = $name; + $this->usedChunks = array(); + $this->changedBlocks = array(); + $this->changedCount = array(); + $gen = Generator::getGenerator($this->level->levelData["generator"]); + $this->generator = new $gen((array) $this->level->levelData["generatorSettings"]); + $this->generator->init($this, new Random($this->level->levelData["seed"])); + self::$list[$name] = $this; + } + + public function close(){ + $this->__destruct(); + } + + + /** + * Unloads the current level from memory safely + * + * @param bool $force default false, force unload of default level + * + * @return bool + */ + public function unload($force = false){ + if($this === self::getDefault() and $force !== true){ + return false; + } + console("[INFO] Unloading level \"" . $this->getName() . "\""); + $this->nextSave = PHP_INT_MAX; + $this->save(); + foreach($this->getPlayers() as $player){ + if($this === self::getDefault()){ + $player->close($player->getName() . " has left the game", "forced level unload"); + }else{ + $player->teleport(Level::getDefault()->getSafeSpawn()); + } + } + $this->close(); + if($this === self::getDefault()){ + self::$default = null; + } + + return true; + } + + /** + * Gets the chunks being used by players + * + * @param int $X + * @param int $Z + * + * @return Player[][] + */ + public function getUsingChunk($X, $Z){ + $index = LevelFormat::getIndex($X, $Z); + + return isset($this->usedChunks[$index]) ? $this->usedChunks[$index] : array(); + } + + /** + * WARNING: Do not use this, it's only for internal use. + * Changes to this function won't be recorded on the version. + * + * @param int $X + * @param int $Z + * @param Player $player + */ + public function useChunk($X, $Z, Player $player){ + $index = LevelFormat::getIndex($X, $Z); + $this->loadChunk($X, $Z); + $this->usedChunks[$index][$player->CID] = $player; + } + + /** + * WARNING: Do not use this, it's only for internal use. + * Changes to this function won't be recorded on the version. + * + * @param Player $player + */ + public function freeAllChunks(Player $player){ + foreach($this->usedChunks as $i => $c){ + unset($this->usedChunks[$i][$player->CID]); + } + } + + /** + * WARNING: Do not use this, it's only for internal use. + * Changes to this function won't be recorded on the version. + * + * @param int $X + * @param int $Z + * @param Player $player + */ + public function freeChunk($X, $Z, Player $player){ + unset($this->usedChunks[LevelFormat::getIndex($X, $Z)][$player->CID]); + } + + /** + * @param int $X + * @param int $Z + * + * @return bool + */ + public function isChunkPopulated($X, $Z){ + return $this->level->isPopulated($X, $Z); + } + + /** + * WARNING: Do not use this, it's only for internal use. + * Changes to this function won't be recorded on the version. + */ + public function checkTime(){ + if(!isset($this->level)){ + return; + } + $now = microtime(true); + if($this->stopTime == true){ + return; + }else{ + $time = $this->startTime + ($now - $this->startCheck) * 20; + } + + $this->time = $time; + $pk = new SetTimePacket; + $pk->time = (int) $this->time; + $pk->started = $this->stopTime == false; + Player::broadcastPacket($this->players, $pk); + + return; + } + + /** + * WARNING: Do not use this, it's only for internal use. + * Changes to this function won't be recorded on the version. + * + * @return bool + */ + public function doTick(){ + if(!isset($this->level)){ + return false; + } + + if(($this->server->getTick() % 200) === 0){ + $this->checkTime(); + } + + if($this->level->isGenerating === 0 and count($this->changedCount) > 0){ + foreach($this->changedCount as $index => $mini){ + for($Y = 0; $Y < 8; ++$Y){ + if(($mini & (1 << $Y)) === 0){ + continue; + } + if(count($this->changedBlocks[$index][$Y]) < 582){ //Optimal value, calculated using the relation between minichunks and single packets + continue; + }else{ + foreach($this->players as $p){ + $p->setChunkIndex($index, $mini); + } + unset($this->changedBlocks[$index][$Y]); + } + } + } + $this->changedCount = array(); + + if(count($this->changedBlocks) > 0){ + foreach($this->changedBlocks as $index => $mini){ + foreach($mini as $blocks){ + foreach($blocks as $b){ + $pk = new UpdateBlockPacket; + $pk->x = $b->x; + $pk->y = $b->y; + $pk->z = $b->z; + $pk->block = $b->getID(); + $pk->meta = $b->getMetadata(); + Player::broadcastPacket($this->players, $pk); + } + } + } + $this->changedBlocks = array(); + } + + $X = null; + $Z = null; + + //Do chunk updates + foreach($this->usedChunks as $index => $p){ + LevelFormat::getXZ($index, $X, $Z); + for($Y = 0; $Y < 8; ++$Y){ + if(!$this->level->isMiniChunkEmpty($X, $Z, $Y)){ + for($i = 0; $i < 3; ++$i){ + $block = $this->getBlockRaw(new Vector3(($X << 4) + mt_rand(0, 15), ($Y << 4) + mt_rand(0, 15), ($Z << 4) + mt_rand(0, 15))); + if($block instanceof Block){ + if($block->onUpdate(self::BLOCK_UPDATE_RANDOM) === self::BLOCK_UPDATE_NORMAL){ + //TODO + //$this->server->api->block->blockUpdateAround($block); + } + } + } + } + } + } + } + + if($this->nextSave < microtime(true)){ + $X = null; + $Z = null; + foreach($this->usedChunks as $i => $c){ + if(count($c) === 0){ + unset($this->usedChunks[$i]); + LevelFormat::getXZ($i, $X, $Z); + if(!$this->isSpawnChunk($X, $Z)){ + $this->level->unloadChunk($X, $Z, self::$saveEnabled); + } + } + } + $this->save(false, false); + } + } + + /** + * @param int $X + * @param int $Z + * + * @return bool + */ + public function generateChunk($X, $Z){ + ++$this->level->isGenerating; + $this->generator->generateChunk($X, $Z); + --$this->level->isGenerating; + + return true; + } + + /** + * @param int $X + * @param int $Z + * + * @return bool + */ + public function populateChunk($X, $Z){ + $this->level->setPopulated($X, $Z); + $this->generator->populateChunk($X, $Z); + + return true; + } + + public function __destruct(){ + unset(self::$list[$this->getName()]); + if(isset($this->level)){ + $this->save(false, false); + $this->level->closeLevel(); + unset($this->level); + } + } + + /** + * @param bool $force + * @param bool $extra + * + * @return bool + */ + public function save($force = false, $extra = true){ + if(!isset($this->level)){ + return false; + } + //TODO: save enabled/disabled + /*if(self::$saveEnabled === false and $force === false){ + return; + }*/ + + if($extra !== false){ + $this->doSaveRoundExtra(); + } + + $this->level->setData("time", (int) $this->time); + $this->level->doSaveRound($force); + $this->level->saveData(); + $this->nextSave = microtime(true) + 45; + } + + protected function doSaveRoundExtra(){ + foreach($this->usedChunks as $index => $d){ + LevelFormat::getXZ($index, $X, $Z); + $nbt = new Compound("", array( + new Enum("Entities", array()), + new Enum("TileEntities", array()), + )); + $nbt->Entities->setTagType(NBT::TAG_Compound); + $nbt->TileEntities->setTagType(NBT::TAG_Compound); + + $i = 0; + foreach($this->chunkEntities[$index] as $entity){ + if($entity->closed !== true){ + $entity->saveNBT(); + $nbt->Entities[$i] = $entity->namedtag; + ++$i; + } + } + + $i = 0; + foreach($this->chunkTiles[$index] as $tile){ + if($tile->closed !== true){ + $nbt->TileEntities[$i] = $tile->namedtag; + ++$i; + } + } + + $this->level->setChunkNBT($X, $Z, $nbt); + } + } + + /** + * @param Vector3 $pos + * + * @return Block + */ + public function getBlockRaw(Vector3 $pos){ + $b = $this->level->getBlock($pos->x, $pos->y, $pos->z); + + return Block::get($b[0], $b[1], new Position($pos->x, $pos->y, $pos->z, $this)); + } + + /** + * @param Vector3 $pos + * + * @return bool|Block + */ + public function getBlock(Vector3 $pos){ + if($pos instanceof Position and $pos->level !== $this){ + return false; + } + $b = $this->level->getBlock($pos->x, $pos->y, $pos->z); + + return Block::get($b[0], $b[1], new Position($pos->x, $pos->y, $pos->z, $this)); + } + + /** + * @param Vector3 $pos + * @param Block $block + * @param bool $direct + * @param bool $send + * + * @return bool + */ + public function setBlockRaw(Vector3 $pos, Block $block, $direct = true, $send = true){ + if(($ret = $this->level->setBlock($pos->x, $pos->y, $pos->z, $block->getID(), $block->getMetadata())) === true and $send !== false){ + if($direct === true){ + $pk = new UpdateBlockPacket; + $pk->x = $pos->x; + $pk->y = $pos->y; + $pk->z = $pos->z; + $pk->block = $block->getID(); + $pk->meta = $block->getMetadata(); + Player::broadcastPacket($this->players, $pk); + }elseif($direct === false){ + if(!($pos instanceof Position)){ + $pos = new Position($pos->x, $pos->y, $pos->z, $this); + } + $block->position($pos); + $index = LevelFormat::getIndex($pos->x >> 4, $pos->z >> 4); + if(ADVANCED_CACHE == true){ + Cache::remove("world:{$this->name}:{$index}"); + } + if(!isset($this->changedBlocks[$index])){ + $this->changedBlocks[$index] = array(); + $this->changedCount[$index] = 0; + } + $Y = $pos->y >> 4; + if(!isset($this->changedBlocks[$index][$Y])){ + $this->changedBlocks[$index][$Y] = array(); + $this->changedCount[$index] |= 1 << $Y; + } + $this->changedBlocks[$index][$Y][] = clone $block; + } + } + + return $ret; + } + + /** + * @param Vector3 $pos + * @param Block $block + * @param bool $update + * @param bool $tiles + * @param bool $direct + * + * @return bool + */ + public function setBlock(Vector3 $pos, Block $block, $update = true, $tiles = false, $direct = false){ + if((($pos instanceof Position) and $pos->level !== $this) or $pos->x < 0 or $pos->y < 0 or $pos->z < 0){ + return false; + } + + $ret = $this->level->setBlock($pos->x, $pos->y, $pos->z, $block->getID(), $block->getMetadata()); + if($ret === true){ + if(!($pos instanceof Position)){ + $pos = new Position($pos->x, $pos->y, $pos->z, $this); + } + $block->position($pos); + + if($direct === true){ + $pk = new UpdateBlockPacket; + $pk->x = $pos->x; + $pk->y = $pos->y; + $pk->z = $pos->z; + $pk->block = $block->getID(); + $pk->meta = $block->getMetadata(); + Player::broadcastPacket($this->players, $pk); + }else{ + $index = LevelFormat::getIndex($pos->x >> 4, $pos->z >> 4); + if(ADVANCED_CACHE == true){ + Cache::remove("world:{$this->name}:{$index}"); + } + if(!isset($this->changedBlocks[$index])){ + $this->changedBlocks[$index] = array(); + $this->changedCount[$index] = 0; + } + $Y = $pos->y >> 4; + if(!isset($this->changedBlocks[$index][$Y])){ + $this->changedBlocks[$index][$Y] = array(); + $this->changedCount[$index] |= 1 << $Y; + } + $this->changedBlocks[$index][$Y][] = clone $block; + } + + if($update === true){ + //TODO + //$this->server->api->block->blockUpdateAround($pos, self::BLOCK_UPDATE_NORMAL, 1); + } + if($tiles === true){ + if(($t = $this->getTile($pos)) instanceof Tile){ + $t->close(); + } + } + } + + return $ret; + } + + /** + * Tries to break a block using a item, including Player time checks if available + * + * @param Vector3 $vector + * @param Item &$item (if null, can break anything) + * @param Player $player + * + * @return boolean + */ + public function useBreakOn(Vector3 $vector, Item &$item = null, Player $player = null){ + $target = $this->getBlock($vector); + + if($player instanceof Player){ + $lastTime = $player->lastBreak - $player->getLag() / 1000; + if(($player->getGamemode() & 0x01) === 1 and ($lastTime + 0.15) >= microtime(true)){ + return false; + }elseif(($lastTime + $target->getBreakTime($item)) >= microtime(true)){ + return false; + } + $player->lastBreak = microtime(true); + } + + //TODO: Adventure mode checks + if($player instanceof Player){ + $ev = new BlockBreakEvent($player, $target, $item, ($player->getGamemode() & 0x01) === 1 ? true : false); + if($item instanceof Item and !$target->isBreakable($item) and $ev->getInstaBreak() === false){ + $ev->setCancelled(); + } + $this->server->getPluginManager()->callEvent($ev); + if($ev->isCancelled()){ + return false; + } + }elseif($item instanceof Item and !$target->isBreakable($item)){ + return false; + } + + $target->onBreak($item); + if($item instanceof Item){ + $item->useOn($target); + if($item->isTool() and $item->getMetadata() >= $item->getMaxDurability()){ + $item = Item::get(Item::AIR, 0, 0); + } + } + + return $target->getDrops($item); + } + + /** + * Uses a item on a position and face, placing it or activating the block + * + * @param Vector3 $vector + * @param Item &$item + * @param int $face + * @param float $fx default 0.0 + * @param float $fy default 0.0 + * @param float $fz default 0.0 + * @param Player $player default null + * + * @return boolean + */ + public function useItemOn(Vector3 $vector, Item &$item, $face, $fx = 0.0, $fy = 0.0, $fz = 0.0, Player $player = null){ + $target = $this->getBlock($vector); + $block = $target->getSide($face); + + if($block->y > 127 or $block->y < 0){ + return false; + } + + if($target->getID() === Item::AIR){ + return false; + } + + if($player instanceof Player){ + $this->server->getPluginManager()->callEvent($ev = new PlayerInteractEvent($player, $item, $target, $face)); + if(!$ev->isCancelled()){ + $target->onUpdate(Level::BLOCK_UPDATE_TOUCH); + } + } + + if($target->isActivable === true and $target->onActivate($item, $player) === true){ + return true; + } + + if($item->isPlaceable()){ + $hand = $item->getBlock(); + $hand->position($block); + }elseif($block->getID() === Item::FIRE){ + $this->setBlock($block, new Air(), true, false, true); + + return false; + }else{ + return false; + } + + if(!($block->isReplaceable === true or ($hand->getID() === Item::SLAB and $block->getID() === Item::SLAB))){ + return false; + } + + if($target->isReplaceable === true){ + $block = $target; + $hand->position($block); + //$face = -1; + } + + //TODO: Implement using Bounding Boxes, all entities + /*if($hand->isSolid === true and $player->inBlock($block)){ + return false; //Entity in block + }*/ + + + if($player instanceof Player){ + $this->server->getPluginManager()->callEvent($ev = new BlockPlaceEvent($player, $hand, $block, $target, $item)); + if($ev->isCancelled()){ + return false; + } + }elseif($hand->place($item, $block, $target, $face, $fx, $fy, $fz, $player) === false){ + return false; + } + + if($hand->getID() === Item::SIGN_POST or $hand->getID() === Item::WALL_SIGN){ + $tile = new Sign($this, new Compound(false, array( + new String("id", Tile::SIGN), + new Int("x", $block->x), + new Int("y", $block->y), + new Int("z", $block->z), + new String("Text1", ""), + new String("Text2", ""), + new String("Text3", ""), + new String("Text4", "") + ))); + if($player instanceof Player){ + $tile->namedtag->creator = new String("creator", $player->getName()); + } + } + $item->setCount($item->getCount() - 1); + if($item->getCount() <= 0){ + $item = Item::get(Item::AIR, 0, 0); + } + + return true; + } + + /** + * Gets the biome ID of a column + * + * @param int $x + * @param int $z + * + * @return int + */ + public function getBiome($x, $z){ + return $this->level->getBiome((int) $x, (int) $z); + } + + /** + * Sets the biome ID for a column + * + * @param int $x + * @param int $z + * @param int $biome + * + * @return int + */ + public function setBiome($x, $z, $biome){ + return $this->level->getBiome((int) $x, (int) $z, $biome); + } + + /** + * Gets the list of all the entitites in this level + * + * @return Entity[] + */ + public function getEntities(){ + return $this->entities; + } + + /** + * Returns a list of the Tile entities in this level + * + * @return Tile[] + */ + public function getTiles(){ + return $this->tiles; + } + + /** + * Returns a list of the players in this level + * + * @return Player[] + */ + public function getPlayers(){ + return $this->players; + } + + /** + * Returns the Tile in a position, or false if not found + * + * @param Vector3 $pos + * + * @return bool|Tile + */ + public function getTile(Vector3 $pos){ + if($pos instanceof Position and $pos->level !== $this){ + return false; + } + $tiles = $this->getChunkTiles($pos->x >> 4, $pos->z >> 4); + if(count($tiles) > 0){ + foreach($tiles as $tile){ + if($tile->x === (int) $pos->x and $tile->y === (int) $pos->y and $tile->z === (int) $pos->z){ + return $tile; + } + } + } + + return false; + } + + /** + * Gets a raw minichunk + * + * @param int $X + * @param int $Z + * @param int $Y + * + * @return string + */ + public function getMiniChunk($X, $Z, $Y){ + return $this->level->getMiniChunk($X, $Z, $Y); + } + + /** + * Sets a raw minichunk + * + * @param int $X + * @param int $Z + * @param int $Y + * @param string $data (must be 4096 bytes) + * + * @return bool + */ + public function setMiniChunk($X, $Z, $Y, $data){ + $this->changedCount[$X . ":" . $Y . ":" . $Z] = 4096; + if(ADVANCED_CACHE == true){ + Cache::remove("world:{$this->name}:$X:$Z"); + } + + return $this->level->setMiniChunk($X, $Z, $Y, $data); + } + + /** + * Returns a list of the entities on a given chunk + * + * @param int $X + * @param int $Z + * + * @return Entity[] + */ + public function getChunkEntities($X, $Z){ + $index = LevelFormat::getIndex($X, $Z); + if(isset($this->usedChunks[$index]) or $this->loadChunk($X, $Z) === true){ + return $this->chunkEntities[$index]; + } + + return array(); + } + + /** + * Gives a list of the Tile entities on a given chunk + * + * @param int $X + * @param int $Z + * + * @return Tile[] + */ + public function getChunkTiles($X, $Z){ + $index = LevelFormat::getIndex($X, $Z); + if(isset($this->usedChunks[$index]) or $this->loadChunk($X, $Z) === true){ + return $this->chunkTiles[$index]; + } + + return array(); + } + + /** + * Loads a chunk + * + * @param int $X + * @param int $Z + * + * @return bool + */ + public function loadChunk($X, $Z){ + $index = LevelFormat::getIndex($X, $Z); + if(isset($this->usedChunks[$index])){ + return true; + }elseif($this->level->loadChunk($X, $Z) !== false){ + $this->usedChunks[$index] = array(); + $this->chunkTiles[$index] = array(); + $this->chunkEntities[$index] = array(); + $tags = $this->level->getChunkNBT($X, $Z); + if(isset($tags->Entities)){ + foreach($tags->Entities as $nbt){ + if(!isset($nbt["id"])){ + continue; + } + switch($nbt["id"]){ + //TODO: spawn entities + } + } + } + if(isset($tags->TileEntities)){ + foreach($tags->TileEntities as $nbt){ + switch($nbt["id"]){ + case Tile::CHEST: + new Chest($this, $nbt); + break; + case Tile::FURNACE: + new Furnace($this, $nbt); + break; + case Tile::SIGN: + new Sign($this, $nbt); + break; + } + } + } + + return true; + } + + return false; + } + + /** + * Unloads a chunk + * + * @param int $X + * @param int $Z + * @param bool $force + * + * @return bool + */ + public function unloadChunk($X, $Z, $force = false){ + if(!isset($this->level)){ + return false; + } + + if($force !== true and $this->isSpawnChunk($X, $Z)){ + return false; + } + $index = LevelFormat::getIndex($X, $Z); + unset($this->usedChunks[$index]); + unset($this->chunkEntities[$index]); + unset($this->chunkTiles[$index]); + Cache::remove("world:{$this->name}:$X:$Z"); + + return $this->level->unloadChunk($X, $Z, self::$saveEnabled); + } + + /** + * Returns true if the spawn is part of the spawn + * + * @param int $X + * @param int $Z + * + * @return bool + */ + public function isSpawnChunk($X, $Z){ + $spawnX = $this->level->getData("spawnX") >> 4; + $spawnZ = $this->level->getData("spawnZ") >> 4; + + return abs($X - $spawnX) <= 1 and abs($Z - $spawnZ) <= 1; + } + + /** + * Gets a full chunk or parts of it for networking usage, allows cache usage + * + * @param int $X + * @param int $Z + * @param int $Yndex bitmap of chunks to be returned + * + * @return bool|mixed|string + */ + public function getOrderedChunk($X, $Z, $Yndex){ + if(!isset($this->level)){ + return false; + } + if(ADVANCED_CACHE == true and $Yndex === 0xff){ + $identifier = "world:{$this->name}:" . LevelFormat::getIndex($X, $Z); + if(($cache = Cache::get($identifier)) !== false){ + return $cache; + } + } + + + $raw = array(); + for($Y = 0; $Y < 8; ++$Y){ + if(($Yndex & (1 << $Y)) !== 0){ + $raw[$Y] = $this->level->getMiniChunk($X, $Z, $Y); + } + } + + $ordered = ""; + $flag = chr($Yndex); + for($j = 0; $j < 256; ++$j){ + $ordered .= $flag; + foreach($raw as $mini){ + $ordered .= substr($mini, $j << 5, 24); //16 + 8 + } + } + if(ADVANCED_CACHE == true and $Yndex == 0xff){ + Cache::add($identifier, $ordered, 60); + } + + return $ordered; + } + + /** + * Returns the network minichunk for a given Y + * + * @param int $X + * @param int $Z + * @param int $Y + * + * @return bool|string + */ + public function getOrderedMiniChunk($X, $Z, $Y){ + if(!isset($this->level)){ + return false; + } + $raw = $this->level->getMiniChunk($X, $Z, $Y); + $ordered = ""; + $flag = chr(1 << $Y); + for($j = 0; $j < 256; ++$j){ + $ordered .= $flag . substr($raw, $j << 5, 24); //16 + 8 + } + + return $ordered; + } + + /** + * Returns the raw spawnpoint + * + * @return Position + */ + public function getSpawn(){ + return new Position($this->level->getData("spawnX"), $this->level->getData("spawnY"), $this->level->getData("spawnZ"), $this); + } + + /** + * @param Vector3 $spawn default null + * + * @return bool|Position + */ + public function getSafeSpawn($spawn = null){ + if(!($spawn instanceof Vector3)){ + $spawn = $this->getSpawn(); + } + if($spawn instanceof Vector3){ + $x = (int) round($spawn->x); + $y = (int) round($spawn->y); + $z = (int) round($spawn->z); + for(; $y > 0; --$y){ + $v = new Vector3($x, $y, $z); + $b = $this->getBlock($v->getSide(0)); + if($b === false){ + return $spawn; + }elseif(!($b instanceof Air)){ + break; + } + } + for(; $y < 128; ++$y){ + $v = new Vector3($x, $y, $z); + if($this->getBlock($v->getSide(1)) instanceof Air){ + if($this->getBlock($v) instanceof Air){ + return new Position($x, $y, $z, $this); + } + }else{ + ++$y; + } + } + + return new Position($x, $y, $z, $this); + } + + return false; + } + + /** + * Sets the spawnpoint + * + * @param Vector3 $pos + */ + public function setSpawn(Vector3 $pos){ + $this->level->setData("spawnX", $pos->x); + $this->level->setData("spawnY", $pos->y); + $this->level->setData("spawnZ", $pos->z); + } + + /** + * Gets the current time + * + * @return int + */ + public function getTime(){ + return (int) $this->time; + } + + /** + * Returns the Level name + * + * @return string + */ + public function getName(){ + return $this->name; //return $this->level->getData("name"); + } + + /** + * Sets the current time on the level + * + * @param int $time + */ + public function setTime($time){ + $this->startTime = $this->time = (int) $time; + $this->startCheck = microtime(true); + $this->checkTime(); + } + + /** + * Stops the time for the level, will not save the lock state to disk + */ + public function stopTime(){ + $this->stopTime = true; + $this->startCheck = 0; + $this->checkTime(); + } + + /** + * Start the time again, if it was stopped + */ + public function startTime(){ + $this->stopTime = false; + $this->startCheck = microtime(true); + $this->checkTime(); + } + + /** + * Gets the level seed + * + * @return int + */ + public function getSeed(){ + return (int) $this->level->getData("seed"); + } + + /** + * Sets the seed for the level + * + * @param int $seed + * + * @return bool + */ + public function setSeed($seed){ + if(!isset($this->level)){ + return false; + } + $this->level->setData("seed", (int) $seed); + } + + public function scheduleBlockUpdate(Position $pos, $delay, $type = self::BLOCK_UPDATE_SCHEDULED){ + //TODO + //return $this->server->api->block->scheduleBlockUpdate($pos, $delay, $type); + } +} diff --git a/src/pocketmine/level/LevelImport.php b/src/pocketmine/level/LevelImport.php new file mode 100644 index 000000000..73d24e0ae --- /dev/null +++ b/src/pocketmine/level/LevelImport.php @@ -0,0 +1,129 @@ +path = $path; + } + + public function import(){ + if(file_exists($this->path . "tileEntities.dat")){ //OldPM + $level = unserialize(file_get_contents($this->path . "level.dat")); + console("[INFO] Importing OldPM level \"" . $level["LevelName"] . "\" to PMF format"); + $entities = new Config($this->path . "entities.yml", Config::YAML, unserialize(file_get_contents($this->path . "entities.dat"))); + $entities->save(); + $tiles = new Config($this->path . "tiles.yml", Config::YAML, unserialize(file_get_contents($this->path . "tileEntities.dat"))); + $tiles->save(); + }elseif(file_exists($this->path . "chunks.dat") and file_exists($this->path . "level.dat")){ //Pocket + $nbt = new NBT(NBT::LITTLE_ENDIAN); + $nbt->read(substr(file_get_contents($this->path . "level.dat"), 8)); + $level = $nbt->getData(); + if($level["LevelName"] == ""){ + $level["LevelName"] = "world" . time(); + } + console("[INFO] Importing Pocket level \"" . $level->LevelName . "\" to PMF format"); + unset($level->Player); + $nbt->read(substr(file_get_contents($this->path . "entities.dat"), 12)); + $entities = $nbt->getData(); + if(!isset($entities->TileEntities)){ + $entities->TileEntities = array(); + } + $tiles = $entities->TileEntities; + $entities = $entities->Entities; + $entities = new Config($this->path . "entities.yml", Config::YAML, $entities); + $entities->save(); + $tiles = new Config($this->path . "tiles.yml", Config::YAML, $tiles); + $tiles->save(); + }else{ + return false; + } + + $pmf = new LevelFormat($this->path . "level.pmf", array( + "name" => $level->LevelName, + "seed" => $level->RandomSeed, + "time" => $level->Time, + "spawnX" => $level->SpawnX, + "spawnY" => $level->SpawnY, + "spawnZ" => $level->SpawnZ, + "height" => 8, + "generator" => "default", + "generatorSettings" => "", + "extra" => "" + )); + $chunks = new PocketChunkParser(); + $chunks->loadFile($this->path . "chunks.dat"); + $chunks->loadMap(); + for($Z = 0; $Z < 16; ++$Z){ + for($X = 0; $X < 16; ++$X){ + $chunk = array( + 0 => "", + 1 => "", + 2 => "", + 3 => "", + 4 => "", + 5 => "", + 6 => "", + 7 => "" + ); + + $pmf->initCleanChunk($X, $Z); + for($z = 0; $z < 16; ++$z){ + for($x = 0; $x < 16; ++$x){ + $block = $chunks->getChunkColumn($X, $Z, $x, $z, 0); + $meta = $chunks->getChunkColumn($X, $Z, $x, $z, 1); + for($Y = 0; $Y < 8; ++$Y){ + $chunk[$Y] .= substr($block, $Y << 4, 16); + $chunk[$Y] .= substr($meta, $Y << 3, 8); + $chunk[$Y] .= "\x00\x00\x00\x00\x00\x00\x00\x00"; + } + } + } + foreach($chunk as $Y => $data){ + $pmf->setMiniChunk($X, $Z, $Y, $data); + } + $pmf->setPopulated($X, $Z); + $pmf->saveChunk($X, $Z); + } + console("[NOTICE] Importing level " . ceil(($Z + 1) / 0.16) . "%"); + } + $chunks->map = null; + $chunks = null; + @unlink($this->path . "level.dat"); + @unlink($this->path . "level.dat_old"); + @unlink($this->path . "player.dat"); + @unlink($this->path . "entities.dat"); + @unlink($this->path . "chunks.dat"); + @unlink($this->path . "chunks.dat.gz"); + @unlink($this->path . "tiles.dat"); + unset($chunks, $level, $entities, $tiles, $nbt); + + return true; + } + +} \ No newline at end of file diff --git a/src/pocketmine/level/PocketChunkParser.php b/src/pocketmine/level/PocketChunkParser.php new file mode 100644 index 000000000..b5187115a --- /dev/null +++ b/src/pocketmine/level/PocketChunkParser.php @@ -0,0 +1,234 @@ +location = array(); + console("[DEBUG] Loading Chunk Location table...", true, true, 2); + for($offset = 0; $offset < 0x1000; $offset += 4){ + $data = Utils::readLInt(substr($this->raw, $offset, 4)); + $sectors = $data & 0xff; + if($sectors === 0){ + continue; + } + $sectorLocation = $data >> 8; + $this->location[$offset >> 2] = $sectorLocation * $this->sectorLength; //$this->getOffset($X, $Z, $sectors); + } + } + + public function loadFile($file){ + if(file_exists($file . ".gz")){ + $this->raw = gzinflate(file_get_contents($file . ".gz")); + $r = @gzinflate($this->raw); + if($r !== false and $r != ""){ + $this->raw = $r; + } + @unlink($file . ".gz"); + file_put_contents($file, $this->raw); + }elseif(!file_exists($file)){ + return false; + }else{ + $this->raw = file_get_contents($file); + } + $this->file = $file; + $this->chunkLength = $this->sectorLength * ord($this->raw{0}); + + return true; + } + + public function loadRaw($raw, $file){ + $this->file = $file; + $this->raw = $raw; + $this->chunkLength = $this->sectorLength * ord($this->raw{0}); + + return true; + } + + private function getOffset($X, $Z){ + return $this->location[$X + ($Z << 5)]; + } + + public function getChunk($X, $Z){ + $X = (int) $X; + $Z = (int) $Z; + + return substr($this->raw, $this->getOffset($X, $Z), $this->chunkLength); + } + + public function writeChunk($X, $Z){ + $X = (int) $X; + $Z = (int) $Z; + if(!isset($this->map[$X][$Z])){ + return false; + } + $chunk = ""; + foreach($this->map[$X][$Z] as $section => $data){ + for($i = 0; $i < 256; ++$i){ + $chunk .= $data[$i]; + } + } + + return Utils::writeLInt(strlen($chunk)) . $chunk; + } + + public function parseChunk($X, $Z){ + $X = (int) $X; + $Z = (int) $Z; + $offset = $this->getOffset($X, $Z); + $len = Utils::readLInt(substr($this->raw, $offset, 4)); + $offset += 4; + $chunk = array( + 0 => array(), //Block + 1 => array(), //Data + 2 => array(), //SkyLight + 3 => array(), //BlockLight + ); + foreach($chunk as $section => &$data){ + $l = $section === 0 ? 128 : 64; + for($i = 0; $i < 256; ++$i){ + $data[$i] = substr($this->raw, $offset, $l); + $offset += $l; + } + } + + return $chunk; + } + + public function loadMap(){ + if($this->raw == ""){ + return false; + } + $this->loadLocationTable(); + console("[DEBUG] Loading chunks...", true, true, 2); + for($x = 0; $x < 16; ++$x){ + $this->map[$x] = array(); + for($z = 0; $z < 16; ++$z){ + $this->map[$x][$z] = $this->parseChunk($x, $z); + } + } + $this->raw = ""; + console("[DEBUG] Chunks loaded!", true, true, 2); + + return true; + } + + public function saveMap($final = false){ + console("[DEBUG] Saving chunks...", true, true, 2); + + $fp = fopen($this->file, "r+b"); + flock($fp, LOCK_EX); + foreach($this->map as $x => $d){ + foreach($d as $z => $chunk){ + fseek($fp, $this->getOffset($x, $z)); + fwrite($fp, $this->writeChunk($x, $z), $this->chunkLength); + } + } + flock($fp, LOCK_UN); + fclose($fp); + $original = filesize($this->file); + file_put_contents($this->file . ".gz", gzdeflate(gzdeflate(file_get_contents($this->file), 9), 9)); //Double compression for flat maps + $compressed = filesize($this->file . ".gz"); + console("[DEBUG] Saved chunks.dat.gz with " . round(($compressed / $original) * 100, 2) . "% (" . round($compressed / 1024, 2) . "KB) of the original size", true, true, 2); + if($final === true){ + @unlink($this->file); + } + } + + public function getFloor($x, $z){ + $X = $x >> 4; + $Z = $z >> 4; + $aX = $x - ($X << 4); + $aZ = $z - ($Z << 4); + $index = $aZ + ($aX << 4); + for($y = 127; $y <= 0; --$y){ + if($this->map[$X][$Z][0][$index]{$y} !== "\x00"){ + break; + } + } + + return $y; + } + + public function getBlock($x, $y, $z){ + $x = (int) $x; + $y = (int) $y; + $z = (int) $z; + $X = $x >> 4; + $Z = $z >> 4; + $aX = $x - ($X << 4); + $aZ = $z - ($Z << 4); + $index = $aZ + ($aX << 4); + $block = ord($this->map[$X][$Z][0][$index]{$y}); + $meta = ord($this->map[$X][$Z][1][$index]{$y >> 1}); + if(($y & 1) === 0){ + $meta = $meta & 0x0F; + }else{ + $meta = $meta >> 4; + } + + return array($block, $meta); + } + + public function getChunkColumn($X, $Z, $x, $z, $type = 0){ + $index = $z + ($x << 4); + + return $this->map[$X][$Z][$type][$index]; + } + + public function setBlock($x, $y, $z, $block, $meta = 0){ + $x = (int) $x; + $y = (int) $y; + $z = (int) $z; + $X = $x >> 4; + $Z = $z >> 4; + $aX = $x - ($X << 4); + $aZ = $z - ($Z << 4); + $index = $aZ + ($aX << 4); + $this->map[$X][$Z][0][$index]{$y} = chr($block); + $old_meta = ord($this->map[$X][$Z][1][$index]{$y >> 1}); + if(($y & 1) === 0){ + $meta = ($old_meta & 0xF0) | ($meta & 0x0F); + }else{ + $meta = (($meta << 4) & 0xF0) | ($old_meta & 0x0F); + } + $this->map[$X][$Z][1][$index]{$y >> 1} = chr($meta); + } + +} \ No newline at end of file diff --git a/src/pocketmine/level/Position.php b/src/pocketmine/level/Position.php new file mode 100644 index 000000000..bfdeee699 --- /dev/null +++ b/src/pocketmine/level/Position.php @@ -0,0 +1,78 @@ +x = $x; + $this->y = $y; + $this->z = $z; + $this->level = $level; + } + + public static function fromObject(Vector3 $pos, Level $level){ + return new Position($pos->x, $pos->y, $pos->z, $level); + } + + /** + * Returns a side Vector + * + * @param $side + * + * @return Position + */ + public function getSide($side){ + return Position::fromObject(parent::getSide($side), $this->level); + } + + /** + * Returns the distance between two points or objects + * + * @param Vector3 $pos + * + * @return float + */ + public function distance(Vector3 $pos){ + if(($pos instanceof Position) and $pos->level !== $this->level){ + return PHP_INT_MAX; + } + + return parent::distance($pos); + } + + public function __toString(){ + return "Position(level=" . $this->level->getName() . ",x=" . $this->x . ",y=" . $this->y . ",z=" . $this->z . ")"; + } + +} \ No newline at end of file diff --git a/src/pocketmine/level/WorldGenerator.php b/src/pocketmine/level/WorldGenerator.php new file mode 100644 index 000000000..2d8995d59 --- /dev/null +++ b/src/pocketmine/level/WorldGenerator.php @@ -0,0 +1,71 @@ +seed = $seed !== false ? (int) $seed : Utils::readInt(Utils::getRandomBytes(4, false)); + $this->random = new Random($this->seed); + $this->height = (int) $height; + $this->path = \pocketmine\DATA . "worlds/" . $name . "/"; + $this->generator = $generator; + $level = new LevelFormat($this->path . "level.pmf", array( + "name" => $name, + "seed" => $this->seed, + "time" => 0, + "spawnX" => 128, + "spawnY" => 128, + "spawnZ" => 128, + "height" => $this->height, + "generator" => $this->generator->getName(), + "generatorSettings" => $this->generator->getSettings(), + "extra" => "" + )); + $blockUpdates = new Config($this->path . "bupdates.yml", Config::YAML); + $this->level = new Level($level, $name); + } + + public function generate(){ + $this->generator->init($this->level, $this->random); + + for($Z = 7; $Z <= 9; ++$Z){ + for($X = 7; $X <= 9; ++$X){ + $this->level->level->loadChunk($X, $Z); + } + } + + $this->level->setSpawn($this->generator->getSpawn()); + } + + public function close(){ + $this->level->close(); + } + +} \ No newline at end of file diff --git a/src/pocketmine/level/generator/Flat.php b/src/pocketmine/level/generator/Flat.php new file mode 100644 index 000000000..6523b6581 --- /dev/null +++ b/src/pocketmine/level/generator/Flat.php @@ -0,0 +1,159 @@ +options; + } + + public function getName(){ + return "flat"; + } + + public function __construct(array $options = array()){ + $this->preset = "2;7,59x1,3x3,2;1;spawn(radius=10 block=89),decoration(treecount=80 grasscount=45)"; + $this->options = $options; + if(isset($options["preset"])){ + $this->parsePreset($options["preset"]); + }else{ + $this->parsePreset($this->preset); + } + if(isset($this->options["decoration"])){ + $ores = new Ore(); + $ores->setOreTypes(array( + new object\OreType(new CoalOre(), 20, 16, 0, 128), + new object\OreType(New IronOre(), 20, 8, 0, 64), + new object\OreType(new RedstoneOre(), 8, 7, 0, 16), + new object\OreType(new LapisOre(), 1, 6, 0, 32), + new object\OreType(new GoldOre(), 2, 8, 0, 32), + new object\OreType(new DiamondOre(), 1, 7, 0, 16), + new object\OreType(new Dirt(), 20, 32, 0, 128), + new object\OreType(new Gravel(), 10, 16, 0, 128), + )); + $this->populators[] = $ores; + } + + /*if(isset($this->options["mineshaft"])){ + $this->populators[] = new MineshaftPopulator(isset($this->options["mineshaft"]["chance"]) ? floatval($this->options["mineshaft"]["chance"]) : 0.01); + }*/ + } + + public function parsePreset($preset){ + $this->preset = $preset; + $preset = explode(";", $preset); + $version = (int) $preset[0]; + $blocks = @$preset[1]; + $biome = isset($preset[2]) ? $preset[2] : 1; + $options = isset($preset[3]) ? $preset[3] : ""; + preg_match_all('#(([0-9]{0,})x?([0-9]{1,3}:?[0-9]{0,2})),?#', $blocks, $matches); + $y = 0; + $this->structure = array(); + $this->chunks = array(); + foreach($matches[3] as $i => $b){ + $b = Item::fromString($b); + $cnt = $matches[2][$i] === "" ? 1 : intval($matches[2][$i]); + for($cY = $y, $y += $cnt; $cY < $y; ++$cY){ + $this->structure[$cY] = $b; + } + } + + $this->floorLevel = $y; + + for(; $y < 0xFF; ++$y){ + $this->structure[$y] = new Air(); + } + + + for($Y = 0; $Y < 8; ++$Y){ + $this->chunks[$Y] = ""; + $startY = $Y << 4; + $endY = $startY + 16; + for($Z = 0; $Z < 16; ++$Z){ + for($X = 0; $X < 16; ++$X){ + $blocks = ""; + $metas = ""; + for($y = $startY; $y < $endY; ++$y){ + $blocks .= chr($this->structure[$y]->getID()); + $metas .= substr(dechex($this->structure[$y]->getMetadata()), -1); + } + $this->chunks[$Y] .= $blocks . hex2bin($metas) . "\x00\x00\x00\x00\x00\x00\x00\x00"; + } + } + } + + preg_match_all('#(([0-9a-z_]{1,})\(?([0-9a-z_ =:]{0,})\)?),?#', $options, $matches); + foreach($matches[2] as $i => $option){ + $params = true; + if($matches[3][$i] !== ""){ + $params = array(); + $p = explode(" ", $matches[3][$i]); + foreach($p as $k){ + $k = explode("=", $k); + if(isset($k[1])){ + $params[$k[0]] = $k[1]; + } + } + } + $this->options[$option] = $params; + } + } + + public function init(Level $level, Random $random){ + $this->level = $level; + $this->random = $random; + } + + public function generateChunk($chunkX, $chunkZ){ + for($Y = 0; $Y < 8; ++$Y){ + $this->level->setMiniChunk($chunkX, $chunkZ, $Y, $this->chunks[$Y]); + } + } + + public function populateChunk($chunkX, $chunkZ){ + $this->random->setSeed(0xdeadbeef ^ ($chunkX << 8) ^ $chunkZ ^ $this->level->getSeed()); + foreach($this->populators as $populator){ + $populator->populate($this->level, $chunkX, $chunkZ, $this->random); + } + } + + public function getSpawn(){ + return new Vector3(128, $this->floorLevel, 128); + } +} \ No newline at end of file diff --git a/src/pocketmine/level/generator/Generator.php b/src/pocketmine/level/generator/Generator.php new file mode 100644 index 000000000..6ee1c3644 --- /dev/null +++ b/src/pocketmine/level/generator/Generator.php @@ -0,0 +1,64 @@ +level = $level; + $this->random = $random; + $this->random->setSeed($this->level->getSeed()); + $this->noiseHills = new Simplex($this->random, 3, 0.11, 12); + $this->noisePatches = new Simplex($this->random, 2, 0.03, 16); + $this->noisePatchesSmall = new Simplex($this->random, 2, 0.5, 4); + $this->noiseBase = new Simplex($this->random, 16, 0.7, 16); + + + $ores = new Ore(); + $ores->setOreTypes(array( + new OreType(new CoalOre(), 20, 16, 0, 128), + new OreType(New IronOre(), 20, 8, 0, 64), + new OreType(new RedstoneOre(), 8, 7, 0, 16), + new OreType(new LapisOre(), 1, 6, 0, 32), + new OreType(new GoldOre(), 2, 8, 0, 32), + new OreType(new DiamondOre(), 1, 7, 0, 16), + new OreType(new Dirt(), 20, 32, 0, 128), + new OreType(new Gravel(), 10, 16, 0, 128), + )); + $this->populators[] = $ores; + + $trees = new Tree(); + $trees->setBaseAmount(3); + $trees->setRandomAmount(0); + $this->populators[] = $trees; + + $tallGrass = new TallGrass(); + $tallGrass->setBaseAmount(5); + $tallGrass->setRandomAmount(0); + $this->populators[] = $tallGrass; + } + + public function generateChunk($chunkX, $chunkZ){ + $this->random->setSeed(0xdeadbeef ^ ($chunkX << 8) ^ $chunkZ ^ $this->level->getSeed()); + $hills = array(); + $patches = array(); + $patchesSmall = array(); + $base = array(); + for($z = 0; $z < 16; ++$z){ + for($x = 0; $x < 16; ++$x){ + $i = ($z << 4) + $x; + $hills[$i] = $this->noiseHills->noise2D($x + ($chunkX << 4), $z + ($chunkZ << 4), true); + $patches[$i] = $this->noisePatches->noise2D($x + ($chunkX << 4), $z + ($chunkZ << 4), true); + $patchesSmall[$i] = $this->noisePatchesSmall->noise2D($x + ($chunkX << 4), $z + ($chunkZ << 4), true); + $base[$i] = $this->noiseBase->noise2D($x + ($chunkX << 4), $z + ($chunkZ << 4), true); + + if($base[$i] < 0){ + $base[$i] *= 0.5; + } + } + } + + for($chunkY = 0; $chunkY < 8; ++$chunkY){ + $chunk = ""; + $startY = $chunkY << 4; + $endY = $startY + 16; + for($z = 0; $z < 16; ++$z){ + for($x = 0; $x < 16; ++$x){ + $i = ($z << 4) + $x; + $height = $this->worldHeight + $hills[$i] * 14 + $base[$i] * 7; + $height = (int) $height; + + for($y = $startY; $y < $endY; ++$y){ + $diff = $height - $y; + if($y <= 4 and ($y === 0 or $this->random->nextFloat() < 0.75)){ + $chunk .= "\x07"; //bedrock + }elseif($diff > 2){ + $chunk .= "\x01"; //stone + }elseif($diff > 0){ + if($patches[$i] > 0.7){ + $chunk .= "\x01"; //stone + }elseif($patches[$i] < -0.8){ + $chunk .= "\x0d"; //gravel + }else{ + $chunk .= "\x03"; //dirt + } + }elseif($y <= $this->waterHeight){ + if(($this->waterHeight - $y) <= 1 and $diff === 0){ + $chunk .= "\x0c"; //sand + }elseif($diff === 0){ + if($patchesSmall[$i] > 0.3){ + $chunk .= "\x0d"; //gravel + }elseif($patchesSmall[$i] < -0.45){ + $chunk .= "\x0c"; //sand + }else{ + $chunk .= "\x03"; //dirt + } + }else{ + $chunk .= "\x09"; //still_water + } + }elseif($diff === 0){ + if($patches[$i] > 0.7){ + $chunk .= "\x01"; //stone + }elseif($patches[$i] < -0.8){ + $chunk .= "\x0d"; //gravel + }else{ + $chunk .= "\x02"; //grass + } + }else{ + $chunk .= "\x00"; + } + } + $chunk .= "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; + } + } + $this->level->setMiniChunk($chunkX, $chunkZ, $chunkY, $chunk); + } + + } + + public function populateChunk($chunkX, $chunkZ){ + $this->random->setSeed(0xdeadbeef ^ ($chunkX << 8) ^ $chunkZ ^ $this->level->getSeed()); + foreach($this->populators as $populator){ + $this->random->setSeed(0xdeadbeef ^ ($chunkX << 8) ^ $chunkZ ^ $this->level->getSeed()); + $populator->populate($this->level, $chunkX, $chunkZ, $this->random); + } + } + + public function getSpawn(){ + return $this->level->getSafeSpawn(new Vector3(127.5, 128, 127.5)); + } + +} \ No newline at end of file diff --git a/src/pocketmine/level/generator/noise/Generator.php b/src/pocketmine/level/generator/noise/Generator.php new file mode 100644 index 000000000..95de68ad2 --- /dev/null +++ b/src/pocketmine/level/generator/noise/Generator.php @@ -0,0 +1,104 @@ += 0 ? (int) $x : (int) ($x - 1); + } + + public static function fade($x){ + return $x * $x * $x * ($x * ($x * 6 - 15) + 10); + } + + public static function lerp($x, $y, $z){ + return $y + $x * ($z - $y); + } + + public static function grad($hash, $x, $y, $z){ + $hash &= 15; + $u = $hash < 8 ? $x : $y; + $v = $hash < 4 ? $y : (($hash === 12 or $hash === 14) ? $x : $z); + + return (($hash & 1) === 0 ? $u : -$u) + (($hash & 2) === 0 ? $v : -$v); + } + + abstract public function getNoise2D($x, $z); + + abstract public function getNoise3D($x, $y, $z); + + public function noise2D($x, $z, $normalized = false){ + $result = 0; + $amp = 1; + $freq = 1; + $max = 0; + + for($i = 0; $i < $this->octaves; ++$i){ + $result += $this->getNoise2D($x * $freq, $z * $freq) * $amp; + $max += $amp; + $freq *= $this->frequency; + $amp *= $this->amplitude; + } + if($normalized === true){ + $result /= $max; + } + + return $result; + } + + public function noise3D($x, $y, $z, $normalized = false){ + $result = 0; + $amp = 1; + $freq = 1; + $max = 0; + + for($i = 0; $i < $this->octaves; ++$i){ + $result += $this->getNoise3D($x * $freq, $y * $freq, $z * $freq) * $amp; + $max += $amp; + $freq *= $this->frequency; + $amp *= $this->amplitude; + } + if($normalized === true){ + $result /= $max; + } + + return $result; + } + + public function setOffset($x, $y, $z){ + $this->offsetX = $x; + $this->offsetY = $y; + $this->offsetZ = $z; + } +} \ No newline at end of file diff --git a/src/pocketmine/level/generator/noise/Perlin.php b/src/pocketmine/level/generator/noise/Perlin.php new file mode 100644 index 000000000..1b7c653e7 --- /dev/null +++ b/src/pocketmine/level/generator/noise/Perlin.php @@ -0,0 +1,104 @@ +octaves = $octaves; + $this->frequency = $frequency; + $this->amplitude = $amplitude; + $this->offsetX = $random->nextFloat() * 256; + $this->offsetY = $random->nextFloat() * 256; + $this->offsetZ = $random->nextFloat() * 256; + + for($i = 0; $i < 512; ++$i){ + $this->perm[$i] = 0; + } + + for($i = 0; $i < 256; ++$i){ + $this->perm[$i] = $random->nextRange(0, 255); + } + + for($i = 0; $i < 256; ++$i){ + $pos = $random->nextRange(0, 255 - $i) + $i; + $old = $this->perm[$i]; + + $this->perm[$i] = $this->perm[$pos]; + $this->perm[$pos] = $old; + $this->perm[$i + 256] = $this->perm[$i]; + } + + } + + public function getNoise3D($x, $y, $z){ + $x += $this->offsetX; + $y += $this->offsetY; + $z += $this->offsetZ; + + $floorX = (int) floor($x); + $floorY = (int) floor($y); + $floorZ = (int) floor($z); + + $X = $floorX & 0xFF; + $Y = $floorY & 0xFF; + $Z = $floorZ & 0xFF; + + $x -= $floorX; + $y -= $floorY; + $z -= $floorZ; + + //Fade curves + $fX = self::fade($x); + $fY = self::fade($y); + $fZ = self::fade($z); + + //Cube corners + $A = $this->perm[$X] + $Y; + $AA = $this->perm[$A] + $Z; + $AB = $this->perm[$A + 1] + $Z; + $B = $this->perm[$X + 1] + $Y; + $BA = $this->perm[$B] + $Z; + $BB = $this->perm[$B + 1] + $Z; + + return self::lerp($fZ, self::lerp($fY, self::lerp($fX, self::grad($this->perm[$AA], $x, $y, $z), + self::grad($this->perm[$BA], $x - 1, $y, $z)), + self::lerp($fX, self::grad($this->perm[$AB], $x, $y - 1, $z), + self::grad($this->perm[$BB], $x - 1, $y - 1, $z))), + self::lerp($fY, self::lerp($fX, self::grad($this->perm[$AA + 1], $x, $y, $z - 1), + self::grad($this->perm[$BA + 1], $x - 1, $y, $z - 1)), + self::lerp($fX, self::grad($this->perm[$AB + 1], $x, $y - 1, $z - 1), + self::grad($this->perm[$BB + 1], $x - 1, $y - 1, $z - 1)))); + } + + public function getNoise2D($x, $y){ + return $this->getNoise3D($x, $y, 0); + } +} \ No newline at end of file diff --git a/src/pocketmine/level/generator/noise/Simplex.php b/src/pocketmine/level/generator/noise/Simplex.php new file mode 100644 index 000000000..abcd6ce30 --- /dev/null +++ b/src/pocketmine/level/generator/noise/Simplex.php @@ -0,0 +1,461 @@ + + * This is a modified version of the freely published version in the paper by + * Stefan Gustavson at + * + * http://staffwww.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf + */ +class Simplex extends Perlin{ + protected static $SQRT_3; + protected static $SQRT_5; + protected static $F2; + protected static $G2; + protected static $G22; + protected static $F3; + protected static $G3; + protected static $F4; + protected static $G4; + protected static $G42; + protected static $G43; + protected static $G44; + protected static $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 $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 $offsetW; + + + public function __construct(Random $random, $octaves, $frequency, $amplitude){ + parent::__construct($random, $octaves, $frequency, $amplitude); + $this->offsetW = $random->nextFloat() * 256; + self::$SQRT_3 = sqrt(3); + self::$SQRT_5 = sqrt(5); + self::$F2 = 0.5 * (self::$SQRT_3 - 1); + self::$G2 = (3 - self::$SQRT_3) / 6; + self::$G22 = self::$G2 * 2.0 - 1; + self::$F3 = 1.0 / 3.0; + self::$G3 = 1.0 / 6.0; + self::$F4 = (self::$SQRT_5 - 1.0) / 4.0; + self::$G4 = (5.0 - self::$SQRT_5) / 20.0; + self::$G42 = self::$G4 * 2.0; + self::$G43 = self::$G4 * 3.0; + self::$G44 = self::$G4 * 4.0 - 1.0; + } + + protected static function dot2D($g, $x, $y){ + return $g[0] * $x + $g[1] * $y; + } + + protected static function dot3D($g, $x, $y, $z){ + return $g[0] * $x + $g[1] * $y + $g[2] * $z; + } + + protected static function dot4D($g, $x, $y, $z, $w){ + return $g[0] * $x + $g[1] * $y + $g[2] * $z + $g[3] * $w; + } + + public function getNoise3D($x, $y, $z){ + $x += $this->offsetX; + $y += $this->offsetY; + $z += $this->offsetZ; + + // Skew the input space to determine which simplex cell we're in + $s = ($x + $y + $z) * self::$F3; // Very nice and simple skew factor for 3D + $i = (int) floor($x + $s); + $j = (int) floor($y + $s); + $k = (int) floor($z + $s); + $t = ($i + $j + $k) * self::$G3; + $X0 = $i - $t; // Unskew the cell origin back to (x,y,z) space + $Y0 = $j - $t; + $Z0 = $k - $t; + $x0 = $x - $X0; // The x,y,z distances from the cell origin + $y0 = $y - $Y0; + $z0 = $z - $Z0; + + // For the 3D case, the simplex shape is a slightly irregular tetrahedron. + + // Determine which simplex we are in. + if($x0 >= $y0){ + if($y0 >= $z0){ + $i1 = 1; + $j1 = 0; + $k1 = 0; + $i2 = 1; + $j2 = 1; + $k2 = 0; + } // X Y Z order + elseif($x0 >= $z0){ + $i1 = 1; + $j1 = 0; + $k1 = 0; + $i2 = 1; + $j2 = 0; + $k2 = 1; + } // X Z Y order + else{ + $i1 = 0; + $j1 = 0; + $k1 = 1; + $i2 = 1; + $j2 = 0; + $k2 = 1; + } + // Z X Y order + }else{ // x0perm[$ii + $this->perm[$jj + $this->perm[$kk]]] % 12; + $gi1 = $this->perm[$ii + $i1 + $this->perm[$jj + $j1 + $this->perm[$kk + $k1]]] % 12; + $gi2 = $this->perm[$ii + $i2 + $this->perm[$jj + $j2 + $this->perm[$kk + $k2]]] % 12; + $gi3 = $this->perm[$ii + 1 + $this->perm[$jj + 1 + $this->perm[$kk + 1]]] % 12; + + // Calculate the contribution from the four corners + $t0 = 0.6 - $x0 * $x0 - $y0 * $y0 - $z0 * $z0; + if($t0 < 0){ + $n0 = 0.0; + }else{ + $t0 *= $t0; + $n0 = $t0 * $t0 * self::dot3D(self::$grad3[$gi0], $x0, $y0, $z0); + } + + $t1 = 0.6 - $x1 * $x1 - $y1 * $y1 - $z1 * $z1; + if($t1 < 0){ + $n1 = 0.0; + }else{ + $t1 *= $t1; + $n1 = $t1 * $t1 * self::dot3D(self::$grad3[$gi1], $x1, $y1, $z1); + } + + $t2 = 0.6 - $x2 * $x2 - $y2 * $y2 - $z2 * $z2; + if($t2 < 0){ + $n2 = 0.0; + }else{ + $t2 *= $t2; + $n2 = $t2 * $t2 * self::dot3D(self::$grad3[$gi2], $x2, $y2, $z2); + } + + $t3 = 0.6 - $x3 * $x3 - $y3 * $y3 - $z3 * $z3; + if($t3 < 0){ + $n3 = 0.0; + }else{ + $t3 *= $t3; + $n3 = $t3 * $t3 * self::dot3D(self::$grad3[$gi3], $x3, $y3, $z3); + } + + // Add contributions from each corner to get the noise value. + // The result is scaled to stay just inside [-1,1] + return 32.0 * ($n0 + $n1 + $n2 + $n3); + } + + public function getNoise2D($x, $y){ + $x += $this->offsetX; + $y += $this->offsetY; + + // Skew the input space to determine which simplex cell we're in + $s = ($x + $y) * self::$F2; // Hairy factor for 2D + $i = (int) floor($x + $s); + $j = (int) floor($y + $s); + $t = ($i + $j) * self::$G2; + $X0 = $i - $t; // Unskew the cell origin back to (x,y) space + $Y0 = $j - $t; + $x0 = $x - $X0; // The x,y distances from the cell origin + $y0 = $y - $Y0; + + // For the 2D case, the simplex shape is an equilateral triangle. + + // Determine which simplex we are in. + if($x0 > $y0){ + $i1 = 1; + $j1 = 0; + } // lower triangle, XY order: (0,0)->(1,0)->(1,1) + else{ + $i1 = 0; + $j1 = 1; + } + // upper triangle, YX order: (0,0)->(0,1)->(1,1) + + // A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and + // a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where + // c = (3-sqrt(3))/6 + + $x1 = $x0 - $i1 + self::$G2; // Offsets for middle corner in (x,y) unskewed coords + $y1 = $y0 - $j1 + self::$G2; + $x2 = $x0 + self::$G22; // Offsets for last corner in (x,y) unskewed coords + $y2 = $y0 + self::$G22; + + // Work out the hashed gradient indices of the three simplex corners + $ii = $i & 255; + $jj = $j & 255; + $gi0 = $this->perm[$ii + $this->perm[$jj]] % 12; + $gi1 = $this->perm[$ii + $i1 + $this->perm[$jj + $j1]] % 12; + $gi2 = $this->perm[$ii + 1 + $this->perm[$jj + 1]] % 12; + + // Calculate the contribution from the three corners + $t0 = 0.5 - $x0 * $x0 - $y0 * $y0; + if($t0 < 0){ + $n0 = 0.0; + }else{ + $t0 *= $t0; + $n0 = $t0 * $t0 * self::dot2D(self::$grad3[$gi0], $x0, $y0); // (x,y) of grad3 used for 2D gradient + } + + $t1 = 0.5 - $x1 * $x1 - $y1 * $y1; + if($t1 < 0){ + $n1 = 0.0; + }else{ + $t1 *= $t1; + $n1 = $t1 * $t1 * self::dot2D(self::$grad3[$gi1], $x1, $y1); + } + + $t2 = 0.5 - $x2 * $x2 - $y2 * $y2; + if($t2 < 0){ + $n2 = 0.0; + }else{ + $t2 *= $t2; + $n2 = $t2 * $t2 * self::dot2D(self::$grad3[$gi2], $x2, $y2); + } + + // Add contributions from each corner to get the noise value. + // The result is scaled to return values in the interval [-1,1]. + return 70.0 * ($n0 + $n1 + $n2); + } + + /** + * Computes and returns the 4D simplex noise for the given coordinates in + * 4D space + * + * @param float $x X coordinate + * @param float $y Y coordinate + * @param float $z Z coordinate + * @param float $w W coordinate + * + * @return float Noise at given location, from range -1 to 1 + */ + /*public function getNoise4D($x, $y, $z, $w){ + x += offsetX; + y += offsetY; + z += offsetZ; + w += offsetW; + + n0, n1, n2, n3, n4; // Noise contributions from the five corners + + // Skew the (x,y,z,w) space to determine which cell of 24 simplices we're in + s = (x + y + z + w) * self::$F4; // Factor for 4D skewing + i = floor(x + s); + j = floor(y + s); + k = floor(z + s); + l = floor(w + s); + + t = (i + j + k + l) * self::$G4; // Factor for 4D unskewing + X0 = i - t; // Unskew the cell origin back to (x,y,z,w) space + Y0 = j - t; + Z0 = k - t; + W0 = l - t; + x0 = x - X0; // The x,y,z,w distances from the cell origin + y0 = y - Y0; + z0 = z - Z0; + w0 = w - W0; + + // For the 4D case, the simplex is a 4D shape I won't even try to describe. + // To find out which of the 24 possible simplices we're in, we need to + // determine the magnitude ordering of x0, y0, z0 and w0. + // The method below is a good way of finding the ordering of x,y,z,w and + // then find the correct traversal order for the simplex we’re in. + // First, six pair-wise comparisons are performed between each possible pair + // of the four coordinates, and the results are used to add up binary bits + // for an integer index. + c1 = (x0 > y0) ? 32 : 0; + c2 = (x0 > z0) ? 16 : 0; + c3 = (y0 > z0) ? 8 : 0; + c4 = (x0 > w0) ? 4 : 0; + c5 = (y0 > w0) ? 2 : 0; + c6 = (z0 > w0) ? 1 : 0; + c = c1 + c2 + c3 + c4 + c5 + c6; + i1, j1, k1, l1; // The integer offsets for the second simplex corner + i2, j2, k2, l2; // The integer offsets for the third simplex corner + i3, j3, k3, l3; // The integer offsets for the fourth simplex corner + + // simplex[c] is a 4-vector with the numbers 0, 1, 2 and 3 in some order. + // Many values of c will never occur, since e.g. x>y>z>w makes x= 3 ? 1 : 0; + j1 = simplex[c][1] >= 3 ? 1 : 0; + k1 = simplex[c][2] >= 3 ? 1 : 0; + l1 = simplex[c][3] >= 3 ? 1 : 0; + + // The number 2 in the "simplex" array is at the second largest coordinate. + i2 = simplex[c][0] >= 2 ? 1 : 0; + j2 = simplex[c][1] >= 2 ? 1 : 0; + k2 = simplex[c][2] >= 2 ? 1 : 0; + l2 = simplex[c][3] >= 2 ? 1 : 0; + + // The number 1 in the "simplex" array is at the second smallest coordinate. + i3 = simplex[c][0] >= 1 ? 1 : 0; + j3 = simplex[c][1] >= 1 ? 1 : 0; + k3 = simplex[c][2] >= 1 ? 1 : 0; + l3 = simplex[c][3] >= 1 ? 1 : 0; + + // The fifth corner has all coordinate offsets = 1, so no need to look that up. + + x1 = x0 - i1 + self::$G4; // Offsets for second corner in (x,y,z,w) coords + y1 = y0 - j1 + self::$G4; + z1 = z0 - k1 + self::$G4; + w1 = w0 - l1 + self::$G4; + + x2 = x0 - i2 + self::$G42; // Offsets for third corner in (x,y,z,w) coords + y2 = y0 - j2 + self::$G42; + z2 = z0 - k2 + self::$G42; + w2 = w0 - l2 + self::$G42; + + x3 = x0 - i3 + self::$G43; // Offsets for fourth corner in (x,y,z,w) coords + y3 = y0 - j3 + self::$G43; + z3 = z0 - k3 + self::$G43; + w3 = w0 - l3 + self::$G43; + + x4 = x0 + self::$G44; // Offsets for last corner in (x,y,z,w) coords + y4 = y0 + self::$G44; + z4 = z0 + self::$G44; + w4 = w0 + self::$G44; + + // Work out the hashed gradient indices of the five simplex corners + ii = i & 255; + jj = j & 255; + kk = k & 255; + ll = l & 255; + + gi0 = $this->perm[ii + $this->perm[jj + $this->perm[kk + $this->perm[ll]]]] % 32; + gi1 = $this->perm[ii + i1 + $this->perm[jj + j1 + $this->perm[kk + k1 + $this->perm[ll + l1]]]] % 32; + gi2 = $this->perm[ii + i2 + $this->perm[jj + j2 + $this->perm[kk + k2 + $this->perm[ll + l2]]]] % 32; + gi3 = $this->perm[ii + i3 + $this->perm[jj + j3 + $this->perm[kk + k3 + $this->perm[ll + l3]]]] % 32; + gi4 = $this->perm[ii + 1 + $this->perm[jj + 1 + $this->perm[kk + 1 + $this->perm[ll + 1]]]] % 32; + + // Calculate the contribution from the five corners + t0 = 0.6 - x0 * x0 - y0 * y0 - z0 * z0 - w0 * w0; + if(t0 < 0){ + n0 = 0.0; + }else{ + t0 *= t0; + n0 = t0 * t0 * dot(grad4[gi0], x0, y0, z0, w0); + } + + t1 = 0.6 - x1 * x1 - y1 * y1 - z1 * z1 - w1 * w1; + if(t1 < 0){ + n1 = 0.0; + }else{ + t1 *= t1; + n1 = t1 * t1 * dot(grad4[gi1], x1, y1, z1, w1); + } + + t2 = 0.6 - x2 * x2 - y2 * y2 - z2 * z2 - w2 * w2; + if(t2 < 0){ + n2 = 0.0; + }else{ + t2 *= t2; + n2 = t2 * t2 * dot(grad4[gi2], x2, y2, z2, w2); + } + + t3 = 0.6 - x3 * x3 - y3 * y3 - z3 * z3 - w3 * w3; + if(t3 < 0){ + n3 = 0.0; + }else{ + t3 *= t3; + n3 = t3 * t3 * dot(grad4[gi3], x3, y3, z3, w3); + } + + t4 = 0.6 - x4 * x4 - y4 * y4 - z4 * z4 - w4 * w4; + if(t4 < 0){ + n4 = 0.0; + }else{ + t4 *= t4; + n4 = t4 * t4 * dot(grad4[gi4], x4, y4, z4, w4); + } + + // Sum up and scale the result to cover the range [-1,1] + return 27.0 * (n0 + n1 + n2 + n3 + n4); + }*/ +} \ No newline at end of file diff --git a/src/pocketmine/level/generator/object/BigTree.php b/src/pocketmine/level/generator/object/BigTree.php new file mode 100644 index 000000000..b7fbad7ca --- /dev/null +++ b/src/pocketmine/level/generator/object/BigTree.php @@ -0,0 +1,85 @@ +trunkHeight = (int) ($this->totalHeight * $this->trunkHeightMultiplier); + $leaves = $this->getLeafGroupPoints($level, $pos); + foreach($leaves as $leafGroup){ + $groupX = $leafGroup->getBlockX(); + $groupY = $leafGroup->getBlockY(); + $groupZ = $leafGroup->getBlockZ(); + for($yy = $groupY; $yy < $groupY + $this->leafDistanceLimit; ++$yy){ + $this->generateGroupLayer($level, $groupX, $yy, $groupZ, $this->getLeafGroupLayerSize($yy - $groupY)); + } + } + /*final BlockIterator trunk = new BlockIterator(new Point(w, x, y - 1, z), new Point(w, x, y + trunkHeight, z)); + while (trunk.hasNext()) { + trunk.next().setMaterial(VanillaMaterials.LOG, logMetadata); + } + generateBranches(w, x, y, z, leaves); + + $level->setBlock($x, $pos->y - 1, $z, 3, 0); + $this->totalHeight += $random->nextRange(0, 2); + $this->leavesHeight += mt_rand(0, 1); + for($yy = ($this->totalHeight - $this->leavesHeight); $yy < ($this->totalHeight + 1); ++$yy){ + $yRadius = ($yy - $this->totalHeight); + $xzRadius = (int) (($this->radiusIncrease + 1) - $yRadius / 2); + for($xx = -$xzRadius; $xx < ($xzRadius + 1); ++$xx){ + for($zz = -$xzRadius; $zz < ($xzRadius + 1); ++$zz){ + if((abs($xx) != $xzRadius or abs($zz) != $xzRadius) and $yRadius != 0){ + $level->setBlock($pos->x + $xx, $pos->y + $yy, $pos->z + $zz, 18, $type); + } + } + } + } + for($yy = 0; $yy < ($this->totalHeight - 1); ++$yy){ + $level->setBlock($x, $pos->y + $yy, $z, 17, $type); + } + */ + } + + +} \ No newline at end of file diff --git a/src/pocketmine/level/generator/object/Object.php b/src/pocketmine/level/generator/object/Object.php new file mode 100644 index 000000000..ac1fa9707 --- /dev/null +++ b/src/pocketmine/level/generator/object/Object.php @@ -0,0 +1,30 @@ +type = $type; + $this->random = $random; + } + + public function getType(){ + return $this->type; + } + + public function canPlaceObject(Level $level, $x, $y, $z){ + return ($level->level->getBlockID($x, $y, $z) !== 0); + } + + public function placeObject(Level $level, Vector3 $pos){ + $clusterSize = (int) $this->type->clusterSize; + $angle = $this->random->nextFloat() * M_PI; + $offset = VectorMath::getDirection2D($angle)->multiply($clusterSize)->divide(8); + $x1 = $pos->x + 8 + $offset->x; + $x2 = $pos->x + 8 - $offset->x; + $z1 = $pos->z + 8 + $offset->y; + $z2 = $pos->z + 8 - $offset->y; + $y1 = $pos->y + $this->random->nextRange(0, 3) + 2; + $y2 = $pos->y + $this->random->nextRange(0, 3) + 2; + for($count = 0; $count <= $clusterSize; ++$count){ + $seedX = $x1 + ($x2 - $x1) * $count / $clusterSize; + $seedY = $y1 + ($y2 - $y1) * $count / $clusterSize; + $seedZ = $z1 + ($z2 - $z1) * $count / $clusterSize; + $size = ((sin($count * (M_PI / $clusterSize)) + 1) * $this->random->nextFloat() * $clusterSize / 16 + 1) / 2; + + $startX = (int) ($seedX - $size); + $startY = (int) ($seedY - $size); + $startZ = (int) ($seedZ - $size); + $endX = (int) ($seedX + $size); + $endY = (int) ($seedY + $size); + $endZ = (int) ($seedZ + $size); + + for($x = $startX; $x <= $endX; ++$x){ + $sizeX = ($x + 0.5 - $seedX) / $size; + $sizeX *= $sizeX; + + if($sizeX < 1){ + for($y = $startY; $y <= $endY; ++$y){ + $sizeY = ($y + 0.5 - $seedY) / $size; + $sizeY *= $sizeY; + + if($y > 0 and ($sizeX + $sizeY) < 1){ + for($z = $startZ; $z <= $endZ; ++$z){ + $sizeZ = ($z + 0.5 - $seedZ) / $size; + $sizeZ *= $sizeZ; + + if(($sizeX + $sizeY + $sizeZ) < 1 and $level->level->getBlockID($x, $y, $z) === 1){ + $level->setBlockRaw(new Vector3($x, $y, $z), $this->type->material); + } + } + } + } + } + } + } + } + +} \ No newline at end of file diff --git a/src/pocketmine/level/generator/object/OreType.php b/src/pocketmine/level/generator/object/OreType.php new file mode 100644 index 000000000..b5de566dd --- /dev/null +++ b/src/pocketmine/level/generator/object/OreType.php @@ -0,0 +1,36 @@ +material = $material; + $this->clusterCount = (int) $clusterCount; + $this->clusterSize = (int) $clusterSize; + $this->maxHeight = (int) $maxHeight; + $this->minHeight = (int) $minHeight; + } +} \ No newline at end of file diff --git a/src/pocketmine/level/generator/object/PineTree.php b/src/pocketmine/level/generator/object/PineTree.php new file mode 100644 index 000000000..e85f9823f --- /dev/null +++ b/src/pocketmine/level/generator/object/PineTree.php @@ -0,0 +1,97 @@ +findRandomLeavesSize($random); + $checkRadius = 0; + for($yy = 0; $yy < $this->totalHeight; ++$yy){ + if($yy === $this->leavesSizeY){ + $checkRadius = $this->leavesAbsoluteMaxRadius; + } + for($xx = -$checkRadius; $xx < ($checkRadius + 1); ++$xx){ + for($zz = -$checkRadius; $zz < ($checkRadius + 1); ++$zz){ + if(!isset($this->overridable[$level->level->getBlockID($pos->x + $xx, $pos->y + $yy, $pos->z + $zz)])){ + return false; + } + } + } + } + + return true; + } + + private function findRandomLeavesSize(Random $random){ + $this->totalHeight += $random->nextRange(-1, 2); + $this->leavesSizeY = 1 + $random->nextRange(0, 2); + $this->leavesAbsoluteMaxRadius = 2 + $random->nextRange(0, 1); + } + + public function placeObject(Level $level, Vector3 $pos, Random $random){ + if($this->leavesSizeY === -1 or $this->leavesAbsoluteMaxRadius === -1){ + $this->findRandomLeavesSize($random); + } + $level->setBlockRaw(new Vector3($pos->x, $pos->y - 1, $pos->z), new Dirt()); + $leavesRadius = 0; + $leavesMaxRadius = 1; + $leavesBottomY = $this->totalHeight - $this->leavesSizeY; + $firstMaxedRadius = false; + for($leavesY = 0; $leavesY <= $leavesBottomY; ++$leavesY){ + $yy = $this->totalHeight - $leavesY; + for($xx = -$leavesRadius; $xx <= $leavesRadius; ++$xx){ + for($zz = -$leavesRadius; $zz <= $leavesRadius; ++$zz){ + if(abs($xx) != $leavesRadius or abs($zz) != $leavesRadius or $leavesRadius <= 0){ + $level->setBlockRaw(new Vector3($pos->x + $xx, $pos->y + $yy, $pos->z + $zz), new Leaves($this->type)); + } + } + } + if($leavesRadius >= $leavesMaxRadius){ + $leavesRadius = $firstMaxedRadius ? 1 : 0; + $firstMaxedRadius = true; + if(++$leavesMaxRadius > $this->leavesAbsoluteMaxRadius){ + $leavesMaxRadius = $this->leavesAbsoluteMaxRadius; + } + }else{ + ++$leavesRadius; + } + } + $trunkHeightReducer = $random->nextRange(0, 3); + for($yy = 0; $yy < ($this->totalHeight - $trunkHeightReducer); ++$yy){ + $level->setBlockRaw(new Vector3($pos->x, $pos->y + $yy, $pos->z), new Wood($this->type)); + } + } + + +} \ No newline at end of file diff --git a/src/pocketmine/level/generator/object/Pond.php b/src/pocketmine/level/generator/object/Pond.php new file mode 100644 index 000000000..d4697c221 --- /dev/null +++ b/src/pocketmine/level/generator/object/Pond.php @@ -0,0 +1,44 @@ +type = $type; + $this->random = $random; + } + + public function canPlaceObject(Level $level, Vector3 $pos){ + } + + public function placeObject(Level $level, Vector3 $pos){ + } + +} \ No newline at end of file diff --git a/src/pocketmine/level/generator/object/SmallTree.php b/src/pocketmine/level/generator/object/SmallTree.php new file mode 100644 index 000000000..911077b5f --- /dev/null +++ b/src/pocketmine/level/generator/object/SmallTree.php @@ -0,0 +1,104 @@ +trunkHeight + 3; ++$yy){ + if($yy == 1 or $yy === $this->trunkHeight){ + ++$radiusToCheck; + } + for($xx = -$radiusToCheck; $xx < ($radiusToCheck + 1); ++$xx){ + for($zz = -$radiusToCheck; $zz < ($radiusToCheck + 1); ++$zz){ + if(!isset($this->overridable[$level->level->getBlockID($pos->x + $xx, $pos->y + $yy, $pos->z + $zz)])){ + return false; + } + } + } + } + + return true; + } + + public function placeObject(Level $level, Vector3 $pos, Random $random){ + // The base dirt block + $dirtpos = new Vector3($pos->x, $pos->y - 1, $pos->z); + $level->setBlockRaw($dirtpos, new Dirt()); + + // Adjust the tree trunk's height randomly + // plot [-14:11] int( x / 8 ) + 5 + // - min=4 (all leaves are 4 tall, some trunk must show) + // - max=6 (top leaves are within ground-level whacking range + // on all small trees) + $heightPre = $random->nextRange(-14, 11); + $this->trunkHeight = intval($heightPre / 8) + 5; + + // Adjust the starting leaf density using the trunk height as a + // starting position (tall trees with skimpy leaves don't look + // too good) + $leafPre = $random->nextRange($this->trunkHeight, 10) / 20; // (TODO: seed may apply) + + // Now build the tree (from the top down) + $leaflevel = 0; + for($yy = ($this->trunkHeight + 1); $yy >= 0; --$yy){ + if($leaflevel < self::$leavesHeight){ + // The size is a slight variation on the trunkheight + $radius = self::$leafRadii[$leaflevel] + $leafPre; + $bRadius = 3; + for($xx = -$bRadius; $xx <= $bRadius; ++$xx){ + for($zz = -$bRadius; $zz <= $bRadius; ++$zz){ + if(sqrt(($xx * $xx) + ($zz * $zz)) <= $radius){ + $leafpos = new Vector3($pos->x + $xx, + $pos->y + $yy, + $pos->z + $zz); + $level->setBlockRaw($leafpos, new Leaves($this->type)); + } + } + } + $leaflevel++; + } + + // Place the trunk last + if($leaflevel > 1){ + $trunkpos = new Vector3($pos->x, $pos->y + $yy, $pos->z); + $level->setBlockRaw($trunkpos, new Wood($this->type)); + } + } + } +} \ No newline at end of file diff --git a/src/pocketmine/level/generator/object/SpruceTree.php b/src/pocketmine/level/generator/object/SpruceTree.php new file mode 100644 index 000000000..803be97c8 --- /dev/null +++ b/src/pocketmine/level/generator/object/SpruceTree.php @@ -0,0 +1,88 @@ +findRandomLeavesSize($random); + $checkRadius = 0; + for($yy = 0; $yy < $this->totalHeight + 2; ++$yy){ + if($yy === $this->leavesBottomY){ + $checkRadius = $this->leavesMaxRadius; + } + for($xx = -$checkRadius; $xx < ($checkRadius + 1); ++$xx){ + for($zz = -$checkRadius; $zz < ($checkRadius + 1); ++$zz){ + if(!isset($this->overridable[$level->level->getBlockID($pos->x + $xx, $pos->y + $yy, $pos->z + $zz)])){ + return false; + } + } + } + } + + return true; + } + + private function findRandomLeavesSize(Random $random){ + $this->totalHeight += $random->nextRange(-1, 2); + $this->leavesBottomY = (int) ($this->totalHeight - $random->nextRange(1, 2) - 3); + $this->leavesMaxRadius = 1 + $random->nextRange(0, 1); + } + + public function placeObject(Level $level, Vector3 $pos, Random $random){ + if($this->leavesBottomY === -1 or $this->leavesMaxRadius === -1){ + $this->findRandomLeavesSize($random); + } + $level->setBlockRaw(new Vector3($pos->x, $pos->y - 1, $pos->z), new Dirt()); + $leavesRadius = 0; + for($yy = $this->totalHeight; $yy >= $this->leavesBottomY; --$yy){ + for($xx = -$leavesRadius; $xx <= $leavesRadius; ++$xx){ + for($zz = -$leavesRadius; $zz <= $leavesRadius; ++$zz){ + if(abs($xx) != $leavesRadius or abs($zz) != $leavesRadius or $leavesRadius <= 0){ + $level->setBlockRaw(new Vector3($pos->x + $xx, $pos->y + $yy, $pos->z + $zz), new Leaves($this->type)); + } + } + } + if($leavesRadius > 0 and $yy === ($pos->y + $this->leavesBottomY + 1)){ + --$leavesRadius; + }elseif($leavesRadius < $this->leavesMaxRadius){ + ++$leavesRadius; + } + } + for($yy = 0; $yy < ($this->totalHeight - 1); ++$yy){ + $level->setBlockRaw(new Vector3($pos->x, $pos->y + $yy, $pos->z), new Wood($this->type)); + } + } + + +} \ No newline at end of file diff --git a/src/pocketmine/level/generator/object/TallGrass.php b/src/pocketmine/level/generator/object/TallGrass.php new file mode 100644 index 000000000..a6ac29f8a --- /dev/null +++ b/src/pocketmine/level/generator/object/TallGrass.php @@ -0,0 +1,49 @@ +nextRange($pos->x - $radius, $pos->x + $radius); + $z = $random->nextRange($pos->z - $radius, $pos->z + $radius); + if($level->level->getBlockID($x, $pos->y + 1, $z) === Block::AIR and $level->level->getBlockID($x, $pos->y, $z) === Block::GRASS){ + $t = $arr[$random->nextRange(0, $arrC)]; + $level->setBlockRaw(new Vector3($x, $pos->y + 1, $z), $t); + } + } + } +} \ No newline at end of file diff --git a/src/pocketmine/level/generator/object/Tree.php b/src/pocketmine/level/generator/object/Tree.php new file mode 100644 index 000000000..f9349c582 --- /dev/null +++ b/src/pocketmine/level/generator/object/Tree.php @@ -0,0 +1,69 @@ + true, + 2 => true, + 3 => true, + 6 => true, + 17 => true, + 18 => true, + ); + + public static function growTree(Level $level, Vector3 $pos, Random $random, $type = 0){ + switch($type & 0x03){ + case Sapling::SPRUCE: + if($random->nextRange(0, 1) === 1){ + $tree = new SpruceTree(); + }else{ + $tree = new PineTree(); + } + break; + case Sapling::BIRCH: + $tree = new SmallTree(); + $tree->type = Sapling::BIRCH; + break; + case Sapling::JUNGLE: + $tree = new SmallTree(); + $tree->type = Sapling::JUNGLE; + break; + case Sapling::OAK: + default: + /*if($random->nextRange(0, 9) === 0){ + $tree = new BigTree(); + }else{*/ + $tree = new SmallTree(); + //} + break; + } + if($tree->canPlaceObject($level, $pos, $random)){ + $tree->placeObject($level, $pos, $random); + } + } +} \ No newline at end of file diff --git a/src/pocketmine/level/generator/populator/Mineshaft.php b/src/pocketmine/level/generator/populator/Mineshaft.php new file mode 100644 index 000000000..6acebb8c2 --- /dev/null +++ b/src/pocketmine/level/generator/populator/Mineshaft.php @@ -0,0 +1,40 @@ +nextRange(0, self::$ODD) === 0){ + //$mineshaft = new Mineshaft($random); + } + } + +} \ No newline at end of file diff --git a/src/pocketmine/level/generator/populator/Ore.php b/src/pocketmine/level/generator/populator/Ore.php new file mode 100644 index 000000000..991d0ba88 --- /dev/null +++ b/src/pocketmine/level/generator/populator/Ore.php @@ -0,0 +1,49 @@ +oreTypes as $type){ + $ore = new ObjectOre($random, $type); + for($i = 0; $i < $ore->type->clusterCount; ++$i){ + $x = $random->nextRange($chunkX << 4, ($chunkX << 4) + 15); + $y = $random->nextRange($ore->type->minHeight, $ore->type->maxHeight); + $z = $random->nextRange($chunkZ << 4, ($chunkZ << 4) + 15); + if($ore->canPlaceObject($level, $x, $y, $z)){ + $ore->placeObject($level, new Vector3($x, $y, $z)); + } + } + } + } + + public function setOreTypes(array $types){ + $this->oreTypes = $types; + } +} \ No newline at end of file diff --git a/src/pocketmine/level/generator/populator/Pond.php b/src/pocketmine/level/generator/populator/Pond.php new file mode 100644 index 000000000..dbc63a22c --- /dev/null +++ b/src/pocketmine/level/generator/populator/Pond.php @@ -0,0 +1,59 @@ +nextRange(0, $this->waterOdd) === 0){ + $v = new Vector3( + $random->nextRange($chunkX << 4, ($chunkX << 4) + 16), + $random->nextRange(0, 128), + $random->nextRange($chunkZ << 4, ($chunkZ << 4) + 16) + ); + $pond = new \pocketmine\level\generator\object\Pond($random, new Water()); + if($pond->canPlaceObject($level, $v)){ + $pond->placeObject($level, $v); + } + } + } + + public function setWaterOdd($waterOdd){ + $this->waterOdd = $waterOdd; + } + + public function setLavaOdd($lavaOdd){ + $this->lavaOdd = $lavaOdd; + } + + public function setLavaSurfaceOdd($lavaSurfaceOdd){ + $this->lavaSurfaceOdd = $lavaSurfaceOdd; + } +} \ No newline at end of file diff --git a/src/pocketmine/level/generator/populator/Populator.php b/src/pocketmine/level/generator/populator/Populator.php new file mode 100644 index 000000000..fa44c8e63 --- /dev/null +++ b/src/pocketmine/level/generator/populator/Populator.php @@ -0,0 +1,32 @@ +randomAmount = $amount; + } + + public function setBaseAmount($amount){ + $this->baseAmount = $amount; + } + + public function populate(Level $level, $chunkX, $chunkZ, Random $random){ + $this->level = $level; + $amount = $random->nextRange(0, $this->randomAmount + 1) + $this->baseAmount; + for($i = 0; $i < $amount; ++$i){ + $x = $random->nextRange($chunkX << 4, ($chunkX << 4) + 15); + $z = $random->nextRange($chunkZ << 4, ($chunkZ << 4) + 15); + for($size = 30; $size > 0; --$size){ + $xx = $x - 7 + $random->nextRange(0, 15); + $zz = $z - 7 + $random->nextRange(0, 15); + $yy = $this->getHighestWorkableBlock($xx, $zz); + $vector = new Vector3($xx, $yy, $zz); + if($yy !== -1 and $this->canTallGrassStay($this->level->getBlockRaw($vector))){ + $this->level->setBlockRaw($vector, new BlockTallGrass(1)); + } + } + } + } + + private function canTallGrassStay(Block $block){ + return $block->getID() === Block::AIR and $block->getSide(0)->getID() === Block::GRASS; + } + + private function getHighestWorkableBlock($x, $z){ + for($y = 128; $y > 0; --$y){ + $b = $this->level->getBlockRaw(new Vector3($x, $y, $z)); + if($b->getID() === Block::AIR or $b->getID() === Block::LEAVES){ + if(--$y <= 0){ + return -1; + } + }else{ + break; + } + } + + return ++$y; + } +} \ No newline at end of file diff --git a/src/pocketmine/level/generator/populator/Tree.php b/src/pocketmine/level/generator/populator/Tree.php new file mode 100644 index 000000000..6369faf38 --- /dev/null +++ b/src/pocketmine/level/generator/populator/Tree.php @@ -0,0 +1,77 @@ +randomAmount = $amount; + } + + public function setBaseAmount($amount){ + $this->baseAmount = $amount; + } + + public function populate(Level $level, $chunkX, $chunkZ, Random $random){ + $this->level = $level; + $amount = $random->nextRange(0, $this->randomAmount + 1) + $this->baseAmount; + for($i = 0; $i < $amount; ++$i){ + $x = $random->nextRange($chunkX << 4, ($chunkX << 4) + 15); + $z = $random->nextRange($chunkZ << 4, ($chunkZ << 4) + 15); + $y = $this->getHighestWorkableBlock($x, $z); + if($y === -1){ + continue; + } + if($random->nextFloat() > 0.75){ + $meta = Sapling::BIRCH; + }else{ + $meta = Sapling::OAK; + } + ObjectTree::growTree($this->level, new Vector3($x, $y, $z), $random, $meta); + } + } + + private function getHighestWorkableBlock($x, $z){ + for($y = 128; $y > 0; --$y){ + $b = $this->level->getBlockRaw(new Vector3($x, $y, $z)); + if($b->getID() !== Block::DIRT and $b->getID() !== Block::GRASS){ + if(--$y <= 0){ + return -1; + } + }else{ + break; + } + } + + return ++$y; + } +} \ No newline at end of file diff --git a/src/pocketmine/math/AxisAlignedBB.php b/src/pocketmine/math/AxisAlignedBB.php new file mode 100644 index 000000000..b16120653 --- /dev/null +++ b/src/pocketmine/math/AxisAlignedBB.php @@ -0,0 +1,239 @@ +minX = $minX; + $this->minY = $minY; + $this->minZ = $minZ; + $this->maxX = $maxX; + $this->maxY = $maxY; + $this->maxZ = $maxZ; + } + + public function setBounds($minX, $minY, $minZ, $maxX, $maxY, $maxZ){ + $this->minX = $minX; + $this->minY = $minY; + $this->minZ = $minZ; + $this->maxX = $maxX; + $this->maxY = $maxY; + $this->maxZ = $maxZ; + + return $this; + } + + public function addCoord($x, $y, $z){ + $vec = clone $this; + if($x < 0){ + $vec->minX += $x; + }elseif($x > 0){ + $vec->maxX += $x; + } + + if($y < 0){ + $vec->minY += $y; + }elseif($y > 0){ + $vec->maxY += $y; + } + + if($z < 0){ + $vec->minZ += $z; + }elseif($z > 0){ + $vec->maxZ += $z; + } + + return $vec; + } + + public function expand($x, $y, $z){ + $vec = clone $this; + $vec->minX -= $x; + $vec->minY -= $y; + $vec->minZ -= $z; + $vec->maxX += $x; + $vec->maxY += $y; + $vec->maxZ += $z; + + return $vec; + } + + public function offset($x, $y, $z){ + $vec = clone $this; + $vec->minX += $x; + $vec->minY += $y; + $vec->minZ += $z; + $vec->maxX += $x; + $vec->maxY += $y; + $vec->maxZ += $z; + + return $vec; + } + + public function contract($x, $y, $z){ + $vec = clone $this; + $vec->minX += $x; + $vec->minY += $y; + $vec->minZ += $z; + $vec->maxX -= $x; + $vec->maxY -= $y; + $vec->maxZ -= $z; + + return $vec; + } + + public function setBB(AxisAlignedBB $bb){ + return new AxisAlignedBB( + min($this->minX, $bb->minX), + min($this->minY, $bb->minY), + min($this->minZ, $bb->minZ), + max($this->maxX, $bb->maxX), + max($this->maxY, $bb->maxY), + max($this->maxZ, $bb->maxZ) + ); + } + + public function getOffsetBoundingBox($x, $y, $z){ + return new AxisAlignedBB($this->minX + $x, $this->minY + $y, $this->minZ + $z, $this->maxX + $x, $this->maxY + $y, $this->maxZ + $z); + } + + public function calculateXOffset(AxisAlignedBB $bb, $x){ + if($bb->maxY <= $this->minY or $bb->minY >= $this->maxY){ + return $x; + } + if($bb->maxZ <= $this->minZ or $bb->minZ >= $this->maxZ){ + return $x; + } + if($x > 0 and $bb->maxX <= $this->minX){ + $x1 = $this->minX - $bb->maxX; + if($x1 < $x){ + $x = $x1; + } + } + if($x < 0 and $bb->minX >= $this->maxX){ + $x2 = $this->maxX - $bb->minX; + if($x2 > $x){ + $x = $x2; + } + } + + return $x; + } + + public function calculateYOffset(AxisAlignedBB $bb, $y){ + if($bb->maxX <= $this->minX or $bb->minX >= $this->maxX){ + return $y; + } + if($bb->maxZ <= $this->minZ or $bb->minZ >= $this->maxZ){ + return $y; + } + if($y > 0 and $bb->maxY <= $this->minY){ + $y1 = $this->minY - $bb->maxY; + if($y1 < $y){ + $y = $y1; + } + } + if($y < 0 and $bb->minY >= $this->maxY){ + $y2 = $this->maxY - $bb->minY; + if($y2 > $y){ + $y = $y2; + } + } + + return $y; + } + + public function calculateZOffset(AxisAlignedBB $bb, $z){ + if($bb->maxX <= $this->minX or $bb->minX >= $this->maxX){ + return $z; + } + if($bb->maxY <= $this->minY or $bb->minY >= $this->maxY){ + return $z; + } + if($z > 0 and $bb->maxZ <= $this->minZ){ + $z1 = $this->minZ - $bb->maxZ; + if($z1 < $z){ + $z = $z1; + } + } + if($z < 0 and $bb->minZ >= $this->maxZ){ + $z2 = $this->maxZ - $bb->minZ; + if($z2 > $z){ + $z = $z2; + } + } + + return $z; + } + + public function intersectsWith(AxisAlignedBB $bb){ + if($bb->maxX <= $this->minX or $bb->minX >= $this->maxX){ + return false; + } + if($bb->maxY <= $this->minY or $bb->minY >= $this->maxY){ + return false; + } + + return $bb->maxZ > $this->minZ and $bb->minZ < $this->maxZ; + } + + public function isVectorInside(Vector3 $vector){ + if($vector->x <= $this->minX or $vector->x >= $this->maxX){ + return false; + } + if($vector->y <= $this->minY or $vector->y >= $this->maxY){ + return false; + } + + return $vector->z > $this->minZ and $vector->z < $this->maxZ; + } + + public function getAverageEdgeLength(){ + return ($this->maxX - $this->minX + $this->maxY - $this->minY + $this->maxZ - $this->minZ) / 3; + } + + public function isVectorInYZ(Vector3 $vector){ + return $vector->y >= $this->minY and $vector->y <= $this->maxY and $vector->z >= $this->minZ and $vector->z <= $this->maxZ; + } + + public function isVectorInXZ(Vector3 $vector){ + return $vector->x >= $this->minX and $vector->x <= $this->maxX and $vector->z >= $this->minZ and $vector->z <= $this->maxZ; + } + + public function isVectorInXY(Vector3 $vector){ + return $vector->x >= $this->minX and $vector->x <= $this->maxX and $vector->y >= $this->minY and $vector->y <= $this->maxY; + } + + /* + public function calculateIntercept(...){ + + } + */ +} \ No newline at end of file diff --git a/src/pocketmine/math/Math.php b/src/pocketmine/math/Math.php new file mode 100644 index 000000000..6d48b77d2 --- /dev/null +++ b/src/pocketmine/math/Math.php @@ -0,0 +1,30 @@ +matrix[(int) $offset]); + } + + public function offsetGet($offset){ + return $this->matrix[(int) $offset]; + } + + public function offsetSet($offset, $value){ + $this->matrix[(int) $offset] = $value; + } + + public function offsetUnset($offset){ + unset($this->matrix[(int) $offset]); + } + + public function __construct($rows, $columns, array $set = array()){ + $this->rows = max(1, (int) $rows); + $this->columns = max(1, (int) $columns); + $this->set($set); + } + + public function set(array $m){ + for($r = 0; $r < $this->rows; ++$r){ + $this->matrix[$r] = array(); + for($c = 0; $c < $this->columns; ++$c){ + $this->matrix[$r][$c] = isset($m[$r][$c]) ? $m[$r][$c] : 0; + } + } + } + + public function getRows(){ + return ($this->rows); + } + + public function getColumns(){ + return ($this->columns); + } + + public function setElement($row, $column, $value){ + if($row > $this->rows or $row < 0 or $column > $this->columns or $column < 0){ + return false; + } + $this->matrix[(int) $row][(int) $column] = $value; + + return true; + } + + public function getElement($row, $column){ + if($row > $this->rows or $row < 0 or $column > $this->columns or $column < 0){ + return false; + } + + return $this->matrix[(int) $row][(int) $column]; + } + + public function isSquare(){ + return $this->rows === $this->columns; + } + + public function add(Matrix $matrix){ + if($this->rows !== $matrix->getRows() or $this->columns !== $matrix->getColumns()){ + return false; + } + $result = new Matrix($this->rows, $this->columns); + for($r = 0; $r < $this->rows; ++$r){ + for($c = 0; $c < $this->columns; ++$c){ + $result->setElement($r, $c, $this->matrix[$r][$c] + $matrix->getElement($r, $c)); + } + } + + return $result; + } + + public function substract(Matrix $matrix){ + if($this->rows !== $matrix->getRows() or $this->columns !== $matrix->getColumns()){ + return false; + } + $result = clone $this; + for($r = 0; $r < $this->rows; ++$r){ + for($c = 0; $c < $this->columns; ++$c){ + $result->setElement($r, $c, $this->matrix[$r][$c] - $matrix->getElement($r, $c)); + } + } + + return $result; + } + + public function multiplyScalar($number){ + $result = clone $this; + for($r = 0; $r < $this->rows; ++$r){ + for($c = 0; $c < $this->columns; ++$c){ + $result->setElement($r, $c, $this->matrix[$r][$c] * $number); + } + } + + return $result; + } + + + public function divideScalar($number){ + $result = clone $this; + for($r = 0; $r < $this->rows; ++$r){ + for($c = 0; $c < $this->columns; ++$c){ + $result->setElement($r, $c, $this->matrix[$r][$c] / $number); + } + } + + return $result; + } + + public function transpose(){ + $result = new Matrix($this->columns, $this->rows); + for($r = 0; $r < $this->rows; ++$r){ + for($c = 0; $c < $this->columns; ++$c){ + $result->setElement($c, $r, $this->matrix[$r][$c]); + } + } + + return $result; + } + + //Naive Matrix product, O(n^3) + public function product(Matrix $matrix){ + if($this->columns !== $matrix->getRows()){ + return false; + } + $c = $matrix->getColumns(); + $result = new Matrix($this->rows, $c); + for($i = 0; $i < $this->rows; ++$i){ + for($j = 0; $j < $c; ++$j){ + $sum = 0; + for($k = 0; $k < $this->columns; ++$k){ + $sum += $this->matrix[$i][$k] * $matrix->getElement($k, $j); + } + $result->setElement($i, $j, $sum); + } + } + + return $result; + } + + + //Computation of the determinant of 2x2 and 3x3 matrices + public function determinant(){ + if($this->isSquare() !== true){ + return false; + } + switch($this->rows){ + case 1: + return 0; + case 2: + return $this->matrix[0][0] * $this->matrix[1][1] - $this->matrix[0][1] * $this->matrix[1][0]; + case 3: + return $this->matrix[0][0] * $this->matrix[1][1] * $this->matrix[2][2] + $this->matrix[0][1] * $this->matrix[1][2] * $this->matrix[2][0] + $this->matrix[0][2] * $this->matrix[1][0] * $this->matrix[2][1] - $this->matrix[2][0] * $this->matrix[1][1] * $this->matrix[0][2] - $this->matrix[2][1] * $this->matrix[1][2] * $this->matrix[0][0] - $this->matrix[2][2] * $this->matrix[1][0] * $this->matrix[0][1]; + } + + return false; + } + + + public function __toString(){ + $s = ""; + for($r = 0; $r < $this->rows; ++$r){ + $s .= implode(",", $this->matrix[$r]) . ";"; + } + + return "Matrix({$this->rows}x{$this->columns};" . substr($s, 0, -1) . ")"; + } + +} \ No newline at end of file diff --git a/src/pocketmine/math/Vector2.php b/src/pocketmine/math/Vector2.php new file mode 100644 index 000000000..8cee4e103 --- /dev/null +++ b/src/pocketmine/math/Vector2.php @@ -0,0 +1,130 @@ +x = $x; + $this->y = $y; + } + + public function getX(){ + return $this->x; + } + + public function getY(){ + return $this->y; + } + + public function getFloorX(){ + return (int) $this->x; + } + + public function getFloorY(){ + return (int) $this->y; + } + + public function add($x = 0, $y = 0){ + if(($x instanceof Vector2) === true){ + return $this->add($x->x, $x->y); + }else{ + return new Vector2($this->x + $x, $this->y + $y); + } + } + + public function subtract($x = 0, $y = 0){ + if(($x instanceof Vector2) === true){ + return $this->add(-$x->x, -$x->y); + }else{ + return $this->add(-$x, -$y); + } + } + + public function ceil(){ + return new Vector2((int) ($this->x + 1), (int) ($this->y + 1)); + } + + public function floor(){ + return new Vector2((int) $this->x, (int) $this->y); + } + + public function round(){ + return new Vector2(round($this->x), round($this->y)); + } + + public function abs(){ + return new Vector2(abs($this->x), abs($this->y)); + } + + public function multiply($number){ + return new Vector2($this->x * $number, $this->y * $number); + } + + public function divide($number){ + return new Vector2($this->x / $number, $this->y / $number); + } + + public function distance($x = 0, $y = 0){ + if(($x instanceof Vector2) === true){ + return sqrt($this->distanceSquared($x->x, $x->y)); + }else{ + return sqrt($this->distanceSquared($x, $y)); + } + } + + public function distanceSquared($x = 0, $y = 0){ + if(($x instanceof Vector2) === true){ + return $this->distanceSquared($x->x, $x->y); + }else{ + return pow($this->x - $x, 2) + pow($this->y - $y, 2); + } + } + + public function length(){ + return sqrt($this->lengthSquared()); + } + + public function lengthSquared(){ + return $this->x * $this->x + $this->y * $this->y; + } + + public function normalize(){ + $len = $this->length(); + if($len != 0){ + return $this->divide($len); + } + + return new Vector2(0, 0); + } + + public function dot(Vector2 $v){ + return $this->x * $v->x + $this->y * $v->y; + } + + public function __toString(){ + return "Vector2(x=" . $this->x . ",y=" . $this->y . ")"; + } + +} \ No newline at end of file diff --git a/src/pocketmine/math/Vector3.php b/src/pocketmine/math/Vector3.php new file mode 100644 index 000000000..fcc6c04cc --- /dev/null +++ b/src/pocketmine/math/Vector3.php @@ -0,0 +1,188 @@ +x = $x; + $this->y = $y; + $this->z = $z; + } + + public function getX(){ + return $this->x; + } + + public function getY(){ + return $this->y; + } + + public function getZ(){ + return $this->z; + } + + public function getFloorX(){ + return (int) $this->x; + } + + public function getFloorY(){ + return (int) $this->y; + } + + public function getFloorZ(){ + return (int) $this->z; + } + + public function getRight(){ + return $this->getX(); + } + + public function getUp(){ + return $this->getY(); + } + + public function getForward(){ + return $this->getZ(); + } + + public function getSouth(){ + return $this->getX(); + } + + public function getWest(){ + return $this->getZ(); + } + + public function add($x = 0, $y = 0, $z = 0){ + if(($x instanceof Vector3) === true){ + return $this->add($x->x, $x->y, $x->z); + }else{ + return new Vector3($this->x + $x, $this->y + $y, $this->z + $z); + } + } + + public function subtract($x = 0, $y = 0, $z = 0){ + if(($x instanceof Vector3) === true){ + return $this->add(-$x->x, -$x->y, -$x->z); + }else{ + return $this->add(-$x, -$y, -$z); + } + } + + public function multiply($number){ + return new Vector3($this->x * $number, $this->y * $number, $this->z * $number); + } + + public function divide($number){ + return new Vector3($this->x / $number, $this->y / $number, $this->z / $number); + } + + public function ceil(){ + return new Vector3((int) ($this->x + 1), (int) ($this->y + 1), (int) ($this->z + 1)); + } + + public function floor(){ + return new Vector3((int) $this->x, (int) $this->y, (int) $this->z); + } + + public function round(){ + return new Vector3(round($this->x), round($this->y), round($this->z)); + } + + public function abs(){ + return new Vector3(abs($this->x), abs($this->y), abs($this->z)); + } + + public function getSide($side){ + switch((int) $side){ + case 0: + return new Vector3($this->x, $this->y - 1, $this->z); + case 1: + return new Vector3($this->x, $this->y + 1, $this->z); + case 2: + return new Vector3($this->x, $this->y, $this->z - 1); + case 3: + return new Vector3($this->x, $this->y, $this->z + 1); + case 4: + return new Vector3($this->x - 1, $this->y, $this->z); + case 5: + return new Vector3($this->x + 1, $this->y, $this->z); + default: + return $this; + } + } + + public function distance(Vector3 $pos){ + return sqrt($this->distanceSquared($pos)); + } + + public function distanceSquared(Vector3 $pos){ + return pow($this->x - $pos->x, 2) + pow($this->y - $pos->y, 2) + pow($this->z - $pos->z, 2); + } + + public function maxPlainDistance($x = 0, $z = 0){ + if(($x instanceof Vector3) === true){ + return $this->maxPlainDistance($x->x, $x->z); + }else{ + return max(abs($this->x - $x), abs($this->z - $z)); + } + } + + public function length(){ + return sqrt($this->lengthSquared()); + } + + public function lengthSquared(){ + return $this->x * $this->x + $this->y * $this->y + $this->z * $this->z; + } + + public function normalize(){ + $len = $this->length(); + if($len != 0){ + return $this->divide($len); + } + + return new Vector3(0, 0, 0); + } + + public function dot(Vector3 $v){ + return $this->x * $v->x + $this->y * $v->y + $this->z * $v->z; + } + + public function cross(Vector3 $v){ + return new Vector3( + $this->y * $v->z - $this->z * $v->y, + $this->z * $v->x - $this->x * $v->z, + $this->x * $v->y - $this->y * $v->x + ); + } + + public function __toString(){ + return "Vector3(x=" . $this->x . ",y=" . $this->y . ",z=" . $this->z . ")"; + } + +} \ No newline at end of file diff --git a/src/pocketmine/math/VectorMath.php b/src/pocketmine/math/VectorMath.php new file mode 100644 index 000000000..8bf57c8da --- /dev/null +++ b/src/pocketmine/math/VectorMath.php @@ -0,0 +1,31 @@ +offset = strlen($this->buffer) - 1; + + return ""; + }elseif($len === true){ + return substr($this->buffer, $this->offset); + } + + $buffer = ""; + for(; $len > 0; --$len, ++$this->offset){ + $buffer .= @$this->buffer{$this->offset}; + } + + return $buffer; + } + + public function put($v){ + $this->buffer .= $v; + } + + public function feof(){ + return !isset($this->buffer{$this->offset}); + } + + public function __construct($endianness = self::LITTLE_ENDIAN){ + $this->offset = 0; + $this->endianness = $endianness & 0x01; + } + + public function read($buffer){ + $this->offset = 0; + $this->buffer = $buffer; + $this->data = $this->readTag(); + $this->buffer = ""; + } + + public function readCompressed($buffer){ + $this->read(\gzdecode($buffer)); + } + + public function write(){ + $this->offset = 0; + if($this->data instanceof Compound){ + $this->writeTag($this->data); + + return $this->buffer; + }else{ + return false; + } + } + + public function writeCompressed(){ + if(($write = $this->write()) !== false){ + return \gzencode($write, 9); + } + + return false; + } + + public function readTag(){ + switch($this->getByte()){ + case NBT::TAG_Byte: + $tag = new Byte($this->getString()); + $tag->read($this); + break; + case NBT::TAG_Short: + $tag = new Short($this->getString()); + $tag->read($this); + break; + case NBT::TAG_Int: + $tag = new Int($this->getString()); + $tag->read($this); + break; + case NBT::TAG_Long: + $tag = new Long($this->getString()); + $tag->read($this); + break; + case NBT::TAG_Float: + $tag = new Float($this->getString()); + $tag->read($this); + break; + case NBT::TAG_Double: + $tag = new Double($this->getString()); + $tag->read($this); + break; + case NBT::TAG_Byte_Array: + $tag = new Byte_Array($this->getString()); + $tag->read($this); + break; + case NBT::TAG_String: + $tag = new String($this->getString()); + $tag->read($this); + break; + case NBT::TAG_Enum: + $tag = new Enum($this->getString()); + $tag->read($this); + break; + case NBT::TAG_Compound: + $tag = new Compound($this->getString()); + $tag->read($this); + break; + case NBT::TAG_Int_Array: + $tag = new Int_Array($this->getString()); + $tag->read($this); + break; + + case NBT::TAG_End: //No named tag + default: + $tag = new End; + break; + } + return $tag; + } + + public function writeTag(Tag $tag){ + $this->putByte($tag->getType()); + if($tag instanceof NamedTAG){ + $this->putString($tag->getName()); + } + $tag->write($this); + } + + public function getByte($signed = false){ + return Utils::readByte($this->get(1), $signed); + } + + public function putByte($v){ + $this->buffer .= Utils::writeByte($v); + } + + public function getShort(){ + return $this->endianness === self::BIG_ENDIAN ? Utils::readShort($this->get(2)) : Utils::readLShort($this->get(2)); + } + + public function putShort($v){ + $this->buffer .= $this->endianness === self::BIG_ENDIAN ? Utils::writeShort($v) : Utils::writeLShort($v); + } + + public function getInt(){ + return $this->endianness === self::BIG_ENDIAN ? Utils::readInt($this->get(4)) : Utils::readLInt($this->get(4)); + } + + public function putInt($v){ + $this->buffer .= $this->endianness === self::BIG_ENDIAN ? Utils::writeInt($v) : Utils::writeLInt($v); + } + + public function getLong(){ + return $this->endianness === self::BIG_ENDIAN ? Utils::readLong($this->get(8)) : Utils::readLLong($this->get(8)); + } + + public function putLong($v){ + $this->buffer .= $this->endianness === self::BIG_ENDIAN ? Utils::writeLong($v) : Utils::writeLLong($v); + } + + public function getFloat(){ + return $this->endianness === self::BIG_ENDIAN ? Utils::readFloat($this->get(4)) : Utils::readLFloat($this->get(4)); + } + + public function putFloat($v){ + $this->buffer .= $this->endianness === self::BIG_ENDIAN ? Utils::writeFloat($v) : Utils::writeLFloat($v); + } + + public function getDouble(){ + return $this->endianness === self::BIG_ENDIAN ? Utils::readDouble($this->get(8)) : Utils::readLDouble($this->get(8)); + } + + public function putDouble($v){ + $this->buffer .= $this->endianness === self::BIG_ENDIAN ? Utils::writeDouble($v) : Utils::writeLDouble($v); + } + + public function getString(){ + return $this->get($this->getShort()); + } + + public function putString($v){ + $this->putShort(strlen($v)); + $this->buffer .= $v; + } + + public function getData(){ + return $this->data; + } + + public function setData(Compound $data){ + $this->data = $data; + } + +} \ No newline at end of file diff --git a/src/pocketmine/nbt/tag/Byte.php b/src/pocketmine/nbt/tag/Byte.php new file mode 100644 index 000000000..883ff834a --- /dev/null +++ b/src/pocketmine/nbt/tag/Byte.php @@ -0,0 +1,39 @@ +value = $nbt->getByte(true); + } + + public function write(NBT $nbt){ + $nbt->putByte($this->value); + } +} \ No newline at end of file diff --git a/src/pocketmine/nbt/tag/Byte_Array.php b/src/pocketmine/nbt/tag/Byte_Array.php new file mode 100644 index 000000000..ad43191a1 --- /dev/null +++ b/src/pocketmine/nbt/tag/Byte_Array.php @@ -0,0 +1,40 @@ +value = $nbt->get($nbt->getInt()); + } + + public function write(NBT $nbt){ + $nbt->putInt(strlen($this->value)); + $nbt->put($this->value); + } +} \ No newline at end of file diff --git a/src/pocketmine/nbt/tag/Compound.php b/src/pocketmine/nbt/tag/Compound.php new file mode 100644 index 000000000..1163bf67b --- /dev/null +++ b/src/pocketmine/nbt/tag/Compound.php @@ -0,0 +1,85 @@ +name = $name; + foreach($value as $tag){ + $this->{$tag->getName()} = $tag; + } + } + + public function offsetExists($offset){ + return isset($this->{$offset}); + } + + public function offsetGet($offset){ + if($this->{$offset} instanceof Tag){ + if($this->{$offset} instanceof \ArrayAccess){ + return $this->{$offset}; + }else{ + return $this->{$offset}->getValue(); + } + } + + return null; + } + + public function offsetSet($offset, $value){ + if($value instanceof Tag){ + $this->{$offset} = $value; + }elseif(isset($this->{$offset}) and $this->{$offset} instanceof Tag){ + $this->{$offset}->setValue($value); + } + } + + public function offsetUnset($offset){ + unset($this->{$offset}); + } + + public function getType(){ + return NBT::TAG_Compound; + } + + public function read(NBT $nbt){ + $this->value = array(); + do{ + $tag = $nbt->readTag(); + if($tag instanceof NamedTag and $tag->getName() !== ""){ + $this->{$tag->getName()} = $tag; + } + }while(!($tag instanceof End) and !$nbt->feof()); + } + + public function write(NBT $nbt){ + foreach($this as $tag){ + if($tag instanceof Tag and !($tag instanceof End)){ + $nbt->writeTag($tag); + } + } + $nbt->writeTag(new End); + } +} \ No newline at end of file diff --git a/src/pocketmine/nbt/tag/Double.php b/src/pocketmine/nbt/tag/Double.php new file mode 100644 index 000000000..ecac412cb --- /dev/null +++ b/src/pocketmine/nbt/tag/Double.php @@ -0,0 +1,39 @@ +value = $nbt->getDouble(); + } + + public function write(NBT $nbt){ + $nbt->putDouble($this->value); + } +} \ No newline at end of file diff --git a/src/pocketmine/nbt/tag/End.php b/src/pocketmine/nbt/tag/End.php new file mode 100644 index 000000000..660998b93 --- /dev/null +++ b/src/pocketmine/nbt/tag/End.php @@ -0,0 +1,39 @@ +name = $name; + foreach($value as $k => $v){ + $this->{$k} = $v; + } + } + + public function offsetExists($offset){ + return isset($this->{$offset}); + } + + public function offsetGet($offset){ + if($this->{$offset} instanceof Tag){ + if($this->{$offset} instanceof \ArrayAccess){ + return $this->{$offset}; + }else{ + return $this->{$offset}->getValue(); + } + } + + return null; + } + + public function offsetSet($offset, $value){ + if($value instanceof Tag){ + $this->{$offset} = $value; + }elseif($this->{$offset} instanceof Tag){ + $this->{$offset}->setValue($value); + } + } + + public function offsetUnset($offset){ + unset($this->{$offset}); + } + + public function getType(){ + return NBT::TAG_Enum; + } + + public function setTagType($type){ + $this->tagType = $type; + } + + public function getTagType(){ + return $this->tagType; + } + + public function read(NBT $nbt){ + $this->value = array(); + $this->tagType = $nbt->getByte(); + $size = $nbt->getInt(); + for($i = 0; $i < $size and !$nbt->feof(); ++$i){ + switch($this->tagType){ + case NBT::TAG_Byte: + $tag = new Byte(false); + $tag->read($nbt); + $this->{$i} = $tag; + break; + case NBT::TAG_Short: + $tag = new Short(false); + $tag->read($nbt); + $this->{$i} = $tag; + break; + case NBT::TAG_Int: + $tag = new Int(false); + $tag->read($nbt); + $this->{$i} = $tag; + break; + case NBT::TAG_Long: + $tag = new Long(false); + $tag->read($nbt); + $this->{$i} = $tag; + break; + case NBT::TAG_Float: + $tag = new Float(false); + $tag->read($nbt); + $this->{$i} = $tag; + break; + case NBT::TAG_Double: + $tag = new Double(false); + $tag->read($nbt); + $this->{$i} = $tag; + break; + case NBT::TAG_Byte_Array: + $tag = new Byte_Array(false); + $tag->read($nbt); + $this->{$i} = $tag; + break; + case NBT::TAG_String: + $tag = new String(false); + $tag->read($nbt); + $this->{$i} = $tag; + break; + case NBT::TAG_Enum: + $tag = new TagEnum(false); + $tag->read($nbt); + $this->{$i} = $tag; + break; + case NBT::TAG_Compound: + $tag = new Compound(false); + $tag->read($nbt); + $this->{$i} = $tag; + break; + case NBT::TAG_Int_Array: + $tag = new Int_Array(false); + $tag->read($nbt); + $this->{$i} = $tag; + break; + } + } + } + + public function write(NBT $nbt){ + if(!isset($this->tagType)){ + foreach($this as $tag){ + if($tag instanceof Tag){ + if(!isset($id)){ + $id = $tag->getType(); + }elseif($id !== $tag->getType()){ + return false; + } + } + } + $this->tagType = @$id; + } + + $nbt->putByte($this->tagType); + + $tags = array(); + foreach($this as $tag){ + if($tag instanceof Tag){ + $tags[] = $tag; + } + } + $nbt->putInt(count($tags)); + foreach($tags as $tag){ + $tag->write($nbt); + } + } +} \ No newline at end of file diff --git a/src/pocketmine/nbt/tag/Float.php b/src/pocketmine/nbt/tag/Float.php new file mode 100644 index 000000000..5294ff45f --- /dev/null +++ b/src/pocketmine/nbt/tag/Float.php @@ -0,0 +1,39 @@ +value = $nbt->getFloat(); + } + + public function write(NBT $nbt){ + $nbt->putFloat($this->value); + } +} \ No newline at end of file diff --git a/src/pocketmine/nbt/tag/Int.php b/src/pocketmine/nbt/tag/Int.php new file mode 100644 index 000000000..4f4bf8091 --- /dev/null +++ b/src/pocketmine/nbt/tag/Int.php @@ -0,0 +1,39 @@ +value = $nbt->getInt(); + } + + public function write(NBT $nbt){ + $nbt->putInt($this->value); + } +} \ No newline at end of file diff --git a/src/pocketmine/nbt/tag/Int_Array.php b/src/pocketmine/nbt/tag/Int_Array.php new file mode 100644 index 000000000..76fa777ee --- /dev/null +++ b/src/pocketmine/nbt/tag/Int_Array.php @@ -0,0 +1,46 @@ +value = array(); + $size = $nbt->getInt(); + for($i = 0; $i < $size and !$nbt->feof(); ++$i){ + $this->value[] = $nbt->getInt(); + } + } + + public function write(NBT $nbt){ + $nbt->putInt(count($this->value)); + foreach($this->value as $v){ + $nbt->putInt($v); + } + } +} \ No newline at end of file diff --git a/src/pocketmine/nbt/tag/Long.php b/src/pocketmine/nbt/tag/Long.php new file mode 100644 index 000000000..6e1031663 --- /dev/null +++ b/src/pocketmine/nbt/tag/Long.php @@ -0,0 +1,39 @@ +value = $nbt->getLong(); + } + + public function write(NBT $nbt){ + $nbt->putLong($this->value); + } +} \ No newline at end of file diff --git a/src/pocketmine/nbt/tag/NamedTag.php b/src/pocketmine/nbt/tag/NamedTag.php new file mode 100644 index 000000000..70fca449e --- /dev/null +++ b/src/pocketmine/nbt/tag/NamedTag.php @@ -0,0 +1,47 @@ +name = $name; + if($value !== false){ + $this->value = $value; + } + } + + public function getName(){ + return $this->name === false ? "" : $this->name; + } + + public function setName($name){ + $this->name = $name; + } +} \ No newline at end of file diff --git a/src/pocketmine/nbt/tag/Short.php b/src/pocketmine/nbt/tag/Short.php new file mode 100644 index 000000000..56f79b1b5 --- /dev/null +++ b/src/pocketmine/nbt/tag/Short.php @@ -0,0 +1,39 @@ +value = $nbt->getShort(); + } + + public function write(NBT $nbt){ + $nbt->putShort($this->value); + } +} \ No newline at end of file diff --git a/src/pocketmine/nbt/tag/String.php b/src/pocketmine/nbt/tag/String.php new file mode 100644 index 000000000..2edf10b00 --- /dev/null +++ b/src/pocketmine/nbt/tag/String.php @@ -0,0 +1,40 @@ +value = $nbt->get($nbt->getShort()); + } + + public function write(NBT $nbt){ + $nbt->putShort(strlen($this->value)); + $nbt->put($this->value); + } +} \ No newline at end of file diff --git a/src/pocketmine/nbt/tag/Tag.php b/src/pocketmine/nbt/tag/Tag.php new file mode 100644 index 000000000..4730acf3f --- /dev/null +++ b/src/pocketmine/nbt/tag/Tag.php @@ -0,0 +1,50 @@ +value; + } + + public abstract function getType(); + + public function setValue($value){ + $this->value = $value; + } + + abstract public function write(NBT $nbt); + + abstract public function read(NBT $nbt); + + public final function __toString(){ + return $this->value; + } +} diff --git a/src/pocketmine/network/Packet.php b/src/pocketmine/network/Packet.php new file mode 100644 index 000000000..3f7a3d3d4 --- /dev/null +++ b/src/pocketmine/network/Packet.php @@ -0,0 +1,30 @@ +server = $server; + $this->port = $port; + $this->serverip = $serverip; + $this->bandwidthUp = 0; + $this->bandwidthDown = 0; + $this->bandwidthTime = microtime(true); + $this->packets = new \Threaded(); + $this->queue = new \Threaded(); + $this->stop = false; + + //Load the classes so the Thread gets them + Info::isValid(0); + new Packet(0); + new QueryPacket(); + new RakNetPacket(0); + + $this->start(PTHREADS_INHERIT_ALL); + } + + public function close(){ + $this->synchronized(function (){ + $this->stop = true; + socket_close($this->socket); + }); + } + + /** + * Upload speed in bytes/s + * + * @return float + */ + public function getUploadSpeed(){ + return $this->bandwidthUp / max(1, microtime(true) - $this->bandwidthTime); + } + + /** + * Download speed in bytes/s + * + * @return float + */ + public function getDownloadSpeed(){ + return $this->bandwidthDown / max(1, microtime(true) - $this->bandwidthTime); + } + + + /** + * @return Packet + */ + public function readPacket(){ + return $this->packets->shift(); + } + + /** + * @param Packet $packet + * + * @return int + */ + public function writePacket(Packet $packet){ + $this->queue[] = $packet; + + return strlen($packet->buffer); + } + + public function run(){ + $autoloader = new \SplClassLoader(); + $autoloader->add("pocketmine", array( + \pocketmine\PATH . "src" + )); + $autoloader->register(true); + + $this->socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); + socket_set_option($this->socket, SOL_SOCKET, SO_BROADCAST, 1); //Allow sending broadcast messages + if(@socket_bind($this->socket, $this->serverip, $this->port) === true){ + socket_set_option($this->socket, SOL_SOCKET, SO_REUSEADDR, 0); + @socket_set_option($this->socket, SOL_SOCKET, SO_SNDBUF, 1024 * 1024 * 2); //2MB + @socket_set_option($this->socket, SOL_SOCKET, SO_RCVBUF, 1024 * 1024); //1MB + }else{ + console("[SEVERE] **** FAILED TO BIND TO " . $this->serverip . ":" . $this->port . "!", true, true, 0); + console("[SEVERE] Perhaps a server is already running on that port?", true, true, 0); + exit(1); + } + socket_set_nonblock($this->socket); + + $count = 0; + while($this->stop === false){ + if($this->getPacket() === false and $this->putPacket() === false){ + ++$count; + }else{ + $count = 0; + } + if($count > 128){ + $this->wait(100000); + } + } + } + + private function putPacket(){ + if(($packet = $this->queue->shift()) instanceof Packet){ + if($packet instanceof RakNetPacket){ + $packet->encode(); + } + $this->bandwidthUp += @socket_sendto($this->socket, $packet->buffer, strlen($packet->buffer), 0, $packet->ip, $packet->port); + + return true; + } + + return false; + } + + private function getPacket(){ + $buffer = null; + $source = null; + $port = null; + $len = @socket_recvfrom($this->socket, $buffer, 65535, 0, $source, $port); + if($len === false or $len == 0){ + return false; + } + $this->bandwidthDown += $len; + $pid = ord($buffer{0}); + if(Info::isValid($pid)){ + $packet = new RakNetPacket($pid); + $packet->buffer =& $buffer; + $packet->ip = $source; + $packet->port = $port; + $packet->decode(); + }elseif($pid === 0xfe and $buffer{1} === "\xfd"){ + $packet = new QueryPacket; + $packet->ip = $source; + $packet->port = $port; + $packet->buffer =& $buffer; + }else{ + $packet = new Packet($pid); + $packet->ip = $source; + $packet->port = $port; + $packet->buffer =& $buffer; + } + $this->packets[] = $packet; + + return true; + } + +} + +?> \ No newline at end of file diff --git a/src/pocketmine/network/protocol/AddEntityPacket.php b/src/pocketmine/network/protocol/AddEntityPacket.php new file mode 100644 index 000000000..784ca4d2b --- /dev/null +++ b/src/pocketmine/network/protocol/AddEntityPacket.php @@ -0,0 +1,59 @@ +reset(); + $this->putInt($this->eid); + $this->putByte($this->type); + $this->putFloat($this->x); + $this->putFloat($this->y); + $this->putFloat($this->z); + $this->putInt($this->did); + if($this->did > 0){ + $this->putShort($this->speedX); + $this->putShort($this->speedY); + $this->putShort($this->speedZ); + } + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/AddItemEntityPacket.php b/src/pocketmine/network/protocol/AddItemEntityPacket.php new file mode 100644 index 000000000..8a7fa8384 --- /dev/null +++ b/src/pocketmine/network/protocol/AddItemEntityPacket.php @@ -0,0 +1,55 @@ +reset(); + $this->putInt($this->eid); + $this->putSlot($this->item); + $this->putFloat($this->x); + $this->putFloat($this->y); + $this->putFloat($this->z); + $this->putByte($this->yaw); + $this->putByte($this->pitch); + $this->putByte($this->roll); + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/AddMobPacket.php b/src/pocketmine/network/protocol/AddMobPacket.php new file mode 100644 index 000000000..31d95812e --- /dev/null +++ b/src/pocketmine/network/protocol/AddMobPacket.php @@ -0,0 +1,56 @@ +reset(); + $this->putInt($this->eid); + $this->putInt($this->type); + $this->putFloat($this->x); + $this->putFloat($this->y); + $this->putFloat($this->z); + $this->putByte($this->yaw); + $this->putByte($this->pitch); + $this->put(Utils::writeMetadata($this->metadata)); + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/AddPaintingPacket.php b/src/pocketmine/network/protocol/AddPaintingPacket.php new file mode 100644 index 000000000..97ff094b3 --- /dev/null +++ b/src/pocketmine/network/protocol/AddPaintingPacket.php @@ -0,0 +1,51 @@ +reset(); + $this->putInt($this->eid); + $this->putInt($this->x); + $this->putInt($this->y); + $this->putInt($this->z); + $this->putInt($this->direction); + $this->putString($this->title); + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/AddPlayerPacket.php b/src/pocketmine/network/protocol/AddPlayerPacket.php new file mode 100644 index 000000000..4e32892e6 --- /dev/null +++ b/src/pocketmine/network/protocol/AddPlayerPacket.php @@ -0,0 +1,62 @@ +reset(); + $this->putLong($this->clientID); + $this->putString($this->username); + $this->putInt($this->eid); + $this->putFloat($this->x); + $this->putFloat($this->y); + $this->putFloat($this->z); + $this->putByte($this->yaw); + $this->putByte($this->pitch); + $this->putShort($this->unknown1); + $this->putShort($this->unknown2); + $this->put(Utils::writeMetadata($this->metadata)); + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/AdventureSettingsPacket.php b/src/pocketmine/network/protocol/AdventureSettingsPacket.php new file mode 100644 index 000000000..66ad09d91 --- /dev/null +++ b/src/pocketmine/network/protocol/AdventureSettingsPacket.php @@ -0,0 +1,41 @@ +reset(); + $this->putInt($this->flags); + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/AnimatePacket.php b/src/pocketmine/network/protocol/AnimatePacket.php new file mode 100644 index 000000000..b418cc148 --- /dev/null +++ b/src/pocketmine/network/protocol/AnimatePacket.php @@ -0,0 +1,44 @@ +action = $this->getByte(); + $this->eid = $this->getInt(); + } + + public function encode(){ + $this->reset(); + $this->putByte($this->action); + $this->putInt($this->eid); + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/ChatPacket.php b/src/pocketmine/network/protocol/ChatPacket.php new file mode 100644 index 000000000..4dd9dc762 --- /dev/null +++ b/src/pocketmine/network/protocol/ChatPacket.php @@ -0,0 +1,41 @@ +reset(); + $this->putString($this->message); + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/ChunkDataPacket.php b/src/pocketmine/network/protocol/ChunkDataPacket.php new file mode 100644 index 000000000..045704209 --- /dev/null +++ b/src/pocketmine/network/protocol/ChunkDataPacket.php @@ -0,0 +1,45 @@ +reset(); + $this->putInt($this->chunkX); + $this->putInt($this->chunkZ); + $this->put($this->data); + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/ClientConnectPacket.php b/src/pocketmine/network/protocol/ClientConnectPacket.php new file mode 100644 index 000000000..1f6ac57fd --- /dev/null +++ b/src/pocketmine/network/protocol/ClientConnectPacket.php @@ -0,0 +1,44 @@ +clientID = $this->getLong(); + $this->session = $this->getLong(); + $this->unknown1 = $this->get(1); + } + + public function encode(){ + + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/ClientHandshakePacket.php b/src/pocketmine/network/protocol/ClientHandshakePacket.php new file mode 100644 index 000000000..d742986ab --- /dev/null +++ b/src/pocketmine/network/protocol/ClientHandshakePacket.php @@ -0,0 +1,54 @@ +cookie = $this->get(4); + $this->security = $this->get(1); + $this->port = $this->getShort(true); + $this->dataArray0 = $this->get($this->getByte()); + $this->dataArray = $this->getDataArray(9); + $this->timestamp = $this->get(2); + $this->session2 = $this->getLong(); + $this->session = $this->getLong(); + } + + public function encode(){ + + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/ContainerClosePacket.php b/src/pocketmine/network/protocol/ContainerClosePacket.php new file mode 100644 index 000000000..46f2be2d8 --- /dev/null +++ b/src/pocketmine/network/protocol/ContainerClosePacket.php @@ -0,0 +1,41 @@ +windowid = $this->getByte(); + } + + public function encode(){ + $this->reset(); + $this->putByte($this->windowid); + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/ContainerOpenPacket.php b/src/pocketmine/network/protocol/ContainerOpenPacket.php new file mode 100644 index 000000000..e7713242c --- /dev/null +++ b/src/pocketmine/network/protocol/ContainerOpenPacket.php @@ -0,0 +1,51 @@ +reset(); + $this->putByte($this->windowid); + $this->putByte($this->type); + $this->putByte($this->slots); + $this->putInt($this->x); + $this->putInt($this->y); + $this->putInt($this->z); + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/ContainerSetContentPacket.php b/src/pocketmine/network/protocol/ContainerSetContentPacket.php new file mode 100644 index 000000000..5163e0697 --- /dev/null +++ b/src/pocketmine/network/protocol/ContainerSetContentPacket.php @@ -0,0 +1,63 @@ +windowid = $this->getByte(); + $count = $this->getShort(); + for($s = 0; $s < $count and !$this->feof(); ++$s){ + $this->slots[$s] = $this->getSlot(); + } + if($this->windowid === 0){ + $count = $this->getShort(); + for($s = 0; $s < $count and !$this->feof(); ++$s){ + $this->hotbar[$s] = $this->getInt(); + } + } + } + + public function encode(){ + $this->reset(); + $this->putByte($this->windowid); + $this->putShort(count($this->slots)); + foreach($this->slots as $slot){ + $this->putSlot($slot); + } + if($this->windowid === 0 and count($this->hotbar) > 0){ + $this->putShort(count($this->hotbar)); + foreach($this->hotbar as $slot){ + $this->putInt($slot); + } + } + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/ContainerSetDataPacket.php b/src/pocketmine/network/protocol/ContainerSetDataPacket.php new file mode 100644 index 000000000..160453832 --- /dev/null +++ b/src/pocketmine/network/protocol/ContainerSetDataPacket.php @@ -0,0 +1,45 @@ +reset(); + $this->putByte($this->windowid); + $this->putShort($this->property); + $this->putShort($this->value); + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/ContainerSetSlotPacket.php b/src/pocketmine/network/protocol/ContainerSetSlotPacket.php new file mode 100644 index 000000000..c74ecb783 --- /dev/null +++ b/src/pocketmine/network/protocol/ContainerSetSlotPacket.php @@ -0,0 +1,47 @@ +windowid = $this->getByte(); + $this->slot = $this->getShort(); + $this->item = $this->getSlot(); + } + + public function encode(){ + $this->reset(); + $this->putByte($this->windowid); + $this->putShort($this->slot); + $this->putSlot($this->item); + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/DataPacket.php b/src/pocketmine/network/protocol/DataPacket.php new file mode 100644 index 000000000..798751ce8 --- /dev/null +++ b/src/pocketmine/network/protocol/DataPacket.php @@ -0,0 +1,183 @@ +setBuffer(chr($this->pid())); + } + + public function setBuffer($buffer = ""){ + $this->buffer = $buffer; + $this->offset = 0; + } + + public function getBuffer(){ + return $this->buffer; + } + + protected function get($len){ + if($len <= 0){ + $this->offset = strlen($this->buffer) - 1; + + return ""; + }elseif($len === true){ + return substr($this->buffer, $this->offset); + } + + $buffer = ""; + for(; $len > 0; --$len, ++$this->offset){ + $buffer .= @$this->buffer{$this->offset}; + } + + return $buffer; + } + + protected function put($str){ + $this->buffer .= $str; + } + + protected function getLong($unsigned = false){ + return Utils::readLong($this->get(8), $unsigned); + } + + protected function putLong($v){ + $this->buffer .= Utils::writeLong($v); + } + + protected function getInt(){ + return Utils::readInt($this->get(4)); + } + + protected function putInt($v){ + $this->buffer .= Utils::writeInt($v); + } + + protected function getShort($unsigned = false){ + return Utils::readShort($this->get(2), $unsigned); + } + + protected function putShort($v){ + $this->buffer .= Utils::writeShort($v); + } + + protected function getFloat(){ + return Utils::readFloat($this->get(4)); + } + + protected function putFloat($v){ + $this->buffer .= Utils::writeFloat($v); + } + + protected function getTriad(){ + return Utils::readTriad($this->get(3)); + } + + protected function putTriad($v){ + $this->buffer .= Utils::writeTriad($v); + } + + + protected function getLTriad(){ + return Utils::readTriad(strrev($this->get(3))); + } + + protected function putLTriad($v){ + $this->buffer .= strrev(Utils::writeTriad($v)); + } + + protected function getByte(){ + return ord($this->buffer{$this->offset++}); + } + + protected function putByte($v){ + $this->buffer .= chr($v); + } + + protected function getDataArray($len = 10){ + $data = array(); + for($i = 1; $i <= $len and !$this->feof(); ++$i){ + $data[] = $this->get($this->getTriad()); + } + + return $data; + } + + protected function putDataArray(array $data = array()){ + foreach($data as $v){ + $this->putTriad(strlen($v)); + $this->put($v); + } + } + + protected function getSlot(){ + $id = $this->getShort(); + $cnt = $this->getByte(); + + return Item::get( + $id, + $this->getShort(), + $cnt + ); + } + + protected function putSlot(Item $item){ + $this->putShort($item->getID()); + $this->putByte($item->getCount()); + $this->putShort($item->getMetadata()); + } + + protected function getString(){ + return $this->get($this->getShort(true)); + } + + protected function putString($v){ + $this->putShort(strlen($v)); + $this->put($v); + } + + protected function feof(){ + return !isset($this->buffer{$this->offset}); + } +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/DisconnectPacket.php b/src/pocketmine/network/protocol/DisconnectPacket.php new file mode 100644 index 000000000..2128a3b89 --- /dev/null +++ b/src/pocketmine/network/protocol/DisconnectPacket.php @@ -0,0 +1,38 @@ +reset(); + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/DropItemPacket.php b/src/pocketmine/network/protocol/DropItemPacket.php new file mode 100644 index 000000000..99de843b3 --- /dev/null +++ b/src/pocketmine/network/protocol/DropItemPacket.php @@ -0,0 +1,44 @@ +eid = $this->getInt(); + $this->unknown = $this->getByte(); + $this->item = $this->getSlot(); + } + + public function encode(){ + + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/EntityDataPacket.php b/src/pocketmine/network/protocol/EntityDataPacket.php new file mode 100644 index 000000000..3a8ba854f --- /dev/null +++ b/src/pocketmine/network/protocol/EntityDataPacket.php @@ -0,0 +1,50 @@ +x = $this->getShort(); + $this->y = $this->getByte(); + $this->z = $this->getShort(); + $this->namedtag = $this->get(true); + } + + public function encode(){ + $this->reset(); + $this->putShort($this->x); + $this->putByte($this->y); + $this->putShort($this->z); + $this->put($this->namedtag); + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/EntityEventPacket.php b/src/pocketmine/network/protocol/EntityEventPacket.php new file mode 100644 index 000000000..2e27bcd91 --- /dev/null +++ b/src/pocketmine/network/protocol/EntityEventPacket.php @@ -0,0 +1,44 @@ +eid = $this->getInt(); + $this->event = $this->getByte(); + } + + public function encode(){ + $this->reset(); + $this->putInt($this->eid); + $this->putByte($this->event); + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/ExplodePacket.php b/src/pocketmine/network/protocol/ExplodePacket.php new file mode 100644 index 000000000..7363ff4d2 --- /dev/null +++ b/src/pocketmine/network/protocol/ExplodePacket.php @@ -0,0 +1,56 @@ +reset(); + $this->putFloat($this->x); + $this->putFloat($this->y); + $this->putFloat($this->z); + $this->putFloat($this->radius); + $this->putInt(@count($this->records)); + if(@count($this->records) > 0){ + foreach($this->records as $record){ + $this->putByte($record->x); + $this->putByte($record->y); + $this->putByte($record->z); + } + } + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/HurtArmorPacket.php b/src/pocketmine/network/protocol/HurtArmorPacket.php new file mode 100644 index 000000000..503e2a174 --- /dev/null +++ b/src/pocketmine/network/protocol/HurtArmorPacket.php @@ -0,0 +1,41 @@ +reset(); + $this->putByte($this->health); + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/Info.php b/src/pocketmine/network/protocol/Info.php new file mode 100644 index 000000000..c302fa334 --- /dev/null +++ b/src/pocketmine/network/protocol/Info.php @@ -0,0 +1,106 @@ +action = $this->getByte(); + $this->eid = $this->getInt(); + $this->target = $this->getInt(); + } + + public function encode(){ + $this->reset(); + $this->putByte($this->action); + $this->putInt($this->eid); + $this->putInt($this->target); + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/LevelEventPacket.php b/src/pocketmine/network/protocol/LevelEventPacket.php new file mode 100644 index 000000000..f2ff40118 --- /dev/null +++ b/src/pocketmine/network/protocol/LevelEventPacket.php @@ -0,0 +1,49 @@ +reset(); + $this->putShort($this->evid); + $this->putShort($this->x); + $this->putShort($this->y); + $this->putShort($this->z); + $this->putInt($this->data); + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/LoginPacket.php b/src/pocketmine/network/protocol/LoginPacket.php new file mode 100644 index 000000000..d02c47cff --- /dev/null +++ b/src/pocketmine/network/protocol/LoginPacket.php @@ -0,0 +1,48 @@ +username = $this->getString(); + $this->protocol1 = $this->getInt(); + $this->protocol2 = $this->getInt(); + $this->clientId = $this->getInt(); + $this->loginData = $this->getString(); + } + + public function encode(){ + + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/LoginStatusPacket.php b/src/pocketmine/network/protocol/LoginStatusPacket.php new file mode 100644 index 000000000..1de99be2e --- /dev/null +++ b/src/pocketmine/network/protocol/LoginStatusPacket.php @@ -0,0 +1,41 @@ +reset(); + $this->putInt($this->status); + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/MessagePacket.php b/src/pocketmine/network/protocol/MessagePacket.php new file mode 100644 index 000000000..a4a36e810 --- /dev/null +++ b/src/pocketmine/network/protocol/MessagePacket.php @@ -0,0 +1,44 @@ +source = $this->getString(); + $this->message = $this->getString(); + } + + public function encode(){ + $this->reset(); + $this->putString($this->source); + $this->putString($this->message); + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/MoveEntityPacket.php b/src/pocketmine/network/protocol/MoveEntityPacket.php new file mode 100644 index 000000000..f4246b4c8 --- /dev/null +++ b/src/pocketmine/network/protocol/MoveEntityPacket.php @@ -0,0 +1,39 @@ +reset(); + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/MoveEntityPacket_PosRot.php b/src/pocketmine/network/protocol/MoveEntityPacket_PosRot.php new file mode 100644 index 000000000..17f3a4f20 --- /dev/null +++ b/src/pocketmine/network/protocol/MoveEntityPacket_PosRot.php @@ -0,0 +1,51 @@ +reset(); + $this->putInt($this->eid); + $this->putFloat($this->x); + $this->putFloat($this->y); + $this->putFloat($this->z); + $this->putFloat($this->yaw); + $this->putFloat($this->pitch); + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/MovePlayerPacket.php b/src/pocketmine/network/protocol/MovePlayerPacket.php new file mode 100644 index 000000000..e5a318553 --- /dev/null +++ b/src/pocketmine/network/protocol/MovePlayerPacket.php @@ -0,0 +1,59 @@ +eid = $this->getInt(); + $this->x = $this->getFloat(); + $this->y = $this->getFloat(); + $this->z = $this->getFloat(); + $this->yaw = $this->getFloat(); + $this->pitch = $this->getFloat(); + $this->bodyYaw = $this->getFloat(); + } + + public function encode(){ + $this->reset(); + $this->putInt($this->eid); + $this->putFloat($this->x); + $this->putFloat($this->y); + $this->putFloat($this->z); + $this->putFloat($this->yaw); + $this->putFloat($this->pitch); + $this->putFloat($this->bodyYaw); + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/PingPacket.php b/src/pocketmine/network/protocol/PingPacket.php new file mode 100644 index 000000000..6312217b2 --- /dev/null +++ b/src/pocketmine/network/protocol/PingPacket.php @@ -0,0 +1,41 @@ +time = $this->getLong(); + } + + public function encode(){ + $this->reset(); + $this->putLong($this->time); + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/PlayerActionPacket.php b/src/pocketmine/network/protocol/PlayerActionPacket.php new file mode 100644 index 000000000..4c322a1bf --- /dev/null +++ b/src/pocketmine/network/protocol/PlayerActionPacket.php @@ -0,0 +1,50 @@ +action = $this->getInt(); + $this->x = $this->getInt(); + $this->y = $this->getInt(); + $this->z = $this->getInt(); + $this->face = $this->getInt(); + $this->eid = $this->getInt(); + } + + public function encode(){ + + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/PlayerArmorEquipmentPacket.php b/src/pocketmine/network/protocol/PlayerArmorEquipmentPacket.php new file mode 100644 index 000000000..d31eeb9c4 --- /dev/null +++ b/src/pocketmine/network/protocol/PlayerArmorEquipmentPacket.php @@ -0,0 +1,50 @@ +eid = $this->getInt(); + $this->slots[0] = $this->getByte(); + $this->slots[1] = $this->getByte(); + $this->slots[2] = $this->getByte(); + $this->slots[3] = $this->getByte(); + } + + public function encode(){ + $this->reset(); + $this->putInt($this->eid); + $this->putByte($this->slots[0]); + $this->putByte($this->slots[1]); + $this->putByte($this->slots[2]); + $this->putByte($this->slots[3]); + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/PlayerEquipmentPacket.php b/src/pocketmine/network/protocol/PlayerEquipmentPacket.php new file mode 100644 index 000000000..52cbf64ce --- /dev/null +++ b/src/pocketmine/network/protocol/PlayerEquipmentPacket.php @@ -0,0 +1,50 @@ +eid = $this->getInt(); + $this->item = $this->getShort(); + $this->meta = $this->getShort(); + $this->slot = $this->getByte(); + } + + public function encode(){ + $this->reset(); + $this->putInt($this->eid); + $this->putShort($this->item); + $this->putShort($this->meta); + $this->putByte($this->slot); + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/PongPacket.php b/src/pocketmine/network/protocol/PongPacket.php new file mode 100644 index 000000000..3eee77d12 --- /dev/null +++ b/src/pocketmine/network/protocol/PongPacket.php @@ -0,0 +1,44 @@ +ptime = $this->getLong(); + $this->time = $this->getLong(); + } + + public function encode(){ + $this->reset(); + $this->putLong($this->ptime); + $this->putLong($this->time); + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/ReadyPacket.php b/src/pocketmine/network/protocol/ReadyPacket.php new file mode 100644 index 000000000..c28ae60e9 --- /dev/null +++ b/src/pocketmine/network/protocol/ReadyPacket.php @@ -0,0 +1,40 @@ +status = $this->getByte(); + } + + public function encode(){ + + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/RemoveBlockPacket.php b/src/pocketmine/network/protocol/RemoveBlockPacket.php new file mode 100644 index 000000000..75cdd6eb3 --- /dev/null +++ b/src/pocketmine/network/protocol/RemoveBlockPacket.php @@ -0,0 +1,46 @@ +eid = $this->getInt(); + $this->x = $this->getInt(); + $this->z = $this->getInt(); + $this->y = $this->getByte(); + } + + public function encode(){ + + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/RemoveEntityPacket.php b/src/pocketmine/network/protocol/RemoveEntityPacket.php new file mode 100644 index 000000000..d5306fae3 --- /dev/null +++ b/src/pocketmine/network/protocol/RemoveEntityPacket.php @@ -0,0 +1,41 @@ +reset(); + $this->putInt($this->eid); + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/RemovePlayerPacket.php b/src/pocketmine/network/protocol/RemovePlayerPacket.php new file mode 100644 index 000000000..bc35b25e6 --- /dev/null +++ b/src/pocketmine/network/protocol/RemovePlayerPacket.php @@ -0,0 +1,43 @@ +reset(); + $this->putInt($this->eid); + $this->putLong($this->clientID); + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/RequestChunkPacket.php b/src/pocketmine/network/protocol/RequestChunkPacket.php new file mode 100644 index 000000000..bdba239d9 --- /dev/null +++ b/src/pocketmine/network/protocol/RequestChunkPacket.php @@ -0,0 +1,42 @@ +chunkX = $this->getInt(); + $this->chunkZ = $this->getInt(); + } + + public function encode(){ + + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/RespawnPacket.php b/src/pocketmine/network/protocol/RespawnPacket.php new file mode 100644 index 000000000..076c9b4dd --- /dev/null +++ b/src/pocketmine/network/protocol/RespawnPacket.php @@ -0,0 +1,50 @@ +eid = $this->getInt(); + $this->x = $this->getFloat(); + $this->y = $this->getFloat(); + $this->z = $this->getFloat(); + } + + public function encode(){ + $this->reset(); + $this->putInt($this->eid); + $this->putFloat($this->x); + $this->putFloat($this->y); + $this->putFloat($this->z); + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/RotateHeadPacket.php b/src/pocketmine/network/protocol/RotateHeadPacket.php new file mode 100644 index 000000000..81458b9d2 --- /dev/null +++ b/src/pocketmine/network/protocol/RotateHeadPacket.php @@ -0,0 +1,43 @@ +reset(); + $this->putInt($this->eid); + $this->putByte($this->yaw); + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/SendInventoryPacket.php b/src/pocketmine/network/protocol/SendInventoryPacket.php new file mode 100644 index 000000000..f3348e07e --- /dev/null +++ b/src/pocketmine/network/protocol/SendInventoryPacket.php @@ -0,0 +1,64 @@ +eid = $this->getInt(); + $this->windowid = $this->getByte(); + $count = $this->getShort(); + for($s = 0; $s < $count and !$this->feof(); ++$s){ + $this->slots[$s] = $this->getSlot(); + } + if($this->windowid === 1){ //Armor is sent + for($s = 0; $s < 4; ++$s){ + $this->armor[$s] = $this->getSlot(); + } + } + } + + public function encode(){ + $this->reset(); + $this->putInt($this->eid); + $this->putByte($this->windowid); + $this->putShort(count($this->slots)); + foreach($this->slots as $slot){ + $this->putSlot($slot); + } + if($this->windowid === 1 and count($this->armor) === 4){ + for($s = 0; $s < 4; ++$s){ + $this->putSlot($this->armor[$s]); + } + } + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/ServerHandshakePacket.php b/src/pocketmine/network/protocol/ServerHandshakePacket.php new file mode 100644 index 000000000..97fc199c3 --- /dev/null +++ b/src/pocketmine/network/protocol/ServerHandshakePacket.php @@ -0,0 +1,60 @@ +reset(); + $this->put("\x04\x3f\x57\xfe"); //cookie + $this->put("\xcd"); //Security flags + $this->putShort($this->port); + $this->putDataArray(array( + "\xf5\xff\xff\xf5", + "\xff\xff\xff\xff", + "\xff\xff\xff\xff", + "\xff\xff\xff\xff", + "\xff\xff\xff\xff", + "\xff\xff\xff\xff", + "\xff\xff\xff\xff", + "\xff\xff\xff\xff", + "\xff\xff\xff\xff", + "\xff\xff\xff\xff", + )); + $this->put("\x00\x00"); + $this->putLong($this->session); + $this->putLong($this->session2); + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/SetEntityDataPacket.php b/src/pocketmine/network/protocol/SetEntityDataPacket.php new file mode 100644 index 000000000..21bcc38c5 --- /dev/null +++ b/src/pocketmine/network/protocol/SetEntityDataPacket.php @@ -0,0 +1,44 @@ +reset(); + $this->putInt($this->eid); + $this->put(Utils::writeMetadata($this->metadata)); + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/SetEntityMotionPacket.php b/src/pocketmine/network/protocol/SetEntityMotionPacket.php new file mode 100644 index 000000000..7661557e7 --- /dev/null +++ b/src/pocketmine/network/protocol/SetEntityMotionPacket.php @@ -0,0 +1,47 @@ +reset(); + $this->putInt($this->eid); + $this->putShort((int) ($this->speedX * 400)); + $this->putShort((int) ($this->speedY * 400)); + $this->putShort((int) ($this->speedZ * 400)); + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/SetHealthPacket.php b/src/pocketmine/network/protocol/SetHealthPacket.php new file mode 100644 index 000000000..036402faa --- /dev/null +++ b/src/pocketmine/network/protocol/SetHealthPacket.php @@ -0,0 +1,41 @@ +health = $this->getByte(); + } + + public function encode(){ + $this->reset(); + $this->putByte($this->health); + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/SetSpawnPositionPacket.php b/src/pocketmine/network/protocol/SetSpawnPositionPacket.php new file mode 100644 index 000000000..0862adcad --- /dev/null +++ b/src/pocketmine/network/protocol/SetSpawnPositionPacket.php @@ -0,0 +1,45 @@ +reset(); + $this->putInt($this->x); + $this->putInt($this->z); + $this->putByte($this->y); + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/SetTimePacket.php b/src/pocketmine/network/protocol/SetTimePacket.php new file mode 100644 index 000000000..2f013bd34 --- /dev/null +++ b/src/pocketmine/network/protocol/SetTimePacket.php @@ -0,0 +1,43 @@ +reset(); + $this->putInt($this->time); + $this->putByte($this->started == true ? 0x80 : 0x00); + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/StartGamePacket.php b/src/pocketmine/network/protocol/StartGamePacket.php new file mode 100644 index 000000000..4a7e097d1 --- /dev/null +++ b/src/pocketmine/network/protocol/StartGamePacket.php @@ -0,0 +1,53 @@ +reset(); + $this->putInt($this->seed); + $this->putInt($this->generator); + $this->putInt($this->gamemode); + $this->putInt($this->eid); + $this->putFloat($this->x); + $this->putFloat($this->y); + $this->putFloat($this->z); + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/TakeItemEntityPacket.php b/src/pocketmine/network/protocol/TakeItemEntityPacket.php new file mode 100644 index 000000000..918382c18 --- /dev/null +++ b/src/pocketmine/network/protocol/TakeItemEntityPacket.php @@ -0,0 +1,43 @@ +reset(); + $this->putInt($this->target); + $this->putInt($this->eid); + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/TileEventPacket.php b/src/pocketmine/network/protocol/TileEventPacket.php new file mode 100644 index 000000000..28c01b7d6 --- /dev/null +++ b/src/pocketmine/network/protocol/TileEventPacket.php @@ -0,0 +1,49 @@ +reset(); + $this->putInt($this->x); + $this->putInt($this->y); + $this->putInt($this->z); + $this->putInt($this->case1); + $this->putInt($this->case2); + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/UnknownPacket.php b/src/pocketmine/network/protocol/UnknownPacket.php new file mode 100644 index 000000000..f873d1919 --- /dev/null +++ b/src/pocketmine/network/protocol/UnknownPacket.php @@ -0,0 +1,41 @@ +packetID; + } + + public function decode(){ + + } + + public function encode(){ + + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/UpdateBlockPacket.php b/src/pocketmine/network/protocol/UpdateBlockPacket.php new file mode 100644 index 000000000..b24b86b30 --- /dev/null +++ b/src/pocketmine/network/protocol/UpdateBlockPacket.php @@ -0,0 +1,49 @@ +reset(); + $this->putInt($this->x); + $this->putInt($this->z); + $this->putByte($this->y); + $this->putByte($this->block); + $this->putByte($this->meta); + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/protocol/UseItemPacket.php b/src/pocketmine/network/protocol/UseItemPacket.php new file mode 100644 index 000000000..9697b0e2e --- /dev/null +++ b/src/pocketmine/network/protocol/UseItemPacket.php @@ -0,0 +1,64 @@ +x = $this->getInt(); + $this->y = $this->getInt(); + $this->z = $this->getInt(); + $this->face = $this->getInt(); + $this->item = $this->getShort(); + $this->meta = $this->getByte(); //Mojang: fix this + $this->eid = $this->getInt(); + $this->fx = $this->getFloat(); + $this->fy = $this->getFloat(); + $this->fz = $this->getFloat(); + $this->posX = $this->getFloat(); + $this->posY = $this->getFloat(); + $this->posZ = $this->getFloat(); + } + + public function encode(){ + + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/query/QueryHandler.php b/src/pocketmine/network/query/QueryHandler.php new file mode 100644 index 000000000..ae218568f --- /dev/null +++ b/src/pocketmine/network/query/QueryHandler.php @@ -0,0 +1,143 @@ +server = Server::getInstance(); + $addr = ($ip = $this->server->getIp()) != "" ? $ip : "0.0.0.0"; + $port = $this->server->getPort(); + console("[INFO] Setting query port to $port"); + /* + The Query protocol is built on top of the existing Minecraft PE UDP network stack. + Because the 0xFE packet does not exist in the MCPE protocol, + we can identify Query packets and remove them from the packet queue. + + Then, the Query class handles itself sending the packets in raw form, because + packets can conflict with the MCPE ones. + */ + + $this->regenerateToken(); + $this->lastToken = $this->token; + $this->regenerateInfo(); + console("[INFO] Query running on $addr:$port"); + } + + public function regenerateInfo(){ + $str = ""; + $plist = "PocketMine-MP " . $this->server->getPocketMineVersion(); + $pl = $this->server->getPluginManager()->getPlugins(); + if(count($pl) > 0){ + $plist .= ":"; + foreach($pl as $p){ + $d = $p->getDescription(); + $plist .= " " . str_replace(array(";", ":", " "), array("", "", "_"), $d->getName()) . " " . str_replace(array(";", ":", " "), array("", "", "_"), $d->getVersion()) . ";"; + } + $plist = substr($plist, 0, -1); + } + $KVdata = array( + "splitnum" => chr(128), + "hostname" => $this->server->getServerName(), + "gametype" => ($this->server->getGamemode() & 0x01) === 0 ? "SMP" : "CMP", + "game_id" => "MINECRAFTPE", + "version" => $this->server->getVersion(), + "server_engine" => $this->server->getName() . " " . $this->server->getPocketMineVersion(), + "plugins" => $plist, + "map" => Level::getDefault()->getName(), + "numplayers" => count(Player::$list), + "maxplayers" => $this->server->getMaxPlayers(), + "whitelist" => $this->server->hasWhitelist() === true ? "on" : "off", + "hostport" => $this->server->getPort() + ); + foreach($KVdata as $key => $value){ + $str .= $key . "\x00" . $value . "\x00"; + } + $str .= "\x00\x01player_\x00\x00"; + foreach(Player::$list as $player){ + if($player->getName() != ""){ + $str .= $player->getName() . "\x00"; + } + } + $str .= "\x00"; + $this->longData = $str; + $this->timeout = microtime(true) + 5; + } + + public function regenerateToken(){ + $this->lastToken = $this->token; + $this->token = Utils::getRandomBytes(16, false); + } + + public static function getTokenString($token, $salt){ + return Utils::readInt(substr(hash("sha512", $salt . ":" . $token, true), 7, 4)); + } + + public function handle(QueryPacket $packet){ + $packet->decode(); + switch($packet->packetType){ + case QueryPacket::HANDSHAKE: //Handshake + $pk = new QueryPacket; + $pk->ip = $packet->ip; + $pk->port = $packet->port; + $pk->packetType = QueryPacket::HANDSHAKE; + $pk->sessionID = $packet->sessionID; + $pk->payload = self::getTokenString($this->token, $packet->ip) . "\x00"; + $pk->encode(); + $this->server->sendPacket($pk); + break; + case QueryPacket::STATISTICS: //Stat + $token = Utils::readInt(substr($packet->payload, 0, 4)); + if($token !== self::getTokenString($this->token, $packet->ip) and $token !== self::getTokenString($this->lastToken, $packet->ip)){ + break; + } + $pk = new QueryPacket; + $pk->ip = $packet->ip; + $pk->port = $packet->port; + $pk->packetType = QueryPacket::STATISTICS; + $pk->sessionID = $packet->sessionID; + if(strlen($packet->payload) === 8){ + if($this->timeout < microtime(true)){ + $this->regenerateInfo(); + } + $pk->payload = $this->longData; + }else{ + $pk->payload = $this->server->getServerName() . "\x00" . (($this->server->getGamemode() & 0x01) === 0 ? "SMP" : "CMP") . "\x00" . Level::getDefault()->getName() . "\x00" . count(Player::$list) . "\x00" . $this->server->getMaxPlayers() . "\x00" . Utils::writeLShort($this->server->getPort()) . $this->server->getIp() . "\x00"; + } + $pk->encode(); + $this->server->sendPacket($pk); + break; + } + } + +} diff --git a/src/pocketmine/network/query/QueryPacket.php b/src/pocketmine/network/query/QueryPacket.php new file mode 100644 index 000000000..adf42a85b --- /dev/null +++ b/src/pocketmine/network/query/QueryPacket.php @@ -0,0 +1,46 @@ +packetType = ord($this->buffer{2}); + $this->sessionID = Utils::readInt(substr($this->buffer, 3, 4)); + $this->payload = substr($this->buffer, 7); + } + + public function encode(){ + $this->buffer .= chr($this->packetType); + $this->buffer .= Utils::writeInt($this->sessionID); + $this->buffer .= $this->payload; + } +} \ No newline at end of file diff --git a/src/pocketmine/network/raknet/Info.php b/src/pocketmine/network/raknet/Info.php new file mode 100644 index 000000000..743ccd5a4 --- /dev/null +++ b/src/pocketmine/network/raknet/Info.php @@ -0,0 +1,99 @@ +packetID = (int) $packetID; + } + + public function pid(){ + return $this->packetID; + } + + protected function get($len){ + if($len <= 0){ + $this->offset = strlen($this->buffer) - 1; + + return ""; + }elseif($len === true){ + return substr($this->buffer, $this->offset); + } + + $buffer = ""; + for(; $len > 0; --$len, ++$this->offset){ + $buffer .= @$this->buffer{$this->offset}; + } + + return $buffer; + } + + private function getLong($unsigned = false){ + return Utils::readLong($this->get(8), $unsigned); + } + + private function getInt(){ + return Utils::readInt($this->get(4)); + } + + private function getShort($unsigned = false){ + return Utils::readShort($this->get(2), $unsigned); + } + + private function getLTriad(){ + return Utils::readTriad(strrev($this->get(3))); + } + + private function getByte(){ + return ord($this->buffer{$this->offset++}); + } + + private function feof(){ + return !isset($this->buffer{$this->offset}); + } + + public function decode(){ + $this->offset = 1; + switch($this->packetID){ + case Info::UNCONNECTED_PING: + case Info::UNCONNECTED_PING_OPEN_CONNECTIONS: + $this->pingID = $this->getLong(); + $this->offset += 16; //Magic + break; + case Info::OPEN_CONNECTION_REQUEST_1: + $this->offset += 16; //Magic + $this->structure = $this->getByte(); + $this->mtuSize = strlen($this->get(true)); + break; + case Info::OPEN_CONNECTION_REQUEST_2: + $this->offset += 16; //Magic + $this->security = $this->get(5); + $this->clientPort = $this->getShort(false); + $this->mtuSize = $this->getShort(false); + $this->clientID = $this->getLong(); + break; + case Info::DATA_PACKET_0: + case Info::DATA_PACKET_1: + case Info::DATA_PACKET_2: + case Info::DATA_PACKET_3: + case Info::DATA_PACKET_4: + case Info::DATA_PACKET_5: + case Info::DATA_PACKET_6: + case Info::DATA_PACKET_7: + case Info::DATA_PACKET_8: + case Info::DATA_PACKET_9: + case Info::DATA_PACKET_A: + case Info::DATA_PACKET_B: + case Info::DATA_PACKET_C: + case Info::DATA_PACKET_D: + case Info::DATA_PACKET_E: + case Info::DATA_PACKET_F: + $this->seqNumber = $this->getLTriad(); + $this->data = array(); + while(!$this->feof() and $this->parseDataPacket() !== false){ + + } + break; + case Info::NACK: + case Info::ACK: + $count = $this->getShort(); + $this->packets = array(); + for($i = 0; $i < $count and !$this->feof(); ++$i){ + if($this->getByte() === 0){ + $start = $this->getLTriad(); + $end = $this->getLTriad(); + if(($end - $start) > 4096){ + $end = $start + 4096; + } + for($c = $start; $c <= $end; ++$c){ + $this->packets[] = $c; + } + }else{ + $this->packets[] = $this->getLTriad(); + } + } + break; + default: + break; + } + } + + private function parseDataPacket(){ + $packetFlags = $this->getByte(); + $reliability = ($packetFlags & 0b11100000) >> 5; + $hasSplit = ($packetFlags & 0b00010000) > 0; + $length = (int) ceil($this->getShort() / 8); + if($reliability === 2 + or $reliability === 3 + or $reliability === 4 + or $reliability === 6 + or $reliability === 7 + ){ + $messageIndex = $this->getLTriad(); + }else{ + $messageIndex = false; + } + + if($reliability === 1 + or $reliability === 3 + or $reliability === 4 + or $reliability === 7 + ){ + $orderIndex = $this->getLTriad(); + $orderChannel = $this->getByte(); + }else{ + $orderIndex = false; + $orderChannel = false; + } + + if($hasSplit == true){ + $splitCount = $this->getInt(); + $splitID = $this->getShort(); + $splitIndex = $this->getInt(); + }else{ + $splitCount = false; + $splitID = false; + $splitIndex = false; + } + + if($length <= 0 + or $orderChannel >= 32 + or ($hasSplit === true and $splitIndex >= $splitCount) + ){ + return false; + }else{ + $pid = $this->getByte(); + $buffer = $this->get($length - 1); + if(strlen($buffer) < ($length - 1)){ + return false; + } + switch($pid){ + case ProtocolInfo::PING_PACKET: + $data = new PingPacket(); + break; + case ProtocolInfo::PONG_PACKET: + $data = new PongPacket(); + break; + case ProtocolInfo::CLIENT_CONNECT_PACKET: + $data = new ClientConnectPacket(); + break; + case ProtocolInfo::SERVER_HANDSHAKE_PACKET: + $data = new ServerHandshakePacket(); + break; + case ProtocolInfo::DISCONNECT_PACKET: + $data = new DisconnectPacket(); + break; + case ProtocolInfo::LOGIN_PACKET: + $data = new LoginPacket(); + break; + case ProtocolInfo::LOGIN_STATUS_PACKET: + $data = new LoginStatusPacket(); + break; + case ProtocolInfo::READY_PACKET: + $data = new ReadyPacket(); + break; + case ProtocolInfo::MESSAGE_PACKET: + $data = new MessagePacket(); + break; + case ProtocolInfo::SET_TIME_PACKET: + $data = new SetTimePacket(); + break; + case ProtocolInfo::START_GAME_PACKET: + $data = new StartGamePacket(); + break; + case ProtocolInfo::ADD_MOB_PACKET: + $data = new AddMobPacket(); + break; + case ProtocolInfo::ADD_PLAYER_PACKET: + $data = new AddPlayerPacket(); + break; + case ProtocolInfo::REMOVE_PLAYER_PACKET: + $data = new RemovePlayerPacket(); + break; + case ProtocolInfo::ADD_ENTITY_PACKET: + $data = new AddEntityPacket(); + break; + case ProtocolInfo::REMOVE_ENTITY_PACKET: + $data = new RemoveEntityPacket(); + break; + case ProtocolInfo::ADD_ITEM_ENTITY_PACKET: + $data = new AddItemEntityPacket(); + break; + case ProtocolInfo::TAKE_ITEM_ENTITY_PACKET: + $data = new TakeItemEntityPacket(); + break; + case ProtocolInfo::MOVE_ENTITY_PACKET: + $data = new MoveEntityPacket(); + break; + case ProtocolInfo::MOVE_ENTITY_PACKET_POSROT: + $data = new MoveEntityPacket_PosRot(); + break; + case ProtocolInfo::ROTATE_HEAD_PACKET: + $data = new RotateHeadPacket(); + break; + case ProtocolInfo::MOVE_PLAYER_PACKET: + $data = new MovePlayerPacket(); + break; + case ProtocolInfo::REMOVE_BLOCK_PACKET: + $data = new RemoveBlockPacket(); + break; + case ProtocolInfo::UPDATE_BLOCK_PACKET: + $data = new UpdateBlockPacket(); + break; + case ProtocolInfo::ADD_PAINTING_PACKET: + $data = new AddPaintingPacket(); + break; + case ProtocolInfo::EXPLODE_PACKET: + $data = new ExplodePacket(); + break; + case ProtocolInfo::LEVEL_EVENT_PACKET: + $data = new LevelEventPacket(); + break; + case ProtocolInfo::TILE_EVENT_PACKET: + $data = new TileEventPacket(); + break; + case ProtocolInfo::ENTITY_EVENT_PACKET: + $data = new EntityEventPacket(); + break; + case ProtocolInfo::REQUEST_CHUNK_PACKET: + $data = new RequestChunkPacket(); + break; + case ProtocolInfo::CHUNK_DATA_PACKET: + $data = new ChunkDataPacket(); + break; + case ProtocolInfo::PLAYER_EQUIPMENT_PACKET: + $data = new PlayerEquipmentPacket(); + break; + case ProtocolInfo::PLAYER_ARMOR_EQUIPMENT_PACKET: + $data = new PlayerArmorEquipmentPacket(); + break; + case ProtocolInfo::INTERACT_PACKET: + $data = new InteractPacket(); + break; + case ProtocolInfo::USE_ITEM_PACKET: + $data = new UseItemPacket(); + break; + case ProtocolInfo::PLAYER_ACTION_PACKET: + $data = new PlayerActionPacket(); + break; + case ProtocolInfo::HURT_ARMOR_PACKET: + $data = new HurtArmorPacket(); + break; + case ProtocolInfo::SET_ENTITY_DATA_PACKET: + $data = new SetEntityDataPacket(); + break; + case ProtocolInfo::SET_ENTITY_MOTION_PACKET: + $data = new SetEntityMotionPacket(); + break; + case ProtocolInfo::SET_HEALTH_PACKET: + $data = new SetHealthPacket(); + break; + case ProtocolInfo::SET_SPAWN_POSITION_PACKET: + $data = new SetSpawnPositionPacket(); + break; + case ProtocolInfo::ANIMATE_PACKET: + $data = new AnimatePacket(); + break; + case ProtocolInfo::RESPAWN_PACKET: + $data = new RespawnPacket(); + break; + case ProtocolInfo::SEND_INVENTORY_PACKET: + $data = new SendInventoryPacket(); + break; + case ProtocolInfo::DROP_ITEM_PACKET: + $data = new DropItemPacket(); + break; + case ProtocolInfo::CONTAINER_OPEN_PACKET: + $data = new ContainerOpenPacket(); + break; + case ProtocolInfo::CONTAINER_CLOSE_PACKET: + $data = new ContainerClosePacket(); + break; + case ProtocolInfo::CONTAINER_SET_SLOT_PACKET: + $data = new ContainerSetSlotPacket(); + break; + case ProtocolInfo::CONTAINER_SET_DATA_PACKET: + $data = new ContainerSetDataPacket(); + break; + case ProtocolInfo::CONTAINER_SET_CONTENT_PACKET: + $data = new ContainerSetContentPacket(); + break; + case ProtocolInfo::CHAT_PACKET: + $data = new ChatPacket(); + break; + case ProtocolInfo::ADVENTURE_SETTINGS_PACKET: + $data = new AdventureSettingsPacket(); + break; + case ProtocolInfo::ENTITY_DATA_PACKET: + $data = new EntityDataPacket(); + break; + default: + $data = new UnknownPacket(); + $data->packetID = $pid; + break; + } + $data->reliability = $reliability; + $data->hasSplit = $hasSplit; + $data->messageIndex = $messageIndex; + $data->orderIndex = $orderIndex; + $data->orderChannel = $orderChannel; + $data->splitCount = $splitCount; + $data->splitID = $splitID; + $data->splitIndex = $splitIndex; + $data->setBuffer($buffer); + $this->data[] = $data; + } + + return true; + } + + public function encode(){ + if(strlen($this->buffer) > 0){ + return; + } + $this->buffer = chr($this->packetID); + + switch($this->packetID){ + case Info::OPEN_CONNECTION_REPLY_1: + $this->buffer .= Info::MAGIC; + $this->putLong($this->serverID); + $this->putByte(0); //server security + $this->putShort($this->mtuSize); + break; + case Info::OPEN_CONNECTION_REPLY_2: + $this->buffer .= Info::MAGIC; + $this->putLong($this->serverID); + $this->putShort($this->serverPort); + $this->putShort($this->mtuSize); + $this->putByte(0); //Server security + break; + case Info::INCOMPATIBLE_PROTOCOL_VERSION: + $this->putByte(Info::STRUCTURE); + $this->buffer .= Info::MAGIC; + $this->putLong($this->serverID); + break; + case Info::UNCONNECTED_PONG: + case Info::ADVERTISE_SYSTEM: + $this->putLong($this->pingID); + $this->putLong($this->serverID); + $this->buffer .= Info::MAGIC; + $this->putString($this->serverType); + break; + case Info::DATA_PACKET_0: + case Info::DATA_PACKET_1: + case Info::DATA_PACKET_2: + case Info::DATA_PACKET_3: + case Info::DATA_PACKET_4: + case Info::DATA_PACKET_5: + case Info::DATA_PACKET_6: + case Info::DATA_PACKET_7: + case Info::DATA_PACKET_8: + case Info::DATA_PACKET_9: + case Info::DATA_PACKET_A: + case Info::DATA_PACKET_B: + case Info::DATA_PACKET_C: + case Info::DATA_PACKET_D: + case Info::DATA_PACKET_E: + case Info::DATA_PACKET_F: + $this->putLTriad($this->seqNumber); + foreach($this->data as $pk){ + $this->encodeDataPacket($pk); + } + break; + case Info::NACK: + case Info::ACK: + $payload = ""; + $records = 0; + $pointer = 0; + sort($this->packets, SORT_NUMERIC); + $max = count($this->packets); + + while($pointer < $max){ + $type = true; + $curr = $start = $this->packets[$pointer]; + for($i = $start + 1; $i < $max; ++$i){ + $n = $this->packets[$i]; + if(($n - $curr) === 1){ + $curr = $end = $n; + $type = false; + $pointer = $i + 1; + }else{ + break; + } + } + ++$pointer; + if($type === false){ + $payload .= "\x00"; + $payload .= strrev(Utils::writeTriad($start)); + $payload .= strrev(Utils::writeTriad($end)); + }else{ + $payload .= Utils::writeBool(true); + $payload .= strrev(Utils::writeTriad($start)); + } + ++$records; + } + $this->putShort($records); + $this->buffer .= $payload; + break; + default: + + } + + } + + private function encodeDataPacket(DataPacket $pk){ + $this->putByte(($pk->reliability << 5) | ($pk->hasSplit > 0 ? 0b00010000 : 0)); + $this->putShort(strlen($pk->buffer) << 3); + if($pk->reliability === 2 + or $pk->reliability === 3 + or $pk->reliability === 4 + or $pk->reliability === 6 + or $pk->reliability === 7 + ){ + $this->putLTriad($pk->messageIndex); + } + + if($pk->reliability === 1 + or $pk->reliability === 3 + or $pk->reliability === 4 + or $pk->reliability === 7 + ){ + $this->putLTriad($pk->orderIndex); + $this->putByte($pk->orderChannel); + } + + if($pk->hasSplit === true){ + $this->putInt($pk->splitCount); + $this->putShort($pk->splitID); + $this->putInt($pk->splitIndex); + } + + $this->buffer .= $pk->buffer; + } + + protected function put($str){ + $this->buffer .= $str; + } + + protected function putLong($v){ + $this->buffer .= Utils::writeLong($v); + } + + protected function putInt($v){ + $this->buffer .= Utils::writeInt($v); + } + + protected function putShort($v){ + $this->buffer .= Utils::writeShort($v); + } + + protected function putTriad($v){ + $this->buffer .= Utils::writeTriad($v); + } + + protected function putLTriad($v){ + $this->buffer .= strrev(Utils::writeTriad($v)); + } + + protected function putByte($v){ + $this->buffer .= chr($v); + } + + protected function putString($v){ + $this->putShort(strlen($v)); + $this->put($v); + } + + public function __destruct(){ + } +} \ No newline at end of file diff --git a/src/pocketmine/network/rcon/RCON.php b/src/pocketmine/network/rcon/RCON.php new file mode 100644 index 000000000..fbc143e82 --- /dev/null +++ b/src/pocketmine/network/rcon/RCON.php @@ -0,0 +1,87 @@ +workers = array(); + $this->password = (string) $password; + console("[INFO] Starting remote control listener"); + if($this->password === ""){ + console("[ERROR] RCON can't be started: Empty password"); + + return; + } + $this->threads = (int) max(1, $threads); + $this->clientsPerThread = (int) max(1, $clientsPerThread); + $this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); + if($this->socket === false or !socket_bind($this->socket, $interface, (int) $port) or !socket_listen($this->socket)){ + console("[ERROR] RCON can't be started: " . socket_strerror(socket_last_error())); + + return; + } + @socket_set_block($this->socket); + for($n = 0; $n < $this->threads; ++$n){ + $this->workers[$n] = new RCONInstance($this->socket, $this->password, $this->clientsPerThread); + } + @socket_getsockname($this->socket, $addr, $port); + console("[INFO] RCON running on $addr:$port"); + Server::getInstance()->schedule(2, array($this, "check"), array(), true); + } + + public function stop(){ + for($n = 0; $n < $this->threads; ++$n){ + $this->workers[$n]->close(); + $this->workers[$n]->join(); + usleep(50000); + $this->workers[$n]->kill(); + } + @socket_close($this->socket); + $this->threads = 0; + } + + public function check(){ + for($n = 0; $n < $this->threads; ++$n){ + if($this->workers[$n]->isTerminated() === true){ + $this->workers[$n] = new RCONInstance($this->socket, $this->password, $this->clientsPerThread); + }elseif($this->workers[$n]->isWaiting()){ + if($this->workers[$n]->response !== ""){ + console($this->workers[$n]->response); + $this->workers[$n]->notify(); + }else{ + $this->workers[$n]->response = Server::getInstance()->api->console->run($this->workers[$n]->cmd, "rcon"); + $this->workers[$n]->notify(); + } + } + } + } + +} diff --git a/src/pocketmine/network/rcon/RCONInstance.php b/src/pocketmine/network/rcon/RCONInstance.php new file mode 100644 index 000000000..5be2c440d --- /dev/null +++ b/src/pocketmine/network/rcon/RCONInstance.php @@ -0,0 +1,173 @@ +stop = false; + $this->cmd = ""; + $this->response = ""; + $this->socket = $socket; + $this->password = $password; + $this->maxClients = (int) $maxClients; + for($n = 0; $n < $this->maxClients; ++$n){ + $this->{"client" . $n} = null; + $this->{"status" . $n} = 0; + $this->{"timeout" . $n} = 0; + } + $this->start(); + } + + private function writePacket($client, $requestID, $packetType, $payload){ + $pk = Utils::writeLInt((int) $requestID) + . Utils::writeLInt((int) $packetType) + . $payload + . "\x00\x00"; //Terminate payload and packet + return socket_write($client, Utils::writeLInt(strlen($pk)) . $pk); + } + + private function readPacket($client, &$size, &$requestID, &$packetType, &$payload){ + @socket_set_nonblock($client); + $d = socket_read($client, 4); + if($this->stop === true){ + return false; + }elseif($d === false){ + return null; + }elseif($d === "" or strlen($d) < 4){ + return false; + } + @socket_set_block($client); + $size = Utils::readLInt($d); + if($size < 0 or $size > 65535){ + return false; + } + $requestID = Utils::readLInt(socket_read($client, 4)); + $packetType = Utils::readLInt(socket_read($client, 4)); + $payload = rtrim(socket_read($client, $size + 2)); //Strip two null bytes + return true; + } + + public function close(){ + $this->stop = true; + } + + public function run(){ + while($this->stop !== true){ + usleep(2000); + $r = array($socket = $this->socket); + $w = null; + $e = null; + if(socket_select($r, $w, $e, 0) === 1){ + if(($client = socket_accept($this->socket)) !== false){ + socket_set_block($client); + socket_set_option($client, SOL_SOCKET, SO_KEEPALIVE, 1); + $done = false; + for($n = 0; $n < $this->maxClients; ++$n){ + if($this->{"client" . $n} === null){ + $this->{"client" . $n} = $client; + $this->{"status" . $n} = 0; + $this->{"timeout" . $n} = microtime(true) + 5; + $done = true; + break; + } + } + if($done === false){ + @socket_close($client); + } + } + } + + for($n = 0; $n < $this->maxClients; ++$n){ + $client = & $this->{"client" . $n}; + if($client !== null){ + if($this->{"status" . $n} !== -1 and $this->stop !== true){ + if($this->{"status" . $n} === 0 and $this->{"timeout" . $n} < microtime(true)){ //Timeout + $this->{"status" . $n} = -1; + continue; + } + $p = $this->readPacket($client, $size, $requestID, $packetType, $payload); + if($p === false){ + $this->{"status" . $n} = -1; + continue; + }elseif($p === null){ + continue; + } + + switch($packetType){ + case 3: //Login + if($this->{"status" . $n} !== 0){ + $this->{"status" . $n} = -1; + continue; + } + if($payload === $this->password){ + @socket_getpeername($client, $addr, $port); + $this->response = "[INFO] Successful Rcon connection from: /$addr:$port"; + $this->wait(); + $this->response = ""; + $this->writePacket($client, $requestID, 2, ""); + $this->{"status" . $n} = 1; + }else{ + $this->{"status" . $n} = -1; + $this->writePacket($client, -1, 2, ""); + continue; + } + break; + case 2: //Command + if($this->{"status" . $n} !== 1){ + $this->{"status" . $n} = -1; + continue; + } + if(strlen($payload) > 0){ + $this->cmd = ltrim($payload); + $this->wait(); + $this->writePacket($client, $requestID, 0, str_replace("\n", "\r\n", trim($this->response))); + $this->response = ""; + $this->cmd = ""; + } + break; + } + usleep(1); + }else{ + @socket_set_option($client, SOL_SOCKET, SO_LINGER, array("l_onoff" => 1, "l_linger" => 1)); + @socket_shutdown($client, 2); + @socket_set_block($client); + @socket_read($client, 1); + @socket_close($client); + $this->{"status" . $n} = 0; + $this->{"client" . $n} = null; + } + } + } + } + unset($this->socket, $this->cmd, $this->response, $this->stop); + exit(0); + } +} \ No newline at end of file diff --git a/src/pocketmine/network/upnp/UPnP.php b/src/pocketmine/network/upnp/UPnP.php new file mode 100644 index 000000000..e9b116dc2 --- /dev/null +++ b/src/pocketmine/network/upnp/UPnP.php @@ -0,0 +1,72 @@ +StaticPortMappingCollection)){ + return false; + } + $com->StaticPortMappingCollection->Add($port, "UDP", $port, $myLocalIP, true, "PocketMine-MP"); + }catch(\Exception $e){ + return false; + } + + return true; + } + + public static function RemovePortForward($port){ + if(Utils::$online === false){ + return false; + } + if(Utils::getOS() != "win" or !class_exists("COM")){ + return false; + } + $port = (int) $port; + try{ + $com = new \COM("HNetCfg.NATUPnP") or false; + if($com === false or !is_object($com->StaticPortMappingCollection)){ + return false; + } + $com->StaticPortMappingCollection->Remove($port, "UDP"); + }catch(\Exception $e){ + return false; + } + + return true; + } +} \ No newline at end of file diff --git a/src/pocketmine/permission/BanEntry.php b/src/pocketmine/permission/BanEntry.php new file mode 100644 index 000000000..4ca3a3c45 --- /dev/null +++ b/src/pocketmine/permission/BanEntry.php @@ -0,0 +1,136 @@ +name = strtolower($name); + $this->creationDate = new \DateTime(); + } + + public function getName(){ + return $this->name; + } + + public function getCreated(){ + return $this->creationDate; + } + + public function setCreated(\DateTime $date){ + $this->creationDate = $date; + } + + public function getSource(){ + return $this->source; + } + + public function setSource($source){ + $this->source = $source; + } + + public function getExpires(){ + return $this->expirationDate; + } + + /** + * @param \DateTime $date + */ + public function setExpires($date){ + $this->expirationDate = $date; + } + + public function hasExpired(){ + $now = new \DateTime(); + + return $this->expirationDate === null ? false : $this->expirationDate < $now; + } + + public function getReason(){ + return $this->reason; + } + + public function setReason($reason){ + $this->reason = $reason; + } + + public function getString(){ + $str = ""; + $str .= $this->getName(); + $str .= "|"; + $str .= $this->getCreated()->format(self::$format); + $str .= "|"; + $str .= $this->getSource(); + $str .= "|"; + $str .= $this->getExpires() === null ? "Forever" : $this->getExpires()->format(self::$format); + $str .= "|"; + $str .= $this->getReason(); + + return $str; + } + + /** + * @param string $str + * + * @return BanEntry + */ + public static function fromString($str){ + if(strlen($str) < 2){ + return null; + }else{ + $str = explode("|", trim($str)); + $entry = new BanEntry(trim(array_shift($str))); + if(count($str) > 0){ + $entry->setCreated(\DateTime::createFromFormat(self::$format, array_shift($str))); + if(count($str) > 0){ + $entry->setSource(trim(array_shift($str))); + if(count($str) > 0){ + $expire = trim(array_shift($str)); + if(strtolower($expire) !== "forever" and strlen($expire) > 0){ + $entry->setExpires(\DateTime::createFromFormat(self::$format, $expire)); + } + if(count($str) > 0){ + $entry->setReason(trim(array_shift($str))); + + return $entry; + }else{ + return $entry; + } + } + }else{ + return $entry; + } + }else{ + return $entry; + } + } + } +} \ No newline at end of file diff --git a/src/pocketmine/permission/BanList.php b/src/pocketmine/permission/BanList.php new file mode 100644 index 000000000..63c8508dd --- /dev/null +++ b/src/pocketmine/permission/BanList.php @@ -0,0 +1,166 @@ +file = $file; + } + + /** + * @return bool + */ + public function isEnabled(){ + return $this->enabled === true; + } + + /** + * @param bool $flag + */ + public function setEnabled($flag){ + $this->enabled = (bool) $flag; + } + + /** + * @return BanEntry[] + */ + public function getEntries(){ + $this->removeExpired(); + + return $this->list; + } + + /** + * @param string $name + * + * @return bool + */ + public function isBanned($name){ + $name = strtolower($name); + if(!$this->isEnabled()){ + return false; + }else{ + $this->removeExpired(); + + return isset($this->list[$name]); + } + } + + /** + * @param BanEntry $entry + */ + public function add(BanEntry $entry){ + $this->list[$entry->getName()] = $entry; + $this->save(); + } + + /** + * @param string $target + * @param string $reason + * @param \DateTime $expires + * @param string $source + * + * @return BanEntry + */ + public function addBan($target, $reason = null, $expires = null, $source = null){ + $entry = new BanEntry($target); + $entry->setSource($source != null ? $source : $entry->getSource()); + $entry->setExpires($expires); + $entry->setReason($reason != null ? $reason : $entry->getReason()); + + $this->list[$entry->getName()] = $entry; + $this->save(); + + return $entry; + } + + /** + * @param string $name + */ + public function remove($name){ + $name = strtolower($name); + if(isset($this->list[$name])){ + unset($this->list[$name]); + $this->save(); + } + } + + public function removeExpired(){ + foreach($this->list as $name => $entry){ + if($entry->hasExpired()){ + unset($this->list[$name]); + } + } + } + + public function load(){ + $this->list = array(); + $fp = @fopen($this->file, "r"); + if(is_resource($fp)){ + while(($line = fgets($fp)) !== false){ + if($line{0} !== "#"){ + $entry = BanEntry::fromString($line); + if($entry instanceof BanEntry){ + $this->list[$entry->getName()] = $entry; + } + } + } + fclose($fp); + }else{ + console("[ERROR] Could not load ban list"); + } + } + + public function save($flag = true){ + $this->removeExpired(); + $fp = @fopen($this->file, "w"); + if(is_resource($fp)){ + if($flag === true){ + fwrite($fp, "# Updated " . strftime("%x %H:%M", time()) . " by " . Server::getInstance()->getName() . " " . Server::getInstance()->getPocketMineVersion() . "\n"); + fwrite($fp, "# victim name | ban date | banned by | banned until | reason\n\n"); + } + + foreach($this->list as $entry){ + fwrite($fp, $entry->getString() . "\n"); + } + fclose($fp); + }else{ + console("[ERROR] Could not save ban list"); + } + } + +} \ No newline at end of file diff --git a/src/pocketmine/permission/DefaultPermissions.php b/src/pocketmine/permission/DefaultPermissions.php new file mode 100644 index 000000000..ca94f28e0 --- /dev/null +++ b/src/pocketmine/permission/DefaultPermissions.php @@ -0,0 +1,114 @@ +getChildren()[$perm->getName()] = true; + + return self::registerPermission($perm); + } + Server::getInstance()->getPluginManager()->addPermission($perm); + + return Server::getInstance()->getPluginManager()->getPermission($perm->getName()); + } + + public static function registerCorePermissions(){ + $parent = self::registerPermission(new Permission(self::ROOT, "Allows using all PocketMine commands and utilities")); + + $broadcasts = self::registerPermission(new Permission(self::ROOT . ".broadcast", "Allows the user to receive all broadcast messages"), $parent); + + self::registerPermission(new Permission(self::ROOT . ".broadcast.admin", "Allows the user to receive administrative broadcasts", Permission::DEFAULT_OP), $broadcasts); + self::registerPermission(new Permission(self::ROOT . ".broadcast.user", "Allows the user to receive user broadcasts", Permission::DEFAULT_TRUE), $broadcasts); + + $broadcasts->recalculatePermissibles(); + + $commands = self::registerPermission(new Permission(self::ROOT . ".command", "Allows using all PocketMine commands"), $parent); + + $whitelist = self::registerPermission(new Permission(self::ROOT . ".command.whitelist", "Allows the user to modify the server whitelist", Permission::DEFAULT_OP), $commands); + self::registerPermission(new Permission(self::ROOT . ".command.whitelist.add", "Allows the user to add a player to the server whitelist"), $whitelist); + self::registerPermission(new Permission(self::ROOT . ".command.whitelist.remove", "Allows the user to remove a player to the server whitelist"), $whitelist); + self::registerPermission(new Permission(self::ROOT . ".command.whitelist.reload", "Allows the user to reload the server whitelist"), $whitelist); + self::registerPermission(new Permission(self::ROOT . ".command.whitelist.enable", "Allows the user to enable the server whitelist"), $whitelist); + self::registerPermission(new Permission(self::ROOT . ".command.whitelist.disable", "Allows the user to disable the server whitelist"), $whitelist); + self::registerPermission(new Permission(self::ROOT . ".command.whitelist.list", "Allows the user to list all the players on the server whitelist"), $whitelist); + $whitelist->recalculatePermissibles(); + + $ban = self::registerPermission(new Permission(self::ROOT . ".command.ban", "Allows the user to ban people", Permission::DEFAULT_OP), $commands); + self::registerPermission(new Permission(self::ROOT . ".command.ban.player", "Allows the user to ban players"), $ban); + self::registerPermission(new Permission(self::ROOT . ".command.ban.ip", "Allows the user to ban IP addresses"), $ban); + $ban->recalculatePermissibles(); + + $unban = self::registerPermission(new Permission(self::ROOT . ".command.unban", "Allows the user to unban people", Permission::DEFAULT_OP), $commands); + self::registerPermission(new Permission(self::ROOT . ".command.unban.player", "Allows the user to unban players"), $unban); + self::registerPermission(new Permission(self::ROOT . ".command.unban.ip", "Allows the user to unban IP addresses"), $unban); + $unban->recalculatePermissibles(); + + $op = self::registerPermission(new Permission(self::ROOT . ".command.op", "Allows the user to change operators", Permission::DEFAULT_OP), $commands); + self::registerPermission(new Permission(self::ROOT . ".command.op.give", "Allows the user to give a player operator status"), $op); + self::registerPermission(new Permission(self::ROOT . ".command.op.take", "Allows the user to take a players operator status"), $op); + $op->recalculatePermissibles(); + + $save = self::registerPermission(new Permission(self::ROOT . ".command.save", "Allows the user to save the worlds", Permission::DEFAULT_OP), $commands); + self::registerPermission(new Permission(self::ROOT . ".command.save.enable", "Allows the user to enable automatic saving"), $save); + self::registerPermission(new Permission(self::ROOT . ".command.save.disable", "Allows the user to disable automatic saving"), $save); + self::registerPermission(new Permission(self::ROOT . ".command.save.perform", "Allows the user to perform a manual save"), $save); + $save->recalculatePermissibles(); + + $time = self::registerPermission(new Permission(self::ROOT . ".command.time", "Allows the user to alter the time", Permission::DEFAULT_OP), $commands); + self::registerPermission(new Permission(self::ROOT . ".command.time.add", "Allows the user to fast-forward time"), $time); + self::registerPermission(new Permission(self::ROOT . ".command.time.set", "Allows the user to change the time"), $time); + $time->recalculatePermissibles(); + + self::registerPermission(new Permission(self::ROOT . ".command.kill", "Allows the user to commit suicide", Permission::DEFAULT_TRUE), $commands); + self::registerPermission(new Permission(self::ROOT . ".command.me", "Allows the user to perform a chat action", Permission::DEFAULT_TRUE), $commands); + self::registerPermission(new Permission(self::ROOT . ".command.tell", "Allows the user to privately message another player", Permission::DEFAULT_TRUE), $commands); + self::registerPermission(new Permission(self::ROOT . ".command.say", "Allows the user to talk as the console", Permission::DEFAULT_OP), $commands); + self::registerPermission(new Permission(self::ROOT . ".command.give", "Allows the user to give items to players", Permission::DEFAULT_OP), $commands); + self::registerPermission(new Permission(self::ROOT . ".command.teleport", "Allows the user to teleport players", Permission::DEFAULT_OP), $commands); + self::registerPermission(new Permission(self::ROOT . ".command.kick", "Allows the user to kick players", Permission::DEFAULT_OP), $commands); + self::registerPermission(new Permission(self::ROOT . ".command.stop", "Allows the user to stop the server", Permission::DEFAULT_OP), $commands); + self::registerPermission(new Permission(self::ROOT . ".command.list", "Allows the user to list all online players", Permission::DEFAULT_OP), $commands); + self::registerPermission(new Permission(self::ROOT . ".command.help", "Allows the user to view the help menu", Permission::DEFAULT_TRUE), $commands); + self::registerPermission(new Permission(self::ROOT . ".command.plugins", "Allows the user to view the list of plugins", Permission::DEFAULT_OP), $commands); + //TODO: self::registerPermission(new Permission(self::ROOT . ".command.reload", "Allows the user to reload the server settings", Permission::DEFAULT_OP), $commands); + self::registerPermission(new Permission(self::ROOT . ".command.version", "Allows the user to view the version of the server", Permission::DEFAULT_TRUE), $commands); + self::registerPermission(new Permission(self::ROOT . ".command.gamemode", "Allows the user to change the gamemode of players", Permission::DEFAULT_OP), $commands); + self::registerPermission(new Permission(self::ROOT . ".command.defaultgamemode", "Allows the user to change the default gamemode", Permission::DEFAULT_OP), $commands); + self::registerPermission(new Permission(self::ROOT . ".command.seed", "Allows the user to view the seed of the world", Permission::DEFAULT_OP), $commands); + + $commands->recalculatePermissibles(); + + $parent->recalculatePermissibles(); + } +} \ No newline at end of file diff --git a/src/pocketmine/permission/Permissible.php b/src/pocketmine/permission/Permissible.php new file mode 100644 index 000000000..777337fb8 --- /dev/null +++ b/src/pocketmine/permission/Permissible.php @@ -0,0 +1,74 @@ +opable = $opable; + if($opable instanceof Permissible){ + $this->parent = $opable; + }else{ + $this->parent = $this; + } + } + + /** + * @return bool + */ + public function isOp(){ + if($this->opable === null){ + return false; + }else{ + return $this->opable->isOp(); + } + } + + /** + * @param bool $value + */ + public function setOp($value){ + if($this->opable === null){ + trigger_error("Cannot change ip value as no ServerOperator is set", E_USER_WARNING); + + return; + }else{ + $this->opable->setOp($value); + } + } + + /** + * @param Permission|string $name + * + * @return bool + */ + public function isPermissionSet($name){ + return isset($this->permissions[$name instanceof Permission ? $name->getName() : $name]); + } + + /** + * @param Permission|string $name + * + * @return bool + */ + public function hasPermission($name){ + if($name instanceof Permission){ + $name = $name->getName(); + } + + if($this->isPermissionSet($name)){ + return $this->permissions[$name]->getValue(); + } + + if(($perm = Server::getInstance()->getPluginManager()->getPermission($name)) !== null){ + $perm = $perm->getDefault(); + return $perm === Permission::DEFAULT_TRUE or ($this->isOp() and $perm === Permission::DEFAULT_OP) or (!$this->isOp() and $perm === Permission::DEFAULT_NOT_OP); + }else{ + return Permission::$DEFAULT_PERMISSION === Permission::DEFAULT_TRUE or ($this->isOp() and Permission::$DEFAULT_PERMISSION === Permission::DEFAULT_OP) or (!$this->isOp() and Permission::$DEFAULT_PERMISSION === Permission::DEFAULT_NOT_OP); + } + + } + + /** + * //TODO: tick scheduled attachments + * + * @param Plugin $plugin + * @param string $name + * @param bool $value + * + * @return PermissionAttachment + */ + public function addAttachment(Plugin $plugin, $name = null, $value = null){ + if($plugin === null){ + trigger_error("Plugin cannot be null", E_USER_WARNING); + + return null; + }elseif(!$plugin->isEnabled()){ + trigger_error("Plugin " . $plugin->getDescription()->getName() . " is disabled", E_USER_WARNING); + + return null; + } + + $result = new PermissionAttachment($plugin, $this->parent); + $this->attachments[spl_object_hash($result)] = $result; + if($name !== null and $value !== null){ + $result->setPermission($name, $value); + } + + $this->recalculatePermissions(); + + return $result; + } + + /** + * @param PermissionAttachment $attachment + * + * @return void + */ + public function removeAttachment(PermissionAttachment $attachment){ + if($attachment === null){ + trigger_error("Attachment cannot be null", E_USER_WARNING); + + return; + } + + if(isset($this->attachments[spl_object_hash($attachment)])){ + unset($this->attachments[spl_object_hash($attachment)]); + if(($ex = $attachment->getRemovalCallback()) !== null){ + $ex->attachmentRemoved($attachment); + } + + $this->recalculatePermissions(); + + } + + } + + public function recalculatePermissions(){ + $this->clearPermissions(); + $defaults = Server::getInstance()->getPluginManager()->getDefaultPermissions($this->isOp()); + Server::getInstance()->getPluginManager()->subscribeToDefaultPerms($this->isOp(), $this->parent); + + foreach($defaults as $perm){ + $name = $perm->getName(); + $this->permissions[$name] = new PermissionAttachmentInfo($this->parent, $name, null, true); + Server::getInstance()->getPluginManager()->subscribeToPermission($name, $this->parent); + $this->calculateChildPermissions($perm->getChildren(), false, null); + } + + foreach($this->attachments as $attachment){ + $this->calculateChildPermissions($attachment->getPermissions(), false, $attachment); + } + } + + public function clearPermissions(){ + foreach(array_keys($this->permissions) as $name){ + Server::getInstance()->getPluginManager()->unsubscribeFromPermission($name, $this->parent); + } + + Server::getInstance()->getPluginManager()->unsubscribeFromDefaultPerms(false, $this->parent); + Server::getInstance()->getPluginManager()->unsubscribeFromDefaultPerms(true, $this->parent); + + $this->permissions = array(); + } + + /** + * @param bool[] $children + * @param bool $invert + * @param PermissionAttachment $attachment + */ + public function calculateChildPermissions(array $children, $invert, $attachment){ + foreach(array_keys($children) as $name){ + $perm = Server::getInstance()->getPluginManager()->getPermission($name); + $value = $invert === true ? !$children[$name] : $children[$name]; + $this->permissions[$name] = new PermissionAttachmentInfo($this->parent, $name, $attachment, $value); + Server::getInstance()->getPluginManager()->subscribeToPermission($name, $this->parent); + + if($perm instanceof Permission){ + $this->calculateChildPermissions($perm->getChildren(), !$value, $attachment); + } + } + } + + /** + * @return PermissionAttachmentInfo[] + */ + public function getEffectivePermissions(){ + return $this->permissions; + } +} \ No newline at end of file diff --git a/src/pocketmine/permission/Permission.php b/src/pocketmine/permission/Permission.php new file mode 100644 index 000000000..35da701c8 --- /dev/null +++ b/src/pocketmine/permission/Permission.php @@ -0,0 +1,254 @@ +name = $name; + $this->description = $description !== null ? $description : ""; + $this->defaultValue = $defaultValue !== null ? $defaultValue : self::$DEFAULT_PERMISSION; + $this->children = $children; + + $this->recalculatePermissibles(); + } + + /** + * @return string + */ + public function getName(){ + return $this->name; + } + + /** + * @return string[] + */ + public function &getChildren(){ + return $this->children; + } + + /** + * @return string + */ + public function getDefault(){ + return $this->defaultValue; + } + + /** + * @param string $value + */ + public function setDefault($value){ + if($value !== $this->defaultValue){ + $this->defaultValue = $value; + $this->recalculatePermissibles(); + } + } + + /** + * @return string + */ + public function getDescription(){ + return $this->description; + } + + /** + * @param string $value + */ + public function setDescription($value){ + $this->description = $value; + } + + /** + * @return Permissible[] + */ + public function getPermissibles(){ + return Server::getInstance()->getPluginManager()->getPermissionSubscriptions($this->name); + } + + public function recalculatePermissibles(){ + $perms = $this->getPermissibles(); + + Server::getInstance()->getPluginManager()->recalculatePermissionDefaults($this); + + foreach($perms as $p){ + $p->recalculatePermissions(); + } + } + + + /** + * @param string|Permission $name + * @param $value + * + * @return Permission|void + */ + public function addParent($name, $value){ + if($name instanceof Permission){ + $name->getChildren()[$this->getName()] = $value; + $name->recalculatePermissibles(); + }else{ + $perm = Server::getInstance()->getPluginManager()->getPermission($name); + if($perm === null){ + $perm = new Permission($name); + Server::getInstance()->getPluginManager()->addPermission($perm); + } + + $this->addParent($perm, $value); + + return $perm; + } + } + + /** + * @param array $data + * @param $default + * + * @return Permission[] + */ + public static function loadPermissions(array $data, $default = self::DEFAULT_OP){ + $result = array(); + foreach($data as $key => $entry){ + $result[] = self::loadPermission($key, $entry, $default, $result); + } + + return $result; + } + + /** + * @param string $name + * @param array $data + * @param string $default + * @param array $output + * + * @return Permission + */ + public static function loadPermission($name, array $data, $default = self::DEFAULT_OP, &$output = array()){ + $desc = null; + $children = array(); + if(isset($data["default"])){ + $value = Permission::getByName($data["default"]); + if($value !== null){ + $default = $value; + }else{ + trigger_error("'default' key contained unknown value", E_USER_WARNING); + } + } + + if(isset($data["children"])){ + if(is_array($data["children"])){ + foreach($data["children"] as $k => $v){ + if(is_array($v)){ + if(($perm = self::loadPermission($k, $v, $default, $output)) !== null){ + $output[] = $perm; + } + } + $children[$k] = true; + } + }else{ + trigger_error("'children' key is of wrong type", E_USER_WARNING); + } + } + + if(isset($data["description"])){ + $desc = $data["description"]; + } + + return new Permission($name, $desc, $default, $children); + + } + + +} \ No newline at end of file diff --git a/src/pocketmine/permission/PermissionAttachment.php b/src/pocketmine/permission/PermissionAttachment.php new file mode 100644 index 000000000..f70010d5e --- /dev/null +++ b/src/pocketmine/permission/PermissionAttachment.php @@ -0,0 +1,113 @@ +isEnabled()){ + trigger_error("Plugin " . $plugin->getDescription()->getName() . " is disabled", E_USER_WARNING); + + return; + } + + $this->permissible = $permissible; + $this->plugin = $plugin; + } + + /** + * @return Plugin + */ + public function getPlugin(){ + return $this->plugin; + } + + /** + * @param PermissionRemovedExecutor $ex + */ + public function setRemovalCallback(PermissionRemovedExecutor $ex){ + $this->removed = $ex; + } + + /** + * @return PermissionRemovedExecutor + */ + public function getRemovalCallback(){ + return $this->removed; + } + + /** + * @return Permissible + */ + public function getPermissible(){ + return $this->permissible; + } + + /** + * @return bool[] + */ + public function getPermissions(){ + return $this->permissions; + } + + /** + * @param string|Permission $name + * @param bool $value + */ + public function setPermission($name, $value){ + $this->permissions[$name instanceof Permission ? $name->getName() : $name] = $value; + $this->permissible->recalculatePermissions(); + } + + /** + * @param string|Permission $name + */ + public function unsetPermission($name){ + unset($this->permissions[$name instanceof Permission ? $name->getName() : $name]); + } + + /** + * @return void + */ + public function remove(){ + $this->permissible->removeAttachment($this); + } +} \ No newline at end of file diff --git a/src/pocketmine/permission/PermissionAttachmentInfo.php b/src/pocketmine/permission/PermissionAttachmentInfo.php new file mode 100644 index 000000000..5bb3aba6b --- /dev/null +++ b/src/pocketmine/permission/PermissionAttachmentInfo.php @@ -0,0 +1,84 @@ +permissible = $permissible; + $this->permission = $permission; + $this->attachment = $attachment; + $this->value = $value; + } + + /** + * @return Permissible + */ + public function getPermissible(){ + return $this->permissible; + } + + /** + * @return string + */ + public function getPermission(){ + return $this->permission; + } + + /** + * @return PermissionAttachment + */ + public function getAttachment(){ + return $this->attachment; + } + + /** + * @return bool + */ + public function getValue(){ + return $this->value; + } +} \ No newline at end of file diff --git a/src/pocketmine/permission/PermissionRemovedExecutor.php b/src/pocketmine/permission/PermissionRemovedExecutor.php new file mode 100644 index 000000000..2ecfeed58 --- /dev/null +++ b/src/pocketmine/permission/PermissionRemovedExecutor.php @@ -0,0 +1,33 @@ +server = $server; + } + + /** + * Loads the plugin contained in $file + * + * @param string $file + * + * @return Plugin + */ + public function loadPlugin($file){ + if(is_dir($file) and file_exists($file . "/plugin.yml") and file_exists($file . "/src/")){ + if(($description = $this->getPluginDescription($file)) instanceof PluginDescription){ + console("[INFO] Loading " . $description->getFullName()); + console("[WARNING] Non-packaged plugin ".$description->getName() ." detected, do not use on production."); + $dataFolder = dirname($file) . DIRECTORY_SEPARATOR . $description->getName(); + if(file_exists($dataFolder) and !is_dir($dataFolder)){ + trigger_error("Projected dataFolder '" . $dataFolder . "' for " . $description->getName() . " exists and is not a directory", E_USER_WARNING); + + return null; + } + + + $className = $description->getMain(); + $this->server->getLoader()->add(substr($className, 0, strpos($className, "\\")), array( + $file . "/src" + )); + + if(class_exists($className, true)){ + $plugin = new $className(); + $this->initPlugin($plugin, $description, $dataFolder, $file); + + return $plugin; + }else{ + trigger_error("Couldn't load plugin " . $description->getName() . ": main class not found", E_USER_WARNING); + + return null; + } + } + } + + return null; + } + + /** + * Gets the PluginDescription from the file + * + * @param string $file + * + * @return PluginDescription + */ + public function getPluginDescription($file){ + if(is_dir($file) and file_exists($file . "/plugin.yml")){ + $yaml = @file_get_contents($file . "/plugin.yml"); + if($yaml != ""){ + return new PluginDescription($yaml); + } + } + + return null; + } + + /** + * Returns the filename patterns that this loader accepts + * + * @return array + */ + public function getPluginFilters(){ + return "/[^\\.]/"; + } + + /** + * @param PluginBase $plugin + * @param PluginDescription $description + * @param string $dataFolder + * @param string $file + */ + private function initPlugin(PluginBase $plugin, PluginDescription $description, $dataFolder, $file){ + $plugin->init($this, $this->server, $description, $dataFolder, $file); + $plugin->onLoad(); + } + + /** + * @param Plugin $plugin + */ + public function enablePlugin(Plugin $plugin){ + if($plugin instanceof PluginBase and !$plugin->isEnabled()){ + console("[INFO] Enabling " . $plugin->getDescription()->getFullName()); + + $plugin->setEnabled(true); + + Server::getInstance()->getPluginManager()->callEvent(new PluginEnableEvent($plugin)); + } + } + + /** + * @param Plugin $plugin + */ + public function disablePlugin(Plugin $plugin){ + if($plugin instanceof PluginBase and $plugin->isEnabled()){ + console("[INFO] Disabling " . $plugin->getDescription()->getFullName()); + + Server::getInstance()->getPluginManager()->callEvent(new PluginDisableEvent($plugin)); + + $plugin->setEnabled(false); + } + } +} \ No newline at end of file diff --git a/src/pocketmine/plugin/MethodEventExecutor.php b/src/pocketmine/plugin/MethodEventExecutor.php new file mode 100644 index 000000000..a735f2954 --- /dev/null +++ b/src/pocketmine/plugin/MethodEventExecutor.php @@ -0,0 +1,38 @@ +method = $method; + } + + public function execute(Listener $listener, Event $event){ + call_user_func(array($listener, $this->method), $event); + } +} \ No newline at end of file diff --git a/src/pocketmine/plugin/PharPluginLoader.php b/src/pocketmine/plugin/PharPluginLoader.php new file mode 100644 index 000000000..f1f14f04b --- /dev/null +++ b/src/pocketmine/plugin/PharPluginLoader.php @@ -0,0 +1,145 @@ +server = $server; + } + + /** + * Loads the plugin contained in $file + * + * @param string $file + * + * @return Plugin + */ + public function loadPlugin($file){ + if(\Phar::isValidPharFilename($file) and ($description = $this->getPluginDescription($file)) instanceof PluginDescription){ + console("[INFO] Loading " . $description->getFullName()); + $dataFolder = dirname($file) . DIRECTORY_SEPARATOR . $description->getName(); + if(file_exists($dataFolder) and !is_dir($dataFolder)){ + trigger_error("Projected dataFolder '" . $dataFolder . "' for " . $description->getName() . " exists and is not a directory", E_USER_WARNING); + + return null; + } + $file = "phar://$file"; + $className = $description->getMain(); + $this->server->getLoader()->add(substr($className, 0, strpos($className, "\\")), array( + "$file/src" + )); + + if(class_exists($className, true)){ + $plugin = new $className(); + $this->initPlugin($plugin, $description, $dataFolder, $file); + + return $plugin; + }else{ + trigger_error("Couldn't load plugin " . $description->getName() . ": main class not found", E_USER_WARNING); + + return null; + } + } + return null; + } + + /** + * Gets the PluginDescription from the file + * + * @param string $file + * + * @return PluginDescription + */ + public function getPluginDescription($file){ + if(\Phar::isValidPharFilename($file)){ + $phar = new \Phar($file); + if(isset($phar["plugin.yml"])){ + $pluginYml = $phar["plugin.yml"]; + if($pluginYml instanceof \PharFileInfo){ + return new PluginDescription($pluginYml->getContent()); + } + } + } + + return null; + } + + /** + * Returns the filename patterns that this loader accepts + * + * @return array + */ + public function getPluginFilters(){ + return "/\\.phar$/i"; + } + + /** + * @param PluginBase $plugin + * @param PluginDescription $description + * @param string $dataFolder + * @param string $file + */ + private function initPlugin(PluginBase $plugin, PluginDescription $description, $dataFolder, $file){ + $plugin->init($this, $this->server, $description, $dataFolder, $file); + $plugin->onLoad(); + } + + /** + * @param Plugin $plugin + */ + public function enablePlugin(Plugin $plugin){ + if($plugin instanceof PluginBase and !$plugin->isEnabled()){ + console("[INFO] Enabling " . $plugin->getDescription()->getFullName()); + + $plugin->setEnabled(true); + + Server::getInstance()->getPluginManager()->callEvent(new PluginEnableEvent($plugin)); + } + } + + /** + * @param Plugin $plugin + */ + public function disablePlugin(Plugin $plugin){ + if($plugin instanceof PluginBase and $plugin->isEnabled()){ + console("[INFO] Disabling " . $plugin->getDescription()->getFullName()); + + Server::getInstance()->getPluginManager()->callEvent(new PluginDisableEvent($plugin)); + + $plugin->setEnabled(false); + } + } +} \ No newline at end of file diff --git a/src/pocketmine/plugin/Plugin.php b/src/pocketmine/plugin/Plugin.php new file mode 100644 index 000000000..5348e58b1 --- /dev/null +++ b/src/pocketmine/plugin/Plugin.php @@ -0,0 +1,83 @@ +isEnabled === true; + } + + /** + * @param bool $boolean + */ + public final function setEnabled($boolean = true){ + if($this->isEnabled !== $boolean){ + $this->isEnabled = $boolean; + if($this->isEnabled === true){ + $this->onEnable(); + }else{ + $this->onDisable(); + } + } + } + + /** + * @return bool + */ + public final function isDisabled(){ + return $this->isEnabled === false; + } + + public final function getDataFolder(){ + return $this->dataFolder; + } + + public final function getDescription(){ + return $this->description; + } + + public final function init(PluginLoader $loader, Server $server, PluginDescription $description, $dataFolder, $file){ + if($this->initialized === false){ + $this->initialized = true; + $this->loader = $loader; + $this->server = $server; + $this->description = $description; + $this->dataFolder = $dataFolder; + $this->file = $file; + $this->configFile = $this->dataFolder . "config.yml"; + } + } + + public final function isInitialized(){ + return $this->initialized; + } + + /** + * @param CommandSender $sender + * @param Command $command + * @param string $label + * @param string[] $args + * + * @return bool + */ + public function onCommand(CommandSender $sender, Command $command, $label, array $args){ + return false; + } + + public function getCommand($name){ + $command = $this->getServer()->getPluginCommand($name); + if($command === null or $command->getPlugin() !== $this){ + $command = $this->getServer()->getPluginCommand(strtolower($this->description->getName()) . ":" . $name); + } + + if($command instanceof PluginCommand and $command->getPlugin() === $this){ + return $command; + }else{ + return null; + } + } + + /** + * @return bool + */ + protected function isPhar(){ + return substr($this->file, 0, 7) === "phar://"; + } + + /** + * Gets an embedded resource on the plugin file. + * + * @param string $filename + * + * @return bool|string Resource data, or false + */ + public function getResource($filename){ + $filename = str_replace("\\", "/", $filename); + if(file_exists($this->file . "resources/" . $filename)){ + return file_get_contents($this->file . "resources/" . $filename); + } + + return false; + } + + /** + * @param string $filename + * @param bool $replace + * + * @return bool + */ + public function saveResource($filename, $replace = false){ + if(trim($filename) === ""){ + return false; + } + + if(($resource = $this->getResource($filename)) === false){ + return false; + } + + $out = $this->file . $filename; + if(!file_exists($this->dataFolder)){ + @mkdir($this->dataFolder, 0755, true); + } + + if(file_exists($out) and $replace !== true){ + return false; + } + + return @file_put_contents($out, $resource) !== false; + } + + /** + * Returns all the resources incrusted on the plugin + * + * @return string[] + */ + public function getResources(){ + if(!$this->isPhar()){ + $resources = array(); + foreach(new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->file . "resources/")) as $resource){ + $resources[] = $resource; + } + + return $resources; + } + } + + public function getConfig(){ + if(!isset($this->config)){ + $this->reloadConfig(); + } + + return $this->config; + } + + public function saveConfig(){ + if($this->getConfig()->save() === false){ + console("[SEVERE] Could not save config to " . $this->configFile); + } + } + + public function saveDefaultConfig(){ + if(!file_exists($this->configFile)){ + $this->saveResource("config.yml", false); + } + } + + public function reloadConfig(){ + $this->config = new Config($this->configFile); + if(($configStream = $this->getResource("config.yml")) !== false){ + $this->config->setDefaults(yaml_parse(config::fixYAMLIndexes($configStream))); + } + } + + /** + * @return Server + */ + public final function getServer(){ + return $this->server; + } + + /** + * @return string + */ + public final function getName(){ + return $this->description->getName(); + } + + protected function getFile(){ + return $this->file; + } + + /** + * @return PluginLoader + */ + public function getPluginLoader(){ + return $this->loader; + } + +} \ No newline at end of file diff --git a/src/pocketmine/plugin/PluginDescription.php b/src/pocketmine/plugin/PluginDescription.php new file mode 100644 index 000000000..7a66945cb --- /dev/null +++ b/src/pocketmine/plugin/PluginDescription.php @@ -0,0 +1,211 @@ +loadMap(\yaml_parse($yamlString)); + } + + private function loadMap(array $plugin){ + $this->name = preg_replace("[^A-Za-z0-9 _.-]", "", $plugin["name"]); + if($this->name === ""){ + trigger_error("Invalid PluginDescription name", E_USER_WARNING); + + return; + } + $this->name = str_replace(" ", "_", $this->name); + $this->version = $plugin["version"]; + $this->main = $plugin["main"]; + $this->api = !is_array($plugin["api"]) ? array($plugin["api"]) : $plugin["api"]; + if(stripos($this->main, "pocketmine\\") === 0){ + trigger_error("Invalid PluginDescription main, cannot start within the PocketMine namespace", E_USER_ERROR); + + return; + } + + if(isset($plugin["commands"]) and is_array($plugin["commands"])){ + $this->commands = $plugin["commands"]; + } + + if(isset($plugin["depend"])){ + $this->depend = (array) $plugin["depend"]; + } + if(isset($plugin["softdepend"])){ + $this->softDepend = (array) $plugin["softdepend"]; + } + if(isset($plugin["loadbefore"])){ + $this->loadBefore = (array) $plugin["loadbefore"]; + } + + if(isset($plugin["website"])){ + $this->website = $plugin["website"]; + } + if(isset($plugin["description"])){ + $this->description = $plugin["description"]; + } + if(isset($plugin["load"])){ + $order = strtoupper($plugin["load"]); + if(!defined("pocketmine\\plugin\\PluginLoadOrder::" . $order)){ + trigger_error("Invalid PluginDescription load", E_USER_ERROR); + + return; + }else{ + $this->order = constant("pocketmine\\plugin\\PluginLoadOrder::" . $order); + } + } + $this->authors = array(); + if(isset($plugin["author"])){ + $this->authors[] = $plugin["author"]; + } + if(isset($plugin["authors"])){ + foreach($plugin["authors"] as $author){ + $this->authors[] = $author; + } + } + + if(isset($plugin["permissions"])){ + $this->permissions = Permission::loadPermissions($plugin["permissions"]); + } + } + + /** + * @return string + */ + public function getFullName(){ + return $this->name . " v" . $this->version; + } + + /** + * @return array + */ + public function getCompatibleApis(){ + return $this->api; + } + + /** + * @return array + */ + public function getAuthors(){ + return $this->authors; + } + + /** + * @return array + */ + public function getCommands(){ + return $this->commands; + } + + /** + * @return array + */ + public function getDepend(){ + return $this->depend; + } + + /** + * @return string + */ + public function getDescription(){ + return $this->description; + } + + /** + * @return array + */ + public function getLoadBefore(){ + return $this->loadBefore; + } + + /** + * @return string + */ + public function getMain(){ + return $this->main; + } + + /** + * @return string + */ + public function getName(){ + return $this->name; + } + + /** + * @return int + */ + public function getOrder(){ + return $this->order; + } + + /** + * @return Permission[] + */ + public function getPermissions(){ + return $this->permissions; + } + + /** + * @return array + */ + public function getSoftDepend(){ + return $this->softDepend; + } + + /** + * @return string + */ + public function getVersion(){ + return $this->version; + } + + /** + * @return string + */ + public function getWebsite(){ + return $this->website; + } +} \ No newline at end of file diff --git a/src/pocketmine/plugin/PluginLoadOrder.php b/src/pocketmine/plugin/PluginLoadOrder.php new file mode 100644 index 000000000..85bd3da40 --- /dev/null +++ b/src/pocketmine/plugin/PluginLoadOrder.php @@ -0,0 +1,35 @@ +server = $server; + $this->commandMap = $commandMap; + } + + /** + * @param string $name + * + * @return null|Plugin + */ + public function getPlugin($name){ + if(isset($this->plugins[$name])){ + return $this->plugins[$name]; + } + + return null; + } + + /** + * @param string $loader A PluginLoader class name + * + * @return boolean + */ + public function registerInterface($loader){ + if(is_subclass_of($loader, "pocketmine\\plugin\\PluginLoader")){ + $loader = new $loader($this->server); + }else{ + return false; + } + + $this->fileAssociations[spl_object_hash($loader)] = $loader; + + return true; + } + + /** + * @return Plugin[] + */ + public function getPlugins(){ + return $this->plugins; + } + + /** + * @param string $path + * + * @return Plugin + */ + public function loadPlugin($path){ + foreach($this->fileAssociations as $loader){ + if(preg_match($loader->getPluginFilters(), basename($path)) > 0){ + $description = $loader->getPluginDescription($path); + if($description instanceof PluginDescription){ + if(($plugin = $loader->loadPlugin($path)) instanceof Plugin){ + $this->plugins[$plugin->getDescription()->getName()] = $plugin; + + return $plugin; + } + } + } + } + + return null; + } + + /** + * @param $directory + * + * @return Plugin[] + */ + public function loadPlugins($directory){ + if(is_dir($directory)){ + $plugins = array(); + $loadedPlugins = array(); + $dependencies = array(); + $softDependencies = array(); + foreach(new \IteratorIterator(new \DirectoryIterator($directory)) as $file){ + if($file === "." or $file === ".."){ + continue; + } + $file = $directory . $file; + foreach($this->fileAssociations as $loader){ + if(preg_match($loader->getPluginFilters(), basename($file)) > 0){ + $description = $loader->getPluginDescription($file); + if($description instanceof PluginDescription){ + $name = $description->getName(); + if(stripos($name, "pocketmine") !== false or stripos($name, "minecraft") !== false or stripos($name, "mojang") !== false){ + console("[ERROR] Could not load plugin '" . $name . "': restricted name"); + continue; + }elseif(strpos($name, " ") !== false){ + console("[WARNING] Plugin '" . $name . "' uses spaces in its name, this is discouraged"); + } + if(isset($plugins[$name])){ + console("[ERROR] Could not load duplicate plugin '" . $name . "': plugin exists"); + continue; + } + + $compatible = false; + + //Check multiple dependencies + foreach($description->getCompatibleApis() as $version){ + //Format: majorVersion.minorVersion.patch + $version = array_map("intval", explode(".", $version)); + $apiVersion = array_map("intval", explode(".", $this->server->getApiVersion())); + + //Completely different API version + if($version[0] !== $apiVersion[0]){ + continue; + } + + //If the plugin requires new API features, being backwards compatible + if($version[1] > $apiVersion[1]){ + continue; + } + + $compatible = true; + break; + } + + if($compatible === false){ + console("[ERROR] Could not load plugin '" . $name . "': API version not compatible"); + continue; + } + + $plugins[$name] = $file; + + $softDependencies[$name] = (array) $description->getSoftDepend(); + $dependencies[$name] = (array) $description->getDepend(); + + foreach($description->getLoadBefore() as $before){ + if(isset($softDependencies[$before])){ + $softDependencies[$before][] = $name; + }else{ + $softDependencies[$before] = array($name); + } + } + + break; + } + } + } + } + + while(count($plugins) > 0){ + $missingDependency = true; + foreach($plugins as $name => $file){ + if(isset($dependencies[$name])){ + foreach($dependencies[$name] as $key => $dependency){ + if(isset($loadedPlugins[$dependency])){ + unset($dependencies[$name][$key]); + }elseif(!isset($plugins[$dependency])){ + console("[SEVERE] Could not load plugin '" . $name . "': Unknown dependency"); + break; + } + } + + if(count($dependencies[$name]) === 0){ + unset($dependencies[$name]); + } + } + + if(isset($softDependencies[$name])){ + foreach($softDependencies[$name] as $key => $dependency){ + if(isset($loadedPlugins[$dependency])){ + unset($softDependencies[$name][$key]); + } + } + + if(count($softDependencies[$name]) === 0){ + unset($softDependencies[$name]); + } + } + + if(!isset($dependencies[$name]) and !isset($softDependencies[$name])){ + unset($plugins[$name]); + $missingDependency = false; + if($plugin = $this->loadPlugin($file) and $plugin instanceof Plugin){ + $loadedPlugins[$name] = $plugin; + }else{ + console("[SEVERE] Could not load plugin '" . $name . "'"); + } + } + } + + if($missingDependency === true){ + foreach($plugins as $name => $file){ + if(!isset($dependencies[$name])){ + unset($softDependencies[$name]); + unset($plugins[$name]); + $missingDependency = false; + if($plugin = $this->loadPlugin($file) and $plugin instanceof Plugin){ + $loadedPlugins[$name] = $plugin; + }else{ + console("[SEVERE] Could not load plugin '" . $name . "'"); + } + } + } + + //No plugins loaded :( + if($missingDependency === true){ + foreach($plugins as $name => $file){ + console("[SEVERE] Could not load plugin '" . $name . "': circular dependency detected"); + } + $plugins = array(); + } + } + } + + return $loadedPlugins; + }else{ + return array(); + } + } + + /** + * @param string $name + * + * @return null|Permission + */ + public function getPermission($name){ + if(isset($this->permissions[$name])){ + return $this->permissions[$name]; + } + + return null; + } + + /** + * @param Permission $permission + * + * @return bool + */ + public function addPermission(Permission $permission){ + if(!isset($this->permissions[$permission->getName()])){ + $this->permissions[$permission->getName()] = $permission; + $this->calculatePermissionDefault($permission); + + return true; + } + + return false; + } + + /** + * @param string|Permission $permission + */ + public function removePermission($permission){ + if($permission instanceof Permission){ + unset($this->permissions[$permission->getName()]); + }else{ + unset($this->permissions[$permission]); + } + } + + /** + * @param boolean $op + * + * @return Permission[] + */ + public function getDefaultPermissions($op){ + if($op === true){ + return $this->defaultPermsOp; + }else{ + return $this->defaultPerms; + } + } + + /** + * @param Permission $permission + */ + public function recalculatePermissionDefaults(Permission $permission){ + if(isset($this->permissions[$permission->getName()])){ + unset($this->defaultPermsOp[$permission->getName()]); + unset($this->defaultPerms[$permission->getName()]); + $this->calculatePermissionDefault($permission); + } + } + + /** + * @param Permission $permission + */ + private function calculatePermissionDefault(Permission $permission){ + if($permission->getDefault() === Permission::DEFAULT_OP or $permission->getDefault() === Permission::DEFAULT_TRUE){ + $this->defaultPermsOp[$permission->getName()] = $permission; + $this->dirtyPermissibles(true); + } + + if($permission->getDefault() === Permission::DEFAULT_NOT_OP or $permission->getDefault() === Permission::DEFAULT_TRUE){ + $this->defaultPerms[$permission->getName()] = $permission; + $this->dirtyPermissibles(false); + } + } + + /** + * @param boolean $op + */ + private function dirtyPermissibles($op){ + foreach($this->getDefaultPermSubscriptions($op) as $p){ + $p->recalculatePermissions(); + } + } + + /** + * @param string $permission + * @param Permissible $permissible + */ + public function subscribeToPermission($permission, Permissible $permissible){ + if(!isset($this->permSubs[$permission])){ + //TODO: Use WeakRef + $this->permSubs[$permission] = array(); + } + $this->permSubs[$permission][spl_object_hash($permissible)] = $permissible; + } + + /** + * @param string $permission + * @param Permissible $permissible + */ + public function unsubscribeFromPermission($permission, Permissible $permissible){ + if(isset($this->permSubs[$permission])){ + unset($this->permSubs[$permission][spl_object_hash($permissible)]); + } + } + + /** + * @param string $permission + * + * @return Permissible[] + */ + public function getPermissionSubscriptions($permission){ + if(isset($this->permSubs[$permission])){ + return $this->permSubs[$permission]; + } + + return array(); + } + + /** + * @param boolean $op + * @param Permissible $permissible + */ + public function subscribeToDefaultPerms($op, Permissible $permissible){ + if($op === true){ + $this->defSubsOp[spl_object_hash($permissible)] = $permissible; + }else{ + $this->defSubs[spl_object_hash($permissible)] = $permissible; + } + } + + /** + * @param boolean $op + * @param Permissible $permissible + */ + public function unsubscribeFromDefaultPerms($op, Permissible $permissible){ + if($op === true){ + unset($this->defSubsOp[spl_object_hash($permissible)]); + }else{ + unset($this->defSubs[spl_object_hash($permissible)]); + } + } + + /** + * @param boolean $op + * + * @return Permissible[] + */ + public function getDefaultPermSubscriptions($op){ + if($op === true){ + return $this->defSubsOp; + }else{ + return $this->defSubs; + } + } + + /** + * @return Permission[] + */ + public function getPermissions(){ + return $this->permissions; + } + + /** + * @param Plugin $plugin + * + * @return bool + */ + public function isPluginEnabled(Plugin $plugin){ + if($plugin instanceof Plugin and isset($this->plugins[$plugin->getDescription()->getName()])){ + return $plugin->isEnabled(); + }else{ + return false; + } + } + + /** + * @param Plugin $plugin + */ + public function enablePlugin(Plugin $plugin){ + if(!$plugin->isEnabled()){ + $pluginCommands = $this->parseYamlCommands($plugin); + + if(count($pluginCommands) > 0){ + $this->commandMap->registerAll($plugin->getDescription()->getName(), $pluginCommands); + } + + foreach($plugin->getDescription()->getPermissions() as $perm){ + $this->addPermission($perm); + } + + $plugin->getPluginLoader()->enablePlugin($plugin); + } + } + + /** + * @param Plugin $plugin + * + * @return PluginCommand[] + */ + protected function parseYamlCommands(Plugin $plugin){ + $pluginCmds = array(); + + foreach($plugin->getDescription()->getCommands() as $key => $data){ + if(strpos($key, ":") !== false){ + console("[SEVERE] Could not load command " . $key . " for plugin " . $plugin->getDescription()->getName()); + continue; + } + if(is_array($data)){ + $newCmd = new PluginCommand($key, $plugin); + if(isset($data["description"])){ + $newCmd->setDescription($data["description"]); + } + + if(isset($data["usage"])){ + $newCmd->setUsage($data["usage"]); + } + + if(isset($data["aliases"]) and is_array($data["aliases"])){ + $aliasList = array(); + foreach($data["aliases"] as $alias){ + if(strpos($alias, ":") !== false){ + console("[SEVERE] Could not load alias " . $alias . " for plugin " . $plugin->getDescription()->getName()); + continue; + } + $aliasList[] = $alias; + } + + $newCmd->setAliases($aliasList); + } + + if(isset($data["permission"])){ + $newCmd->setPermission($data["permission"]); + } + + if(isset($data["permission-message"])){ + $newCmd->setPermissionMessage($data["permission-message"]); + } + + $pluginCmds[] = $newCmd; + } + } + return $pluginCmds; + } + + public function disablePlugins(){ + foreach($this->getPlugins() as $plugin){ + $this->disablePlugin($plugin); + } + } + + /** + * @param Plugin $plugin + */ + public function disablePlugin(Plugin $plugin){ + if($plugin->isEnabled()){ + $plugin->getPluginLoader()->disablePlugin($plugin); + $this->server->getScheduler()->cancelTasks($plugin); + HandlerList::unregisterAll($plugin); + foreach($plugin->getDescription()->getPermissions() as $perm){ + $this->removePermission($perm); + } + } + } + + public function clearPlugins(){ + $this->disablePlugins(); + $this->plugins = array(); + $this->fileAssociations = array(); + $this->permissions = array(); + $this->defaultPerms = array(); + $this->defaultPermsOp = array(); + } + + /** + * Calls an event + * + * @param Event $event + */ + public function callEvent(Event $event){ + $this->fireEvent($event); + } + + private function fireEvent(Event $event){ + $handlers = $event->getHandlers(); + $listeners = $handlers->getRegisteredListeners(); + + foreach($listeners as $registration){ + if(!$registration->getPlugin()->isEnabled()){ + continue; + } + $registration->callEvent($event); + } + } + + /** + * Registers all the events in the given Listener class + * + * @param Listener $listener + * @param Plugin $plugin + */ + public function registerEvents(Listener $listener, Plugin $plugin){ + if(!$plugin->isEnabled()){ + trigger_error("Plugin attempted to register ".get_class($listener)." while not enabled", E_USER_WARNING); + return; + } + + $reflection = new \ReflectionClass(get_class($listener)); + foreach($reflection->getMethods() as $method){ + if(!$method->isStatic()){ + $priority = EventPriority::NORMAL; + $ignoreCancelled = false; + if(preg_match("/^[\t ]*\\* @priority[\t ]{1,}([a-zA-Z]{1,})$/m", (string) $method->getDocComment(), $matches) > 0){ + $matches[1] = strtoupper($matches[1]); + if(defined("pocketmine\\event\\EventPriority::".$matches[1])){ + $priority = constant("pocketmine\\event\\EventPriority::".$matches[1]); + } + } + if(preg_match("/^[\t ]*\\* @ignoreCancelled[\t ]{1,}([a-zA-Z]{1,})$/m", (string) $method->getDocComment(), $matches) > 0){ + $matches[1] = strtolower($matches[1]); + if($matches[1] === "false"){ + $ignoreCancelled = false; + }elseif($matches[1] === "true"){ + $ignoreCancelled = true; + } + } + $parameters = $method->getParameters(); + if(count($parameters) === 1 and $parameters[0]->getClass() instanceof \ReflectionClass and is_subclass_of($parameters[0]->getClass()->getName(), "pocketmine\\event\\Event")){ + $this->registerEvent($parameters[0]->getClass()->getName(), $listener, $priority, new MethodEventExecutor($method->getName()), $plugin, $ignoreCancelled); + } + } + } + } + + /** + * @param string $event Class name that extends Event + * @param Listener $listener + * @param int $priority + * @param EventExecutor $executor + * @param Plugin $plugin + * @param bool $ignoreCancelled + */ + public function registerEvent($event, Listener $listener, $priority, EventExecutor $executor, Plugin $plugin, $ignoreCancelled = false){ + if(!is_subclass_of($event, "pocketmine\\event\\Event")){ + trigger_error($event . " is not a valid Event", E_USER_WARNING); + + return; + } + if(!$plugin->isEnabled()){ + trigger_error("Plugin attempted to register " . $event . " while not enabled"); + + return; + } + + $this->getEventListeners($event)->register(new RegisteredListener($listener, $executor, $priority, $plugin, $ignoreCancelled)); + } + + /** + * @param string $event + * + * @return HandlerList + */ + private function getEventListeners($event){ + if($event::$handlerList === null){ + $event::$handlerList = new HandlerList(); + } + + return $event::$handlerList; + } + +} \ No newline at end of file diff --git a/src/pocketmine/plugin/RegisteredListener.php b/src/pocketmine/plugin/RegisteredListener.php new file mode 100644 index 000000000..b24e01e5e --- /dev/null +++ b/src/pocketmine/plugin/RegisteredListener.php @@ -0,0 +1,97 @@ +listener = $listener; + $this->priority = $priority; + $this->plugin = $plugin; + $this->executor = $executor; + $this->ignoreCancelled = $ignoreCancelled; + } + + /** + * @return Listener + */ + public function getListener(){ + return $this->listener; + } + + /** + * @return Plugin + */ + public function getPlugin(){ + return $this->plugin; + } + + /** + * @return int + */ + public function getPriority(){ + return $this->priority; + } + + /** + * @param Event $event + */ + public function callEvent(Event $event){ + if($event instanceof Cancellable and $event->isCancelled() and $this->isIgnoringCancelled()){ + return; + } + $this->executor->execute($this->listener, $event); + } + + /** + * @return bool + */ + public function isIgnoringCancelled(){ + return $this->ignoreCancelled === true; + } +} \ No newline at end of file diff --git a/src/pocketmine/pmf/LevelFormat.php b/src/pocketmine/pmf/LevelFormat.php new file mode 100644 index 000000000..4d48a27e0 --- /dev/null +++ b/src/pocketmine/pmf/LevelFormat.php @@ -0,0 +1,761 @@ +levelData[$index])){ + return false; + } + + return ($this->levelData[$index]); + } + + public function setData($index, $data){ + if(!isset($this->levelData[$index])){ + return false; + } + $this->levelData[$index] = $data; + + return true; + } + + public function closeLevel(){ + $this->chunks = null; + unset($this->chunks, $this->chunkChange, $this->chunkInfo, $this->level); + $this->close(); + } + + /** + * @param string $file + * @param bool|array $blank default false + */ + public function __construct($file, $blank = false){ + $this->chunks = array(); + $this->chunkChange = array(); + $this->chunkInfo = array(); + if(is_array($blank)){ + $this->create($file, 0); + $this->levelData = $blank; + $this->createBlank(); + $this->isLoaded = true; + }else{ + if($this->load($file) !== false){ + $this->parseInfo(); + if($this->parseLevel() === false){ + $this->isLoaded = false; + }else{ + $this->isLoaded = true; + } + }else{ + $this->isLoaded = false; + } + } + } + + public function saveData(){ + $this->levelData["version"] = self::VERSION; + @ftruncate($this->fp, 5); + $this->seek(5); + $this->write(chr($this->levelData["version"])); + $this->write(Utils::writeShort(strlen($this->levelData["name"])) . $this->levelData["name"]); + $this->write(Utils::writeInt($this->levelData["seed"])); + $this->write(Utils::writeInt($this->levelData["time"])); + $this->write(Utils::writeFloat($this->levelData["spawnX"])); + $this->write(Utils::writeFloat($this->levelData["spawnY"])); + $this->write(Utils::writeFloat($this->levelData["spawnZ"])); + $this->write(chr($this->levelData["height"])); + $this->write(Utils::writeShort(strlen($this->levelData["generator"])) . $this->levelData["generator"]); + $settings = serialize($this->levelData["generatorSettings"]); + $this->write(Utils::writeShort(strlen($settings)) . $settings); + $extra = zlib_encode($this->levelData["extra"], self::ZLIB_ENCODING, self::ZLIB_LEVEL); + $this->write(Utils::writeShort(strlen($extra)) . $extra); + } + + private function createBlank(){ + $this->saveData(); + @mkdir(dirname($this->file) . "/chunks/", 0755); + } + + protected function parseLevel(){ + if($this->getType() !== 0x00){ + return false; + } + $this->seek(5); + $this->levelData["version"] = ord($this->read(1)); + if($this->levelData["version"] > self::VERSION){ + console("[ERROR] New unsupported PMF Level format version #" . $this->levelData["version"] . ", current version is #" . self::VERSION); + + return false; + } + $this->levelData["name"] = $this->read(Utils::readShort($this->read(2), false)); + $this->levelData["seed"] = Utils::readInt($this->read(4)); + $this->levelData["time"] = Utils::readInt($this->read(4)); + $this->levelData["spawnX"] = Utils::readFloat($this->read(4)); + $this->levelData["spawnY"] = Utils::readFloat($this->read(4)); + $this->levelData["spawnZ"] = Utils::readFloat($this->read(4)); + if($this->levelData["version"] === 0){ + $this->read(1); + $this->levelData["height"] = ord($this->read(1)); + }else{ + $this->levelData["height"] = ord($this->read(1)); + if($this->levelData["height"] !== 8){ + return false; + } + $this->levelData["generator"] = $this->read(Utils::readShort($this->read(2), false)); + $this->levelData["generatorSettings"] = unserialize($this->read(Utils::readShort($this->read(2), false))); + + } + $this->levelData["extra"] = @zlib_decode($this->read(Utils::readShort($this->read(2), false))); + + $upgrade = false; + if($this->levelData["version"] === 0){ + $this->upgrade_From0_To1(); + $upgrade = true; + } + if($this->levelData["version"] === 1){ + $this->upgrade_From1_To2(); + $upgrade = true; + } + + if($upgrade === true){ + $this->saveData(); + } + } + + private function upgrade_From0_To1(){ + console("[NOTICE] Old PMF Level format version #0 detected, upgrading to version #1"); + for($index = 0; $index < 256; ++$index){ + $X = $index & 0x0F; + $Z = $index >> 4; + + $bitflags = Utils::readShort($this->read(2)); + $oldPath = dirname($this->file) . "/chunks/" . $Z . "." . $X . ".pmc"; + $chunkOld = gzopen($oldPath, "rb"); + $newPath = dirname($this->file) . "/chunks/" . (($X ^ $Z) & 0xff) . "/" . $Z . "." . $X . ".pmc"; + @mkdir(dirname($newPath)); + $chunkNew = gzopen($newPath, "wb1"); + gzwrite($chunkNew, chr($bitflags) . "\x00\x00\x00\x01"); + while(gzeof($chunkOld) === false){ + gzwrite($chunkNew, gzread($chunkOld, 65535)); + } + gzclose($chunkNew); + gzclose($chunkOld); + @unlink($oldPath); + } + $this->levelData["version"] = 1; + $this->levelData["generator"] = "default"; + $this->levelData["generatorSettings"] = ""; + } + + private function upgrade_From1_To2(){ + console("[NOTICE] Old PMF Level format version #1 detected, upgrading to version #2"); + $nbt = new Compound("", array( + new Enum("Entities", array()), + new Enum("TileEntities", array()) + )); + $nbt->Entities->setTagType(NBT::TAG_Compound); + $nbt->TileEntities->setTagType(NBT::TAG_Compound); + $nbtCodec = new NBT(NBT::BIG_ENDIAN); + $nbtCodec->setData($nbt); + $namedtag = $nbtCodec->write(); + $namedtag = Utils::writeInt(strlen($namedtag)) . $namedtag; + foreach(glob(dirname($this->file) . "/chunks/*/*.*.pmc") as $chunkFile){ + $oldChunk = zlib_decode(file_get_contents($chunkFile)); + $newChunk = substr($oldChunk, 0, 5); + $newChunk .= $namedtag; + $newChunk .= str_repeat("\x01", 256); //Biome indexes (all Plains) + $newChunk .= substr($oldChunk, 5); + file_put_contents($chunkFile, zlib_encode($newChunk, self::ZLIB_ENCODING, self::ZLIB_LEVEL)); + } + $this->levelData["version"] = 2; + } + + public static function getIndex($X, $Z){ + return ($Z << 16) | ($X < 0 ? (~--$X & 0x7fff) | 0x8000 : $X & 0x7fff); + } + + public static function getXZ($index, &$X = null, &$Z = null){ + $Z = $index >> 16; + $X = ($index & 0x8000) === 0x8000 ? -($index & 0x7fff) : $index & 0x7fff; + + return array($X, $Z); + } + + private function getChunkPath($X, $Z){ + return dirname($this->file) . "/chunks/" . (((int) $X ^ (int) $Z) & 0xff) . "/" . $Z . "." . $X . ".pmc"; + } + + public function generateChunk($X, $Z){ + $path = $this->getChunkPath($X, $Z); + if(!file_exists(dirname($path))){ + @mkdir(dirname($path), 0755); + } + $this->initCleanChunk($X, $Z); + if($this->level instanceof Level){ + $ret = $this->level->generateChunk($X, $Z); + $this->saveChunk($X, $Z); + $this->populateChunk($X - 1, $Z); + $this->populateChunk($X + 1, $Z); + $this->populateChunk($X, $Z - 1); + $this->populateChunk($X, $Z + 1); + $this->populateChunk($X + 1, $Z + 1); + $this->populateChunk($X + 1, $Z - 1); + $this->populateChunk($X - 1, $Z - 1); + $this->populateChunk($X - 1, $Z + 1); + + return $ret; + } + } + + public function populateChunk($X, $Z){ + if($this->level instanceof Level){ + if($this->isGenerating === 0 and + $this->isChunkLoaded($X, $Z) and + !$this->isPopulated($X, $Z) and + $this->isGenerated($X - 1, $Z) and + $this->isGenerated($X, $Z - 1) and + $this->isGenerated($X + 1, $Z) and + $this->isGenerated($X, $Z + 1) and + $this->isGenerated($X + 1, $Z + 1) and + $this->isGenerated($X - 1, $Z - 1) and + $this->isGenerated($X + 1, $Z - 1) and + $this->isGenerated($X - 1, $Z + 1) + ){ + $this->level->populateChunk($X, $Z); + $this->saveChunk($X, $Z); + } + } + } + + public function loadChunk($X, $Z){ + if($this->isChunkLoaded($X, $Z)){ + return true; + } + $index = self::getIndex($X, $Z); + $path = $this->getChunkPath($X, $Z); + if(!file_exists($path)){ + if($this->generateChunk($X, $Z) === false){ + return false; + } + if($this->isGenerating === 0){ + $this->populateChunk($X, $Z); + } + + return true; + } + + $chunk = file_get_contents($path); + if($chunk === false){ + return false; + } + $chunk = zlib_decode($chunk); + $offset = 0; + + $this->chunkInfo[$index] = array( + 0 => ord($chunk{0}), + 1 => Utils::readInt(substr($chunk, 1, 4)), + ); + $offset += 5; + $len = Utils::readInt(substr($chunk, $offset, 4)); + $offset += 4; + $nbt = new NBT(NBT::BIG_ENDIAN); + $nbt->read(substr($chunk, $offset, $len)); + $this->chunkInfo[$index][2] = $nbt->getData(); + $offset += $len; + $this->chunks[$index] = array(); + $this->chunkChange[$index] = array(-1 => false); + $this->chunkInfo[$index][3] = substr($chunk, $offset, 256); //Biome data + $offset += 256; + for($Y = 0; $Y < 8; ++$Y){ + if(($this->chunkInfo[$index][0] & (1 << $Y)) !== 0){ + // 4096 + 2048 + 2048, Block Data, Meta, Light + if(strlen($this->chunks[$index][$Y] = substr($chunk, $offset, 8192)) < 8192){ + console("[NOTICE] Empty corrupt chunk detected [$X,$Z,:$Y], recovering contents", true, true, 2); + $this->fillMiniChunk($X, $Z, $Y); + } + $offset += 8192; + }else{ + $this->chunks[$index][$Y] = false; + } + } + if($this->isGenerating === 0 and !$this->isPopulated($X, $Z)){ + $this->populateChunk($X, $Z); + } + + return true; + } + + public function unloadChunk($X, $Z, $save = true){ + $X = (int) $X; + $Z = (int) $Z; + if(!$this->isChunkLoaded($X, $Z)){ + return false; + }elseif($save !== false){ + $this->saveChunk($X, $Z); + } + $index = self::getIndex($X, $Z); + $this->chunks[$index] = null; + $this->chunkChange[$index] = null; + $this->chunkInfo[$index] = null; + unset($this->chunks[$index], $this->chunkChange[$index], $this->chunkInfo[$index]); + + return true; + } + + public function isChunkLoaded($X, $Z){ + $index = self::getIndex($X, $Z); + if(!isset($this->chunks[$index])){ + return false; + } + + return true; + } + + protected function cleanChunk($X, $Z){ + $index = self::getIndex($X, $Z); + if(isset($this->chunks[$index])){ + for($Y = 0; $Y < 8; ++$Y){ + if($this->chunks[$index][$Y] !== false and substr_count($this->chunks[$index][$Y], "\x00") === 8192){ + $this->chunks[$index][$Y] = false; + $this->chunkInfo[$index][0] &= ~(1 << $Y); + } + } + } + } + + public function isMiniChunkEmpty($X, $Z, $Y){ + $index = self::getIndex($X, $Z); + if(!isset($this->chunks[$index]) or $this->chunks[$index][$Y] === false){ + return true; + } + + return false; + } + + protected function fillMiniChunk($X, $Z, $Y){ + if($this->isChunkLoaded($X, $Z) === false){ + return false; + } + $index = self::getIndex($X, $Z); + $this->chunks[$index][$Y] = str_repeat("\x00", 8192); + $this->chunkChange[$index][-1] = true; + $this->chunkChange[$index][$Y] = 8192; + $this->chunkInfo[$index][0] |= 1 << $Y; + + return true; + } + + public function getMiniChunk($X, $Z, $Y){ + if($this->isChunkLoaded($X, $Z) === false and $this->loadChunk($X, $Z) === false){ + return str_repeat("\x00", 8192); + } + $index = self::getIndex($X, $Z); + if(!isset($this->chunks[$index][$Y]) or $this->chunks[$index][$Y] === false){ + return str_repeat("\x00", 8192); + } + + return $this->chunks[$index][$Y]; + } + + public function initCleanChunk($X, $Z){ + $index = self::getIndex($X, $Z); + if(!isset($this->chunks[$index])){ + $this->chunks[$index] = array( + 0 => false, + 1 => false, + 2 => false, + 3 => false, + 4 => false, + 5 => false, + 6 => false, + 7 => false, + ); + $this->chunkChange[$index] = array( + -1 => true, + 0 => 8192, + 1 => 8192, + 2 => 8192, + 3 => 8192, + 4 => 8192, + 5 => 8192, + 6 => 8192, + 7 => 8192, + ); + $nbt = new Compound("", array( + new Enum("Entities", array()), + new Enum("TileEntities", array()) + )); + $nbt->Entities->setTagType(NBT::TAG_Compound); + $nbt->TileEntities->setTagType(NBT::TAG_Compound); + $this->chunkInfo[$index] = array( + 0 => 0, + 1 => 0, + 2 => $nbt, + 3 => str_repeat("\x00", 256), + ); + } + } + + public function setMiniChunk($X, $Z, $Y, $data){ + if($this->isGenerating > 0){ + $this->initCleanChunk($X, $Z); + }elseif($this->isChunkLoaded($X, $Z) === false){ + $this->loadChunk($X, $Z); + } + if(strlen($data) !== 8192){ + return false; + } + $index = self::getIndex($X, $Z); + $this->chunks[$index][$Y] = (string) $data; + $this->chunkChange[$index][-1] = true; + $this->chunkChange[$index][$Y] = 8192; + $this->chunkInfo[$index][0] |= 1 << $Y; + + return true; + } + + public function getBlockID($x, $y, $z){ + if($y > 127 or $y < 0){ + return 0; + } + $X = $x >> 4; + $Z = $z >> 4; + $Y = $y >> 4; + $index = self::getIndex($X, $Z); + if(!isset($this->chunks[$index])){ + return 0; + } + $aX = $x - ($X << 4); + $aZ = $z - ($Z << 4); + $aY = $y - ($Y << 4); + $b = ord($this->chunks[$index][$Y]{(int) ($aY + ($aX << 5) + ($aZ << 9))}); + + return $b; + } + + + public function getBiome($x, $z){ + $X = $x >> 4; + $Z = $z >> 4; + $index = self::getIndex($X, $Z); + if(!isset($this->chunks[$index])){ + return 0; + } + $aX = $x - ($X << 4); + $aZ = $z - ($Z << 4); + + return ord($this->chunkInfo[$index][3]{$aX + ($aZ << 4)}); + } + + public function setBiome($x, $z, $biome){ + $X = $x >> 4; + $Z = $z >> 4; + $index = self::getIndex($X, $Z); + if(!isset($this->chunks[$index])){ + return false; + } + $aX = $x - ($X << 4); + $aZ = $z - ($Z << 4); + $this->chunkInfo[$index][3]{$aX + ($aZ << 4)} = chr((int) $biome); + + return true; + } + + public function setBlockID($x, $y, $z, $block){ + if($y > 127 or $y < 0){ + return false; + } + $X = $x >> 4; + $Z = $z >> 4; + $Y = $y >> 4; + $block &= 0xFF; + $index = self::getIndex($X, $Z); + if(!isset($this->chunks[$index])){ + return false; + } + $aX = $x - ($X << 4); + $aZ = $z - ($Z << 4); + $aY = $y - ($Y << 4); + $this->chunks[$index][$Y]{(int) ($aY + ($aX << 5) + ($aZ << 9))} = chr($block); + if(!isset($this->chunkChange[$index][$Y])){ + $this->chunkChange[$index][$Y] = 1; + }else{ + ++$this->chunkChange[$index][$Y]; + } + $this->chunkChange[$index][-1] = true; + + return true; + } + + public function getBlockDamage($x, $y, $z){ + if($y > 127 or $y < 0){ + return 0; + } + $X = $x >> 4; + $Z = $z >> 4; + $Y = $y >> 4; + $index = self::getIndex($X, $Z); + if(!isset($this->chunks[$index])){ + return 0; + } + $aX = $x - ($X << 4); + $aZ = $z - ($Z << 4); + $aY = $y - ($Y << 4); + $m = ord($this->chunks[$index][$Y]{(int) (($aY >> 1) + 16 + ($aX << 5) + ($aZ << 9))}); + if(($y & 1) === 0){ + $m = $m & 0x0F; + }else{ + $m = $m >> 4; + } + + return $m; + } + + public function setBlockDamage($x, $y, $z, $damage){ + if($y > 127 or $y < 0){ + return false; + } + $X = $x >> 4; + $Z = $z >> 4; + $Y = $y >> 4; + $damage &= 0x0F; + $index = self::getIndex($X, $Z); + if(!isset($this->chunks[$index])){ + return false; + } + $aX = $x - ($X << 4); + $aZ = $z - ($Z << 4); + $aY = $y - ($Y << 4); + $mindex = (int) (($aY >> 1) + 16 + ($aX << 5) + ($aZ << 9)); + $old_m = ord($this->chunks[$index][$Y]{$mindex}); + if(($y & 1) === 0){ + $m = ($old_m & 0xF0) | $damage; + }else{ + $m = ($damage << 4) | ($old_m & 0x0F); + } + + if($old_m != $m){ + $this->chunks[$index][$Y]{$mindex} = chr($m); + if(!isset($this->chunkChange[$index][$Y])){ + $this->chunkChange[$index][$Y] = 1; + }else{ + ++$this->chunkChange[$index][$Y]; + } + $this->chunkChange[$index][-1] = true; + + return true; + } + + return false; + } + + public function getBlock($x, $y, $z){ + $X = $x >> 4; + $Z = $z >> 4; + $Y = $y >> 4; + if($y < 0 or $y > 127){ + return array(0, 0); + } + $index = self::getIndex($X, $Z); + if(!isset($this->chunks[$index]) and $this->loadChunk($X, $Z) === false){ + return array(0, 0); + }elseif($this->chunks[$index][$Y] === false){ + return array(0, 0); + } + $aX = $x - ($X << 4); + $aZ = $z - ($Z << 4); + $aY = $y - ($Y << 4); + $b = ord($this->chunks[$index][$Y]{(int) ($aY + ($aX << 5) + ($aZ << 9))}); + $m = ord($this->chunks[$index][$Y]{(int) (($aY >> 1) + 16 + ($aX << 5) + ($aZ << 9))}); + if(($y & 1) === 0){ + $m = $m & 0x0F; + }else{ + $m = $m >> 4; + } + + return array($b, $m); + } + + public function setBlock($x, $y, $z, $block, $meta = 0){ + if($y > 127 or $y < 0){ + return false; + } + $X = $x >> 4; + $Z = $z >> 4; + $Y = $y >> 4; + $block &= 0xFF; + $meta &= 0x0F; + $index = self::getIndex($X, $Z); + if(!isset($this->chunks[$index]) and $this->loadChunk($X, $Z) === false){ + return false; + }elseif($this->chunks[$index][$Y] === false){ + $this->fillMiniChunk($X, $Z, $Y); + } + $aX = $x - ($X << 4); + $aZ = $z - ($Z << 4); + $aY = $y - ($Y << 4); + $bindex = (int) ($aY + ($aX << 5) + ($aZ << 9)); + $mindex = (int) (($aY >> 1) + 16 + ($aX << 5) + ($aZ << 9)); + $old_b = ord($this->chunks[$index][$Y]{$bindex}); + $old_m = ord($this->chunks[$index][$Y]{$mindex}); + if(($y & 1) === 0){ + $m = ($old_m & 0xF0) | $meta; + }else{ + $m = ($meta << 4) | ($old_m & 0x0F); + } + + if($old_b !== $block or $old_m !== $m){ + $this->chunks[$index][$Y]{$bindex} = chr($block); + $this->chunks[$index][$Y]{$mindex} = chr($m); + if(!isset($this->chunkChange[$index][$Y])){ + $this->chunkChange[$index][$Y] = 1; + }else{ + ++$this->chunkChange[$index][$Y]; + } + $this->chunkChange[$index][-1] = true; + + return true; + } + + return false; + } + + public function getChunkNBT($X, $Z){ + if(!$this->isChunkLoaded($X, $Z) and $this->loadChunk($X, $Z) === false){ + return false; + } + $index = self::getIndex($X, $Z); + + return $this->chunkInfo[$index][2]; + } + + public function setChunkNBT($X, $Z, Compound $nbt){ + if(!$this->isChunkLoaded($X, $Z) and $this->loadChunk($X, $Z) === false){ + return false; + } + $index = self::getIndex($X, $Z); + $this->chunkChange[$index][-1] = true; + $this->chunkInfo[$index][2] = $nbt; + } + + public function saveChunk($X, $Z, $force = false){ + $X = (int) $X; + $Z = (int) $Z; + if(!$this->isChunkLoaded($X, $Z)){ + return false; + } + $index = self::getIndex($X, $Z); + if($force !== true and (!isset($this->chunkChange[$index]) or $this->chunkChange[$index][-1] === false)){ //No changes in chunk + return true; + } + + $path = $this->getChunkPath($X, $Z); + if(!file_exists(dirname($path))){ + @mkdir(dirname($path), 0755); + } + $bitmap = 0; + $this->cleanChunk($X, $Z); + for($Y = 0; $Y < 8; ++$Y){ + if($this->chunks[$index][$Y] !== false and ((isset($this->chunkChange[$index][$Y]) and $this->chunkChange[$index][$Y] === 0) or !$this->isMiniChunkEmpty($X, $Z, $Y))){ + $bitmap |= 1 << $Y; + }else{ + $this->chunks[$index][$Y] = false; + } + $this->chunkChange[$index][$Y] = 0; + } + $this->chunkInfo[$index][0] = $bitmap; + $this->chunkChange[$index][-1] = false; + $chunk = ""; + $chunk .= chr($bitmap); + $chunk .= Utils::writeInt($this->chunkInfo[$index][1]); + $namedtag = new NBT(NBT::BIG_ENDIAN); + $namedtag->setData($this->chunkInfo[$index][2]); + $namedtag = $namedtag->write(); + $chunk .= Utils::writeInt(strlen($namedtag)) . $namedtag; + $chunk .= $this->chunkInfo[$index][3]; //biomes + for($Y = 0; $Y < 8; ++$Y){ + $t = 1 << $Y; + if(($bitmap & $t) === $t){ + $chunk .= $this->chunks[$index][$Y]; + } + } + file_put_contents($path, zlib_encode($chunk, self::ZLIB_ENCODING, self::ZLIB_LEVEL)); + + return true; + } + + public function setPopulated($X, $Z){ + if(!$this->isChunkLoaded($X, $Z)){ + return false; + } + $index = self::getIndex($X, $Z); + $this->chunkInfo[$index][1] |= 0b00000000000000000000000000000001; + } + + public function unsetPopulated($X, $Z){ + if(!$this->isChunkLoaded($X, $Z)){ + return false; + } + $index = self::getIndex($X, $Z); + $this->chunkInfo[$index][1] &= ~0b00000000000000000000000000000001; + } + + public function isPopulated($X, $Z){ + if(!$this->isChunkLoaded($X, $Z)){ + return false; + } + $index = self::getIndex($X, $Z); + + return ($this->chunkInfo[$index][1] & 0b00000000000000000000000000000001) > 0; + } + + public function isGenerated($X, $Z){ + return file_exists($this->getChunkPath($X, $Z)); + } + + public function doSaveRound($force = false){ + foreach($this->chunks as $index => $chunk){ + self::getXZ($index, $X, $Z); + $this->saveChunk($X, $Z, $force); + } + } + +} \ No newline at end of file diff --git a/src/pocketmine/pmf/PMF.php b/src/pocketmine/pmf/PMF.php new file mode 100644 index 000000000..8d009a6e3 --- /dev/null +++ b/src/pocketmine/pmf/PMF.php @@ -0,0 +1,143 @@ +create($file, $type, $version); + }else{ + if($this->load($file) !== true){ + $this->parseInfo(); + } + } + } + + public function getVersion(){ + return $this->version; + } + + public function getType(){ + return $this->type; + } + + public function load($file){ + $this->close(); + $this->file = $file; + if(($this->fp = @fopen($file, "c+b")) !== false){ + fseek($this->fp, 0, SEEK_END); + if(ftell($this->fp) >= 5){ //Header + 2 Bytes + @flock($this->fp, LOCK_EX); + + return true; + } + $this->close(); + } + + return false; + } + + public function parseInfo(){ + $this->seek(0); + if(fread($this->fp, 3) !== "PMF"){ + return false; + } + $this->version = ord($this->read(1)); + switch($this->version){ + case 0x01: + $this->type = ord($this->read(1)); + break; + default: + console("[ERROR] Tried loading non-supported PMF version " . $this->version . " on file " . $this->file); + + return false; + } + + return true; + } + + public function getFile(){ + return $this->file; + } + + public function close(){ + unset($this->version, $this->type, $this->file); + if(is_object($this->fp)){ + @flock($this->fp, LOCK_UN); + fclose($this->fp); + } + } + + public function create($file, $type, $version = PMF::VERSION){ + $this->file = $file; + @mkdir(dirname($this->file), 0755, true); + if(!is_resource($this->fp)){ + if(($this->fp = @fopen($file, "c+b")) === false){ + return false; + } + } + $this->seek(0); + $this->write("PMF" . chr((int) $version) . chr((int) $type)); + + return true; + } + + public function read($length){ + if($length <= 0){ + return ""; + } + if(is_resource($this->fp)){ + return fread($this->fp, (int) $length); + } + + return false; + } + + public function write($string, $length = false){ + if(is_resource($this->fp)){ + return ($length === false ? fwrite($this->fp, $string) : fwrite($this->fp, $string, $length)); + } + + return false; + } + + public function seek($offset, $whence = SEEK_SET){ + if(is_resource($this->fp)){ + return fseek($this->fp, (int) $offset, (int) $whence); + } + + return false; + } + +} \ No newline at end of file diff --git a/src/pocketmine/recipes/Crafting.php b/src/pocketmine/recipes/Crafting.php new file mode 100644 index 000000000..bd6843854 --- /dev/null +++ b/src/pocketmine/recipes/Crafting.php @@ -0,0 +1,334 @@ +CLAY_BLOCK:0x1", + "WOODEN_PLANKS:?x4=>WORKBENCH:0x1", + "GLOWSTONE_DUST:?x4=>GLOWSTONE_BLOCK:0x1", + "PUMPKIN:?x1,TORCH:?x1=>LIT_PUMPKIN:0x1", + "SNOWBALL:?x4=>SNOW_BLOCK:0x1", + "WOODEN_PLANKS:?x2=>STICK:0x4", + "COBBLESTONE:?x4=>STONECUTTER:0x1", + "WOOD:0x1=>WOODEN_PLANKS:0x4", + "WOOD:1x1=>WOODEN_PLANKS:1x4", + "WOOD:2x1=>WOODEN_PLANKS:2x4", + "WOOD:3x1=>WOODEN_PLANKS:3x4", + "WOOL:0x1,DYE:0x1=>WOOL:15x1", + "WOOL:0x1,DYE:1x1=>WOOL:14x1", + "WOOL:0x1,DYE:2x1=>WOOL:13x1", + "WOOL:0x1,DYE:3x1=>WOOL:12x1", + "WOOL:0x1,DYE:4x1=>WOOL:11x1", + "WOOL:0x1,DYE:5x1=>WOOL:10x1", + "WOOL:0x1,DYE:6x1=>WOOL:9x1", + "WOOL:0x1,DYE:7x1=>WOOL:8x1", + "WOOL:0x1,DYE:8x1=>WOOL:7x1", + "WOOL:0x1,DYE:9x1=>WOOL:6x1", + "WOOL:0x1,DYE:10x1=>WOOL:5x1", + "WOOL:0x1,DYE:11x1=>WOOL:4x1", + "WOOL:0x1,DYE:12x1=>WOOL:3x1", + "WOOL:0x1,DYE:13x1=>WOOL:2x1", + "WOOL:0x1,DYE:14x1=>WOOL:1x1", + "STRING:?x4=>WOOL:0x1", + + //Tools + "IRON_INGOT:?x1,FLINT:?x1=>FLINT_STEEL:0x1", + "IRON_INGOT:?x2=>SHEARS:0x1", + "COAL:0x1,STICK:?x1=>TORCH:0x4", + "COAL:1x1,STICK:?x1=>TORCH:0x4", + + //Food & protection + "MELON_SLICE:?x1=>MELON_SEEDS:0x1", + "PUMPKIN:?x1=>PUMPKIN_SEEDS:0x4", + "PUMPKIN:?x1,EGG:?x1,SUGAR:?x1=>PUMPKIN_PIE:0x1", + "BROWN_MUSHROOM:?x1,RED_MUSHROOM:?x1,BOWL:?x1=>MUSHROOM_STEW:0x1", + "SUGARCANE:?x1=>SUGAR:0x1", + "MELON_SLICE:?x1=>MELON_SEEDS:0x1", + "HAY_BALE:?x1=>WHEAT:0x9", + + //Items + "DIAMOND_BLOCK:?x1=>DIAMOND:0x9", + "GOLD_BLOCK:?x1=>GOLD_INGOT:0x9", + "IRON_BLOCK:?x1=>IRON_INGOT:0x9", + "LAPIS_BLOCK:?x1=>DYE:4x9", + "DANDELION:?x1=>DYE:11x2", + "BONE:?x1=>DYE:15x3", + "DYE:0x1,DYE:14x1=>DYE:3x2", + "DYE:0x1,DYE:1x1,DYE:11x1=>DYE:3x3", + "DYE:1x1,DYE:15x1=>DYE:9x2", + "DYE:1x1,DYE:11x1=>DYE:14x2", + "DYE:2x1,DYE:15x1=>DYE:10x2", + "DYE:4x1,DYE:15x1=>DYE:12x2", + "DYE:2x1,DYE:4x1=>DYE:6x2", + "DYE:1x1,DYE:4x1=>DYE:5x2", + "DYE:1x1,DYE:4x1,DYE:15x1=>DYE:13x3", + "BEETROOT:?x1=>DYE:1x1", + "DYE:15x1,DYE:1x2,DYE:4x1=>DYE:13x4", // + "DYE:5x1,DYE:9x1=>DYE:13x2", // + "DYE:0x1,DYE:15x1=>DYE:8x2", // + "DYE:0x1,DYE:15x2=>DYE:7x3", // + "DYE:0x1,DYE:8x1=>DYE:7x2", // + ); + + private static $big = array( //Probably means only craftable on crafting bench. Name it better! + //Building + "WOOL:?x3,WOODEN_PLANKS:?x3=>BED:0x1", + "WOODEN_PLANKS:?x8=>CHEST:0x1", + "STICK:?x6=>FENCE:0x2", + "STICK:?x4,WOODEN_PLANKS:?x2=>FENCE_GATE:0x1", + "COBBLESTONE:?x8=>FURNACE:0x1", + "GLASS:?x6=>GLASS_PANE:0x16", + "STICK:?x7=>LADDER:0x2", + "DIAMOND:?x3,IRON_INGOT:?x6=>NETHER_REACTOR:0x1", + "WOODEN_PLANKS:?x6=>TRAPDOOR:0x2", + "WOODEN_PLANKS:?x6=>WOODEN_DOOR:0x1", + "WOODEN_PLANKS:0x6=>WOODEN_STAIRS:0x4", + "WOODEN_PLANKS:0x3=>WOOD_SLAB:0x6", + "WOODEN_PLANKS:1x6=>SPRUCE_WOOD_STAIRS:0x4", + "WOODEN_PLANKS:1x3=>WOOD_SLAB:1x6", + "WOODEN_PLANKS:2x6=>BIRCH_WOOD_STAIRS:0x4", + "WOODEN_PLANKS:2x3=>BIRCH_WOOD_SLAB:2x6", + "WOODEN_PLANKS:3x6=>JUNGLE_WOOD_STAIRS:0x4", + "WOODEN_PLANKS:3x3=>JUNGLE_WOOD_SLAB:3x6", + + //Tools + "STICK:?x1,FEATHER:?x1,FLINT:?x1=>ARROW:0x4", + "STICK:?x3,STRING:?x3=>BOW:0x1", + "IRON_INGOT:?x3=>BUCKET:0x1", + "GOLD_INGOT:?x4,REDSTONE_DUST:?x1=>CLOCK:0x1", + "IRON_INGOT:?x4,REDSTONE_DUST:?x1=>COMPASS:0x1", + "DIAMOND:?x3,STICK:?x2=>DIAMOND_AXE:0x1", + "DIAMOND:?x2,STICK:?x2=>DIAMOND_HOE:0x1", + "DIAMOND:?x3,STICK:?x2=>DIAMOND_PICKAXE:0x1", + "DIAMOND:?x1,STICK:?x2=>DIAMOND_SHOVEL:0x1", + "DIAMOND:?x2,STICK:?x1=>DIAMOND_SWORD:0x1", + "GOLD_INGOT:?x3,STICK:?x2=>GOLD_AXE:0x1", + "GOLD_INGOT:?x2,STICK:?x2=>GOLD_HOE:0x1", + "GOLD_INGOT:?x3,STICK:?x2=>GOLD_PICKAXE:0x1", + "GOLD_INGOT:?x1,STICK:?x2=>GOLD_SHOVEL:0x1", + "GOLD_INGOT:?x2,STICK:?x1=>GOLD_SWORD:0x1", + "IRON_INGOT:?x3,STICK:?x2=>IRON_AXE:0x1", + "IRON_INGOT:?x2,STICK:?x2=>IRON_HOE:0x1", + "IRON_INGOT:?x3,STICK:?x2=>IRON_PICKAXE:0x1", + "IRON_INGOT:?x1,STICK:?x2=>IRON_SHOVEL:0x1", + "IRON_INGOT:?x2,STICK:?x1=>IRON_SWORD:0x1", + "COBBLESTONE:?x3,STICK:?x2=>STONE_AXE:0x1", + "COBBLESTONE:?x2,STICK:?x2=>STONE_HOE:0x1", + "COBBLESTONE:?x3,STICK:?x2=>STONE_PICKAXE:0x1", + "COBBLESTONE:?x1,STICK:?x2=>STONE_SHOVEL:0x1", + "COBBLESTONE:?x2,STICK:?x1=>STONE_SWORD:0x1", + "SAND:?x4,GUNPOWDER:?x5=>TNT:0x1", + "WOODEN_PLANKS:?x3,STICK:?x2=>WOODEN_AXE:0x1", + "WOODEN_PLANKS:?x2,STICK:?x2=>WOODEN_HOE:0x1", + "WOODEN_PLANKS:?x3,STICK:?x2=>WOODEN_PICKAXE:0x1", + "WOODEN_PLANKS:?x1,STICK:?x2=>WOODEN_SHOVEL:0x1", + "WOODEN_PLANKS:?x2,STICK:?x1=>WOODEN_SWORD:0x1", + + //Food & protection + "BEETROOT:?x4,BOWL:?x1=>BEETROOT_SOUP:0x1", + "WOODEN_PLANKS:?x3=>BOWL:0x1", + "WHEAT:?x3=>BREAD:0x1", + "WHEAT:?x3,BUCKET:1x3,EGG:?x1,SUGAR:?x2=>CAKE:0x1", + "DIAMOND:?x4=>DIAMOND_BOOTS:0x1", + "DIAMOND:?x8=>DIAMOND_CHESTPLATE:0x1", + "DIAMOND:?x5=>DIAMOND_HELMET:0x1", + "DIAMOND:?x7=>DIAMOND_LEGGINGS:0x1", + "GOLD_INGOT:?x4=>GOLD_BOOTS:0x1", + "GOLD_INGOT:?x8=>GOLD_CHESTPLATE:0x1", + "GOLD_INGOT:?x5=>GOLD_HELMET:0x1", + "GOLD_INGOT:?x7=>GOLD_LEGGINGS:0x1", + "IRON_INGOT:?x4=>IRON_BOOTS:0x1", + "IRON_INGOT:?x8=>IRON_CHESTPLATE:0x1", + "IRON_INGOT:?x5=>IRON_HELMET:0x1", + "IRON_INGOT:?x7=>IRON_LEGGINGS:0x1", + "LEATHER:?x4=>LEATHER_BOOTS:0x1", + "LEATHER:?x8=>LEATHER_TUNIC:0x1", + "LEATHER:?x5=>LEATHER_CAP:0x1", + "LEATHER:?x7=>LEATHER_PANTS:0x1", + "FIRE:?x4=>CHAIN_BOOTS:0x1", + "FIRE:?x8=>CHAIN_CHESTPLATE:0x1", + "FIRE:?x5=>CHAIN_HELMET:0x1", + "FIRE:?x7=>CHAIN_LEGGINGS:0x1", + + //Items + "DIAMOND:?x9=>DIAMOND_BLOCK:0x1", + "GOLD_INGOT:?x9=>GOLD_BLOCK:0x1", + "IRON_INGOT:?x9=>IRON_BLOCK:0x1", + "IRON_INGOT:?x5=>MINECART:0x1", + "WHEAT:?x9=>HAY_BALE:0x1", + "PAPER:?x3=>BOOK:0x1", + "WOODEN_PLANKS:?x6,BOOK:?x3=>BOOKSHELF:0x1", + "DYE:4x9=>LAPIS_BLOCK:0x1", + "WOOL:?x1,STICK:?x8=>PAINTING:0x1", + "SUGARCANE:?x3=>PAPER:0x1", + "WOODEN_PLANKS:?x6,STICK:?x1=>SIGN:0x1", + "IRON_INGOT:?x6=>IRON_BARS:0x16", + "COAL:0x9=>COAL_BLOCK:0x1", + "COAL_BLOCK:?x1=>COAL:0x9", + ); + + private static $stone = array( + "QUARTZ:?x4=>QUARTZ_BLOCK:0x1", + "BRICKS_BLOCK:?x6=>BRICK_STAIRS:0x4", + "BRICK:?x4=>BRICKS_BLOCK:0x1", + "BRICKS_BLOCK:?x3=>SLAB:4x6", + "SLAB:6x2=>QUARTZ_BLOCK:1x1", + "COBBLESTONE:?x3=>SLAB:3x6", + "COBBLESTONE:0x6=>STONE_WALL:0x6", + "MOSSY_STONE:0x6=>STONE_WALL:1x6", + "NETHER_BRICK:?x4=>NETHER_BRICKS:0x1", + "NETHER_BRICKS:?x6=>NETHER_BRICKS_STAIRS:0x4", + "QUARTZ_BLOCK:0x2=>QUARTZ_BLOCK:2x2", + "QUARTZ_BLOCK:?x3=>SLAB:6x6", + "SANDSTONE:0x6=>SANDSTONE_STAIRS:0x4", + "SAND:?x4=>SANDSTONE:0x1", + "SANDSTONE:0x4=>SANDSTONE:2x4", + "SLAB:1x2=>SANDSTONE:1x1", + "SANDSTONE:0x3=>SLAB:1x6", + "STONE_BRICK:?x6=>STONE_BRICK_STAIRS:0x4", + "STONE:?x4=>STONE_BRICK:0x4", + "STONE_BRICKS:?x3=>SLAB:5x6", + "STONE:?x3=>SLAB:0x6", + "COBBLESTONE:?x6=>COBBLESTONE_STAIRS:0x4", + ); + + private static $recipes = array(); + + private static function parseRecipe($recipe){ + $recipe = explode("=>", $recipe); + $recipeItems = array(); + foreach(explode(",", $recipe[0]) as $item){ + $item = explode("x", $item); + $id = explode(":", $item[0]); + $meta = array_pop($id); + $id = $id[0]; + + $it = Item::fromString($id); + $recipeItems[$it->getID()] = array($it->getID(), $meta === "?" ? false : intval($meta) & 0xFFFF, intval($item[1])); + } + ksort($recipeItems); + $item = explode("x", $recipe[1]); + $id = explode(":", $item[0]); + $meta = array_pop($id); + $id = $id[0]; + + $it = Item::fromString($id); + + $craftItem = array($it->getID(), intval($meta) & 0xFFFF, intval($item[1])); + + $recipeString = ""; + foreach($recipeItems as $item){ + $recipeString .= $item[0] . "x" . $item[2] . ","; + } + $recipeString = substr($recipeString, 0, -1) . "=>" . $craftItem[0] . "x" . $craftItem[2]; + + return array($recipeItems, $craftItem, $recipeString); + } + + public static function init(){ + $id = 1; + + self::$lookupTable[0] = array(); + foreach(self::$small as $recipe){ + $recipe = self::parseRecipe($recipe); + self::$recipes[$id] = $recipe; + if(!isset(self::$lookupTable[0][$recipe[2]])){ + self::$lookupTable[0][$recipe[2]] = array(); + } + self::$lookupTable[0][$recipe[2]][] = $id; + ++$id; + } + + self::$lookupTable[1] = array(); + foreach(self::$big as $recipe){ + $recipe = self::parseRecipe($recipe); + self::$recipes[$id] = $recipe; + if(!isset(self::$lookupTable[1][$recipe[2]])){ + self::$lookupTable[1][$recipe[2]] = array(); + } + self::$lookupTable[1][$recipe[2]][] = $id; + ++$id; + } + + self::$lookupTable[2] = array(); + foreach(self::$stone as $recipe){ + $recipe = self::parseRecipe($recipe); + self::$recipes[$id] = $recipe; + if(!isset(self::$lookupTable[2][$recipe[2]])){ + self::$lookupTable[2][$recipe[2]] = array(); + } + self::$lookupTable[2][$recipe[2]][] = $id; + ++$id; + } + + } + + public static function canCraft(array $craftItem, array $recipeItems, $type){ + ksort($recipeItems); + $recipeString = ""; + foreach($recipeItems as $item){ + $recipeString .= $item[0] . "x" . $item[2] . ","; + } + $recipeString = substr($recipeString, 0, -1) . "=>" . $craftItem[0] . "x" . $craftItem[2]; + + $continue = true; + + if(isset(self::$lookupTable[$type][$recipeString])){ + foreach(self::$lookupTable[$type][$recipeString] as $id){ + $continue = true; + $recipe = self::$recipes[$id]; + foreach($recipe[0] as $item){ + if(!isset($recipeItems[$item[0]])){ + $continue = false; + break; + } + $oitem = $recipeItems[$item[0]]; + if(($oitem[1] !== $item[1] and $item[1] !== false) or $oitem[2] !== $item[2]){ + $continue = false; + break; + } + } + if($continue === false or $craftItem[0] !== $recipe[1][0] or $recipe[1][1] !== $recipe[1][1] or $recipe[1][2] !== $recipe[1][2]){ + $continue = false; + continue; + } + $continue = $recipe; + break; + } + }else{ + return true; + } + + return $continue; + } + +} diff --git a/src/pocketmine/recipes/Fuel.php b/src/pocketmine/recipes/Fuel.php new file mode 100644 index 000000000..3f0663b0c --- /dev/null +++ b/src/pocketmine/recipes/Fuel.php @@ -0,0 +1,53 @@ + 80, + Item::COAL_BLOCK => 800, + Item::TRUNK => 15, + Item::WOODEN_PLANKS => 15, + Item::SAPLING => 5, + Item::WOODEN_AXE => 10, + Item::WOODEN_PICKAXE => 10, + Item::WOODEN_SWORD => 10, + Item::WOODEN_SHOVEL => 10, + Item::WOODEN_HOE => 10, + Item::STICK => 5, + Item::FENCE => 15, + Item::FENCE_GATE => 15, + Item::WOODEN_STAIRS => 15, + Item::SPRUCE_WOOD_STAIRS => 15, + Item::BIRCH_WOOD_STAIRS => 15, + Item::JUNGLE_WOOD_STAIRS => 15, + Item::TRAPDOOR => 15, + Item::WORKBENCH => 15, + Item::BOOKSHELF => 15, + Item::CHEST => 15, + Item::BUCKET => 1000, + + ); + +} \ No newline at end of file diff --git a/src/pocketmine/recipes/Smelt.php b/src/pocketmine/recipes/Smelt.php new file mode 100644 index 000000000..abbdd5445 --- /dev/null +++ b/src/pocketmine/recipes/Smelt.php @@ -0,0 +1,44 @@ + array(Item::STONE, 0), + Item::SAND => array(Item::GLASS, 0), + Item::TRUNK => array(Item::COAL, 1), //Charcoal + Item::GOLD_ORE => array(Item::GOLD_INGOT, 0), + Item::IRON_ORE => array(Item::IRON_INGOT, 0), + Item::NETHERRACK => array(Item::NETHER_BRICK, 0), + Item::RAW_PORKCHOP => array(Item::COOKED_PORKCHOP, 0), + Item::CLAY => array(Item::BRICK, 0), + //Item::RAW_FISH => array(Item::COOKED_FISH, 0), + Item::CACTUS => array(Item::DYE, 2), + Item::RED_MUSHROOM => array(Item::DYE, 1), + Item::RAW_BEEF => array(Item::STEAK, 0), + Item::RAW_CHICKEN => array(Item::COOKED_CHICKEN, 0), + Item::RED_MUSHROOM => array(Item::DYE, 1), + Item::POTATO => array(Item::BAKED_POTATO, 0), + ); +} \ No newline at end of file diff --git a/src/pocketmine/scheduler/CallbackTask.php b/src/pocketmine/scheduler/CallbackTask.php new file mode 100644 index 000000000..1cfc65804 --- /dev/null +++ b/src/pocketmine/scheduler/CallbackTask.php @@ -0,0 +1,52 @@ +callable = $callable; + $this->args = $args; + $this->args[] = $this; + } + + public function onRun($currentTicks){ + call_user_func_array($this->callable, $this->args); + } + +} diff --git a/src/pocketmine/scheduler/PluginTask.php b/src/pocketmine/scheduler/PluginTask.php new file mode 100644 index 000000000..124ca27a6 --- /dev/null +++ b/src/pocketmine/scheduler/PluginTask.php @@ -0,0 +1,48 @@ +owner = $owner; + } + + /** + * @return Plugin + */ + public final function getOwner(){ + return $this->owner; + } + +} diff --git a/src/pocketmine/scheduler/ServerScheduler.php b/src/pocketmine/scheduler/ServerScheduler.php new file mode 100644 index 000000000..528ee22fd --- /dev/null +++ b/src/pocketmine/scheduler/ServerScheduler.php @@ -0,0 +1,197 @@ + + */ + protected $queue; + + /** + * @var TaskHandler[] + */ + protected $tasks = array(); + + /** @var int */ + private $ids = 1; + + /** @var int */ + protected $currentTick = 0; + + public function __construct(){ + $this->queue = new TaskQueue(); + } + + /** + * @param Task $task + * + * @return null|TaskHandler + */ + public function scheduleTask(Task $task){ + return $this->addTask($task, -1, -1); + } + + /** + * @param Task $task + * @param int $delay + * + * @return null|TaskHandler + */ + public function scheduleDelayedTask(Task $task, $delay){ + return $this->addTask($task, (int) $delay, -1); + } + + /** + * @param Task $task + * @param int $period + * + * @return null|TaskHandler + */ + public function scheduleRepeatingTask(Task $task, $period){ + return $this->addTask($task, -1, (int) $period); + } + + /** + * @param Task $task + * @param int $delay + * @param int $period + * + * @return null|TaskHandler + */ + public function scheduleDelayedRepeatingTask(Task $task, $delay, $period){ + return $this->addTask($task, (int) $delay, (int) $period); + } + + /** + * @param int $taskId + */ + public function cancelTask($taskId){ + if(isset($this->tasks[$taskId])){ + $this->tasks[$taskId]->cancel(); + unset($this->tasks[$taskId]); + } + } + + /** + * @param Plugin $plugin + */ + public function cancelTasks(Plugin $plugin){ + foreach($this->tasks as $taskId => $task){ + if($task->getTask() instanceof PluginTask){ + $task->cancel(); + unset($this->tasks[$taskId]); + } + } + } + + public function cancelAllTasks(){ + foreach($this->tasks as $task){ + $task->cancel(); + } + $this->tasks = array(); + } + + /** + * @param int $taskId + * + * @return bool + */ + public function isQueued($taskId){ + return isset($this->tasks[$taskId]); + } + + private function addTask(Task $task, $delay, $period){ + if($task instanceof PluginTask and !$task->getOwner()->isEnabled()){ + trigger_error("Plugin attempted to register a task while disabled", E_USER_WARNING); + + return null; + } + + if($delay <= 0){ + $delay = -1; + } + + if($period === 1){ + $period = 1; + }elseif($period < -1){ + $period = -1; + } + + return $this->handle(new TaskHandler($task, $this->nextId(), $delay, $period)); + } + + private function handle(TaskHandler $handler){ + if($handler->isDelayed()){ + $nextRun = $this->currentTick + $handler->getDelay(); + }else{ + $nextRun = $this->currentTick; + } + + $handler->setNextRun($nextRun); + $this->tasks[$handler->getTaskId()] = $handler; + $this->queue->insert($handler, $nextRun); + + return $handler; + } + + /** + * @param int $currentTick + */ + public function mainThreadHeartbeat($currentTick){ + $this->currentTick = $currentTick; + while($this->isReady($this->currentTick)){ + $task = $this->queue->extract(); + if($task->isCancelled()){ + unset($this->tasks[$task->getTaskId()]); + continue; + }else{ + $task->run($this->currentTick); + } + if($task->isRepeating()){ + $task->setNextRun($this->currentTick + $task->getPeriod()); + $this->queue->insert($task, $this->currentTick + $task->getPeriod()); + }else{ + $task->remove(); + unset($this->tasks[$task->getTaskId()]); + } + } + } + + private function isReady($currentTicks){ + return count($this->tasks) > 0 and $this->queue->current()->getNextRun() <= $currentTicks; + } + + /** + * @return int + */ + private function nextId(){ + return $this->ids++; + } + +} diff --git a/src/pocketmine/scheduler/Task.php b/src/pocketmine/scheduler/Task.php new file mode 100644 index 000000000..0b4d2bded --- /dev/null +++ b/src/pocketmine/scheduler/Task.php @@ -0,0 +1,72 @@ +taskHandler; + } + + /** + * @return int + */ + public final function getTaskId(){ + if($this->taskHandler !== null){ + return $this->taskHandler->getTaskId(); + } + + return -1; + } + + /** + * @param TaskHandler $taskHandler + */ + public final function setHandler($taskHandler){ + if($this->taskHandler === null or $taskHandler === null){ + $this->taskHandler = $taskHandler; + } + } + + /** + * Actions to execute when run + * + * @param $currentTick + * + * @return void + */ + public abstract function onRun($currentTick); + + /** + * Actions to execute if the Task is cancelled + */ + public function onCancel(){ + + } + +} diff --git a/src/pocketmine/scheduler/TaskHandler.php b/src/pocketmine/scheduler/TaskHandler.php new file mode 100644 index 000000000..4cd09563b --- /dev/null +++ b/src/pocketmine/scheduler/TaskHandler.php @@ -0,0 +1,138 @@ +task = $task; + $this->taskId = $taskId; + $this->delay = $delay; + $this->period = $period; + } + + /** + * @return bool + */ + public function isCancelled(){ + return $this->cancelled === true; + } + + /** + * @return int + */ + public function getNextRun(){ + return $this->nextRun; + } + + /** + * @param int $ticks + */ + public function setNextRun($ticks){ + $this->nextRun = $ticks; + } + + /** + * @return int + */ + public function getTaskId(){ + return $this->taskId; + } + + /** + * @return Task + */ + public function getTask(){ + return $this->task; + } + + /** + * @return int + */ + public function getDelay(){ + return $this->delay; + } + + /** + * @return bool + */ + public function isDelayed(){ + return $this->delay > 0; + } + + /** + * @return bool + */ + public function isRepeating(){ + return $this->period > 0; + } + + /** + * @return int + */ + public function getPeriod(){ + return $this->period; + } + + public function cancel(){ + if(!$this->isCancelled()){ + $this->task->onCancel(); + } + $this->remove(); + } + + public function remove(){ + $this->cancelled = true; + $this->task->setHandler(null); + } + + /** + * @param int $currentTick + */ + public function run($currentTick){ + $this->task->onRun($currentTick); + } +} \ No newline at end of file diff --git a/src/pocketmine/scheduler/TaskQueue.php b/src/pocketmine/scheduler/TaskQueue.php new file mode 100644 index 000000000..d872f2610 --- /dev/null +++ b/src/pocketmine/scheduler/TaskQueue.php @@ -0,0 +1,29 @@ +ticksPerSecond = (int) $ticksPerSecond; + $this->sleepTime = (int) (1000000 / $this->ticksPerSecond); + $this->tickMeasure = $this->sleepTime; + $this->start(PTHREADS_INHERIT_ALL & ~PTHREADS_INHERIT_CLASSES); + } + + /** + * Returns true if clear to run tick + * + * @return bool + */ + public function hasTick(){ + return $this->synchronized(function (){ + $hasTick = $this->hasTick; + $this->hasTick = false; + + return $hasTick === true; + }); + } + + public function doTick(){ + $this->notify(); + } + + /** + * @return float + */ + public function getTPS(){ + return $this->synchronized(function (){ + return round(($this->sleepTime / $this->tickMeasure) * $this->ticksPerSecond, 2); + }); + } + + public function run(){ + $tickTime = microtime(true); + $this->hasTick = true; + while(true){ + $this->synchronized(function (){ + $this->hasTick = true; + $this->wait(); + $this->hasTick = false; + }); + + $this->tickMeasure = (int) ((($time = microtime(true)) - $tickTime) * 1000000); + $tickTime = $time; + usleep($this->sleepTime - 100); //Remove a few ms for processing + } + } +} \ No newline at end of file diff --git a/src/pocketmine/tile/Chest.php b/src/pocketmine/tile/Chest.php new file mode 100644 index 000000000..aa0a8ab16 --- /dev/null +++ b/src/pocketmine/tile/Chest.php @@ -0,0 +1,129 @@ +namedtag->pairx) or !isset($this->namedtag->pairz)){ + return false; + } + + return true; + } + + public function getPair(){ + if($this->isPaired()){ + return $this->level->getTile(new Vector3((int) $this->namedtag->pairx, $this->y, (int) $this->namedtag->pairz)); + } + + return false; + } + + public function pairWith(Tile $tile){ + if($this->isPaired() or $tile->isPaired()){ + return false; + } + + $this->namedtag->pairx = $tile->x; + $this->namedtag->pairz = $tile->z; + + $tile->namedtag->pairx = $this->x; + $tile->namedtag->pairz = $this->z; + + $this->spawnToAll(); + $tile->spawnToAll(); + $this->server->handle("tile.update", $this); + $this->server->handle("tile.update", $tile); + + return true; + } + + public function unpair(){ + if(!$this->isPaired()){ + return false; + } + + $tile = $this->getPair(); + unset($this->namedtag->pairx, $this->namedtag->pairz, $tile->namedtag->pairx, $tile->namedtag->pairz); + + $this->spawnToAll(); + $this->server->handle("tile.update", $this); + if($tile instanceof Chest){ + $tile->spawnToAll(); + $this->server->handle("tile.update", $tile); + } + + return true; + } + + public function spawnTo(Player $player){ + if($this->closed){ + return false; + } + + $nbt = new NBT(NBT::LITTLE_ENDIAN); + if($this->isPaired()){ + $nbt->setData(new Compound("", array( + new String("id", Tile::CHEST), + new Int("x", (int) $this->x), + new Int("y", (int) $this->y), + new Int("z", (int) $this->z), + new Int("pairx", (int) $this->namedtag->pairx), + new Int("pairz", (int) $this->namedtag->pairz) + ))); + }else{ + $nbt->setData(new Compound("", array( + new String("id", Tile::CHEST), + new Int("x", (int) $this->x), + new Int("y", (int) $this->y), + new Int("z", (int) $this->z) + ))); + } + + $pk = new EntityDataPacket; + $pk->x = $this->x; + $pk->y = $this->y; + $pk->z = $this->z; + $pk->namedtag = $nbt->write(); + $player->dataPacket($pk); + + return true; + } +} \ No newline at end of file diff --git a/src/pocketmine/tile/Container.php b/src/pocketmine/tile/Container.php new file mode 100644 index 000000000..191ce5161 --- /dev/null +++ b/src/pocketmine/tile/Container.php @@ -0,0 +1,195 @@ +windowCnt++; + $player->windowCnt = $id = max(2, $player->windowCnt % 99); + if(($pair = $this->getPair()) !== false){ + if(($pair->x + ($pair->z << 13)) > ($this->x + ($this->z << 13))){ //Order them correctly + $player->windows[$id] = array( + $pair, + $this + ); + }else{ + $player->windows[$id] = array( + $this, + $pair + ); + } + }else{ + $player->windows[$id] = $this; + } + + $pk = new ContainerOpenPacket(); + $pk->windowid = $id; + $pk->type = 0; + $pk->slots = is_array($player->windows[$id]) ? Chest::SLOTS << 1 : Chest::SLOTS; + $pk->x = $this->x; + $pk->y = $this->y; + $pk->z = $this->z; + $player->dataPacket($pk); + $slots = array(); + + if(is_array($player->windows[$id])){ + $all = $this->level->getPlayers(); + foreach($player->windows[$id] as $ob){ + $pk = new TileEventPacket(); + $pk->x = $ob->x; + $pk->y = $ob->y; + $pk->z = $ob->z; + $pk->case1 = 1; + $pk->case2 = 2; + Player::broadcastPacket($all, $pk); + for($s = 0; $s < Chest::SLOTS; ++$s){ + $slot = $ob->getSlot($s); + if($slot->getID() > Item::AIR and $slot->getCount() > 0){ + $slots[] = $slot; + }else{ + $slots[] = Item::get(Item::AIR, 0, 0); + } + } + } + }else{ + $pk = new TileEventPacket(); + $pk->x = $this->x; + $pk->y = $this->y; + $pk->z = $this->z; + $pk->case1 = 1; + $pk->case2 = 2; + Player::broadcastPacket($this->level->getPlayers(), $pk); + for($s = 0; $s < Chest::SLOTS; ++$s){ + $slot = $this->getSlot($s); + if($slot->getID() > Item::AIR and $slot->getCount() > 0){ + $slots[] = $slot; + }else{ + $slots[] = Item::get(Item::AIR, 0, 0); + } + } + } + + $pk = new ContainerSetContentPacket(); + $pk->windowid = $id; + $pk->slots = $slots; + $player->dataPacket($pk); + + return true; + }elseif($this instanceof Furnace){ + $player->windowCnt++; + $player->windowCnt = $id = max(2, $player->windowCnt % 99); + $player->windows[$id] = $this; + + $pk = new ContainerOpenPacket(); + $pk->windowid = $id; + $pk->type = 2; + $pk->slots = Furnace::SLOTS; + $pk->x = $this->x; + $pk->y = $this->y; + $pk->z = $this->z; + $player->dataPacket($pk); + + $slots = array(); + for($s = 0; $s < Furnace::SLOTS; ++$s){ + $slot = $this->getSlot($s); + if($slot->getID() > Item::AIR and $slot->getCount() > 0){ + $slots[] = $slot; + }else{ + $slots[] = Item::get(Item::AIR, 0, 0); + } + } + $pk = new ContainerSetContentPacket(); + $pk->windowid = $id; + $pk->slots = $slots; + $player->dataPacket($pk); + + return true; + } + } + + public function getSlotIndex($s){ + foreach($this->namedtag->Items as $i => $slot){ + if($slot["Slot"] === $s){ + return $i; + } + } + + return -1; + } + + public function getSlot($s){ + $i = $this->getSlotIndex($s); + if($i === false or $i < 0){ + return Item::get(Item::AIR, 0, 0); + }else{ + return Item::get($this->namedtag->Items[$i]["id"], $this->namedtag->Items[$i]["Damage"], $this->namedtag->Items[$i]["Count"]); + } + } + + public function setSlot($s, Item $item, $update = true, $offset = 0){ + $i = $this->getSlotIndex($s); + if($i === false){ + return false; + } + Server::getInstance()->getPluginManager()->callEvent($ev = new TileInventoryChangeEvent($this, $this->getSlot($s), $item, $s, $offset)); + if($ev->isCancelled()){ + return false; + } + + $item = $ev->getNewItem(); + $d = new Compound(false, array( + new Byte("Count", $item->getCount()), + new Byte("Slot", $s), + new Short("id", $item->getID()), + new Short("Damage", $item->getMetadata()), + )); + + if($item->getID() === Item::AIR or $item->getCount() <= 0){ + if($i >= 0){ + unset($this->namedtag->Items[$i]); + } + }elseif($i < 0){ + $this->namedtag->Items[] = $d; + }else{ + $this->namedtag->Items[$i] = $d; + } + + if($update === true){ + $this->scheduleUpdate(); + } + + return true; + } +} \ No newline at end of file diff --git a/src/pocketmine/tile/Furnace.php b/src/pocketmine/tile/Furnace.php new file mode 100644 index 000000000..ca0b35864 --- /dev/null +++ b/src/pocketmine/tile/Furnace.php @@ -0,0 +1,118 @@ +namedtag->BurnTime) or $this->namedtag->BurnTime < 0){ + $this->namedtag->BurnTime = 0; + } + if(!isset($this->namedtag->CookTime) or $this->namedtag->CookTime < 0 or ($this->namedtag->BurnTime === 0 and $this->namedtag->CookTime > 0)){ + $this->namedtag->CookTime = 0; + } + if(!isset($this->namedtag->MaxTime)){ + $this->namedtag->MaxTime = $this->namedtag->BurnTime; + $this->namedtag->BurnTicks = 0; + } + if($this->namedtag->BurnTime > 0){ + $this->scheduleUpdate(); + } + } + + public function onUpdate(){ + if($this->closed === true){ + return false; + } + + $ret = false; + + $fuel = $this->getSlot(1); + $raw = $this->getSlot(0); + $product = $this->getSlot(2); + $smelt = $raw->getSmeltItem(); + $canSmelt = ($smelt !== false and $raw->getCount() > 0 and (($product->getID() === $smelt->getID() and $product->getMetadata() === $smelt->getMetadata() and $product->getCount() < $product->getMaxStackSize()) or $product->getID() === Item::AIR)); + if($this->namedtag->BurnTime <= 0 and $canSmelt and $fuel->getFuelTime() !== false and $fuel->getCount() > 0){ + $this->lastUpdate = microtime(true); + $this->namedtag->MaxTime = $this->namedtag->BurnTime = floor($fuel->getFuelTime() * 20); + $this->namedtag->BurnTicks = 0; + $fuel->setCount($fuel->getCount() - 1); + if($fuel->getCount() === 0){ + $fuel = Item::get(Item::AIR, 0, 0); + } + $this->setSlot(1, $fuel, false); + $current = $this->level->getBlock($this); + if($current->getID() === Item::FURNACE){ + $this->level->setBlock($this, Block::get(Item::BURNING_FURNACE, $current->getMetadata()), true, false, true); + } + } + if($this->namedtag->BurnTime > 0){ + $ticks = (microtime(true) - $this->lastUpdate) * 20; + $this->namedtag->BurnTime -= $ticks; + $this->namedtag->BurnTicks = ceil(($this->namedtag->BurnTime / $this->namedtag->MaxTime) * 200); + if($smelt !== false and $canSmelt){ + $this->namedtag->CookTime += $ticks; + if($this->namedtag->CookTime >= 200){ //10 seconds + $product = Item::get($smelt->getID(), $smelt->getMetadata(), $product->getCount() + 1); + $this->setSlot(2, $product, false); + $raw->setCount($raw->getCount() - 1); + if($raw->getCount() === 0){ + $raw = Item::get(Item::AIR, 0, 0); + } + $this->setSlot(0, $raw, false); + $this->namedtag->CookTime -= 200; + } + }elseif($this->namedtag->BurnTime <= 0){ + $this->namedtag->BurnTime = 0; + $this->namedtag->CookTime = 0; + $this->namedtag->BurnTicks = 0; + }else{ + $this->namedtag->CookTime = 0; + } + $ret = true; + }else{ + $current = $this->level->getBlock($this); + if($current->getID() === Item::BURNING_FURNACE){ + $this->level->setBlock($this, Block::get(Item::FURNACE, $current->getMetadata()), true, false, true); + } + $this->namedtag->CookTime = 0; + $this->namedtag->BurnTime = 0; + $this->namedtag->BurnTicks = 0; + } + + + $this->server->handle("tile.update", $this); + $this->lastUpdate = microtime(true); + + return $ret; + } +} \ No newline at end of file diff --git a/src/pocketmine/tile/Sign.php b/src/pocketmine/tile/Sign.php new file mode 100644 index 000000000..76b5fe0ec --- /dev/null +++ b/src/pocketmine/tile/Sign.php @@ -0,0 +1,85 @@ +namedtag->Text1 = $line1; + $this->namedtag->Text2 = $line2; + $this->namedtag->Text3 = $line3; + $this->namedtag->Text4 = $line4; + $this->spawnToAll(); + $this->server->handle("tile.update", $this); + + return true; + } + + public function getText(){ + return array( + $this->namedtag->Text1, + $this->namedtag->Text2, + $this->namedtag->Text3, + $this->namedtag->Text4 + ); + } + + public function spawnTo(Player $player){ + if($this->closed){ + return false; + } + + $nbt = new NBT(NBT::LITTLE_ENDIAN); + $nbt->setData(new Compound("", array( + new String("Text1", $this->namedtag->Text1), + new String("Text2", $this->namedtag->Text2), + new String("Text3", $this->namedtag->Text3), + new String("Text4", $this->namedtag->Text4), + new String("id", Tile::SIGN), + new Int("x", (int) $this->x), + new Int("y", (int) $this->y), + new Int("z", (int) $this->z) + ))); + $pk = new EntityDataPacket; + $pk->x = $this->x; + $pk->y = $this->y; + $pk->z = $this->z; + $pk->namedtag = $nbt->write(); + $player->dataPacket($pk); + + return true; + } + +} diff --git a/src/pocketmine/tile/Spawnable.php b/src/pocketmine/tile/Spawnable.php new file mode 100644 index 000000000..9bf873066 --- /dev/null +++ b/src/pocketmine/tile/Spawnable.php @@ -0,0 +1,36 @@ +level->getPlayers() as $player){ + if($player->eid !== false or $player->spawned !== true){ + $this->spawnTo($player); + } + } + } +} \ No newline at end of file diff --git a/src/pocketmine/tile/Tile.php b/src/pocketmine/tile/Tile.php new file mode 100644 index 000000000..2b73db6f4 --- /dev/null +++ b/src/pocketmine/tile/Tile.php @@ -0,0 +1,122 @@ +id; + } + + + public function __construct(Level $level, Compound $nbt){ + $this->server = Server::getInstance(); + $this->level = $level; + $this->namedtag = $nbt; + $this->closed = false; + $this->name = ""; + $this->lastUpdate = microtime(true); + $this->id = Tile::$tileCount++; + Tile::$list[$this->id] = $this; + $this->x = (int) $this->namedtag->x; + $this->y = (int) $this->namedtag->y; + $this->z = (int) $this->namedtag->z; + + $index = LevelFormat::getIndex($this->x >> 4, $this->z >> 4); + $this->chunkIndex = $index; + $this->level->tiles[$this->id] = $this; + $this->level->chunkTiles[$this->chunkIndex][$this->id] = $this; + } + + public function onUpdate(){ + return false; + } + + public final function scheduleUpdate(){ + Tile::$needUpdate[$this->id] = $this; + } + + public function close(){ + if($this->closed === false){ + $this->closed = true; + unset(Tile::$needUpdate[$this->id]); + unset($this->level->tiles[$this->id]); + unset($this->level->chunkTiles[$this->chunkIndex][$this->id]); + unset(Tile::$list[$this->id]); + } + } + + public function __destruct(){ + $this->close(); + } + + public function getName(){ + return $this->name; + } + +} diff --git a/src/pocketmine/utils/Cache.php b/src/pocketmine/utils/Cache.php new file mode 100644 index 000000000..8733a31c2 --- /dev/null +++ b/src/pocketmine/utils/Cache.php @@ -0,0 +1,84 @@ + $data){ + if($data[1] < $time){ + unset(self::$cached[$index]); + } + } + } + +} \ No newline at end of file diff --git a/src/pocketmine/utils/Config.php b/src/pocketmine/utils/Config.php new file mode 100644 index 000000000..4b9245c8f --- /dev/null +++ b/src/pocketmine/utils/Config.php @@ -0,0 +1,385 @@ + Config::PROPERTIES, + "cnf" => Config::CNF, + "conf" => Config::CNF, + "config" => Config::CNF, + "json" => Config::JSON, + "js" => Config::JSON, + "yml" => Config::YAML, + "yaml" => Config::YAML, + //"export" => Config::EXPORT, + //"xport" => Config::EXPORT, + "sl" => Config::SERIALIZED, + "serialize" => Config::SERIALIZED, + "txt" => Config::ENUM, + "list" => Config::ENUM, + "enum" => Config::ENUM, + ); + + /** + * @param string $file Path of the file to be loaded + * @param int $type Config type to load, -1 by default (detect) + * @param array $default Array with the default values, will be set if not existent + * @param null &$correct Sets correct to true if everything has been loaded correctly + */ + public function __construct($file, $type = Config::DETECT, $default = array(), &$correct = null){ + $this->load($file, $type, $default); + $correct = $this->correct; + } + + /** + * Removes all the changes in memory and loads the file again + */ + public function reload(){ + unset($this->config); + unset($this->correct); + unset($this->type); + $this->load($this->file); + } + + /** + * @param $str + * + * @return mixed + */ + public static function fixYAMLIndexes($str){ + return preg_replace("#^([ ]*)([a-zA-Z_]{1}[^\:]*)\:#m", "$1\"$2\":", $str); + } + + /** + * @param $file + * @param int $type + * @param array $default + * + * @return bool + */ + public function load($file, $type = Config::DETECT, $default = array()){ + $this->correct = true; + $this->type = (int) $type; + $this->file = $file; + if(!is_array($default)){ + $default = array(); + } + if(!file_exists($file)){ + $this->config = $default; + $this->save(); + }else{ + if($this->type === Config::DETECT){ + $extension = explode(".", basename($this->file)); + $extension = strtolower(trim(array_pop($extension))); + if(isset(Config::$formats[$extension])){ + $this->type = Config::$formats[$extension]; + }else{ + $this->correct = false; + } + } + if($this->correct === true){ + $content = @file_get_contents($this->file); + switch($this->type){ + case Config::PROPERTIES: + case Config::CNF: + $this->parseProperties($content); + break; + case Config::JSON: + $this->config = @json_decode($content, true); + break; + case Config::YAML: + $content = self::fixYAMLIndexes($content); + $this->config = yaml_parse($content); + break; + case Config::SERIALIZED: + $this->config = @unserialize($content); + break; + case Config::ENUM: + $this->parseList($content); + break; + default: + $this->correct = false; + + return false; + } + if(!is_array($this->config)){ + $this->config = $default; + } + if($this->fillDefaults($default, $this->config) > 0){ + $this->save(); + } + }else{ + return false; + } + } + + return true; + } + + /** + * @return boolean + */ + public function check(){ + return $this->correct === true; + } + + /** + * @return boolean + */ + public function save(){ + if($this->correct === true){ + $content = null; + switch($this->type){ + case Config::PROPERTIES: + case Config::CNF: + $content = $this->writeProperties(); + break; + case Config::JSON: + $content = json_encode($this->config, JSON_PRETTY_PRINT | JSON_BIGINT_AS_STRING); + break; + case Config::YAML: + $content = yaml_emit($this->config, YAML_UTF8_ENCODING); + break; + case Config::SERIALIZED: + $content = @serialize($this->config); + break; + case Config::ENUM: + $content = implode("\r\n", array_keys($this->config)); + break; + } + @file_put_contents($this->file, $content, LOCK_EX); + + return true; + }else{ + return false; + } + } + + /** + * @param $k + * + * @return boolean|mixed + */ + public function &__get($k){ + return $this->get($k); + } + + /** + * @param $k + * @param $v + */ + public function __set($k, $v){ + $this->set($k, $v); + } + + /** + * @param $k + * + * @return boolean + */ + public function __isset($k){ + return $this->exists($k); + } + + /** + * @param $k + */ + public function __unset($k){ + $this->remove($k); + } + + /** + * @param $k + * + * @return boolean|mixed + */ + public function &get($k){ + if(isset($this->correct) and ($this->correct === false or !isset($this->config[$k]))){ + $false = false; + + return $false; + } + + return $this->config[$k]; + } + + /** + * @param string $k key to be set + * @param bool $v value to set key + */ + public function set($k, $v = true){ + $this->config[$k] = $v; + } + + /** + * @param array $v + */ + public function setAll($v){ + $this->config = $v; + } + + /** + * @param $k + * @param bool $lowercase If set, searches Config in single-case / lowercase. + * + * @return boolean + */ + public function exists($k, $lowercase = false){ + if($lowercase === true){ + $k = strtolower($k); //Convert requested key to lower + $array = array_change_key_case($this->config, CASE_LOWER); //Change all keys in array to lower + return isset($array[$k]); //Find $k in modified array + }else{ + return isset($this->config[$k]); + } + } + + /** + * @param $k + */ + public function remove($k){ + unset($this->config[$k]); + } + + /** + * @param bool $keys + * + * @return array + */ + public function getAll($keys = false){ + return ($keys === true ? array_keys($this->config) : $this->config); + } + + /** + * @param array $defaults + */ + public function setDefaults(array $defaults){ + $this->fillDefaults($defaults, $this->config); + } + + /** + * @param $default + * @param $data + * + * @return integer + */ + private function fillDefaults($default, &$data){ + $changed = 0; + foreach($default as $k => $v){ + if(is_array($v)){ + if(!isset($data[$k]) or !is_array($data[$k])){ + $data[$k] = array(); + } + $changed += $this->fillDefaults($v, $data[$k]); + }elseif(!isset($data[$k])){ + $data[$k] = $v; + ++$changed; + } + } + + return $changed; + } + + /** + * @param $content + */ + private function parseList($content){ + foreach(explode("\n", trim(str_replace("\r\n", "\n", $content))) as $v){ + $v = trim($v); + if($v == ""){ + continue; + } + $this->config[$v] = true; + } + } + + /** + * @return string + */ + private function writeProperties(){ + $content = "#Properties Config file\r\n#" . date("D M j H:i:s T Y") . "\r\n"; + foreach($this->config as $k => $v){ + if(is_bool($v) === true){ + $v = $v === true ? "on" : "off"; + }elseif(is_array($v)){ + $v = implode(";", $v); + } + $content .= $k . "=" . $v . "\r\n"; + } + + return $content; + } + + /** + * @param $content + */ + private function parseProperties($content){ + if(preg_match_all('/([a-zA-Z0-9\-_\.]*)=([^\r\n]*)/u', $content, $matches) > 0){ //false or 0 matches + foreach($matches[1] as $i => $k){ + $v = trim($matches[2][$i]); + switch(strtolower($v)){ + case "on": + case "true": + case "yes": + $v = true; + break; + case "off": + case "false": + case "no": + $v = false; + break; + } + if(isset($this->config[$k])){ + console("[NOTICE] [Config] Repeated property " . $k . " on file " . $this->file, true, true, 2); + } + $this->config[$k] = $v; + } + } + } + +} diff --git a/src/pocketmine/utils/Random.php b/src/pocketmine/utils/Random.php new file mode 100644 index 000000000..b688fa392 --- /dev/null +++ b/src/pocketmine/utils/Random.php @@ -0,0 +1,122 @@ +setSeed($seed); + } + + /** + * @param int|bool $seed Integer to be used as seed. If false, generates a Random one + */ + public function setSeed($seed = false){ + $seed = $seed !== false ? (int) $seed : Utils::readInt(Utils::getRandomBytes(4, false)); + $this->z = $seed ^ 0xdeadbeef; + $this->w = $seed ^ 0xc0de1337; + } + + /** + * Returns an 31-bit integer (not signed) + * + * @return int + */ + public function nextInt(){ + return Utils::readInt($this->nextBytes(4)) & 0x7FFFFFFF; + } + + /** + * Returns a 32-bit integer (signed) + * + * @return int + */ + public function nextSignedInt(){ + return Utils::readInt($this->nextBytes(4)); + } + + /** + * Returns a float between 0.0 and 1.0 (inclusive) + * + * @return float + */ + public function nextFloat(){ + return $this->nextInt() / 0x7FFFFFFF; + } + + /** + * Returns a float between -1.0 and 1.0 (inclusive) + * + * @return float + */ + public function nextSignedFloat(){ + return $this->nextSignedInt() / 0x7FFFFFFF; + } + + /** + * Returns $byteCount random bytes + * + * @param $byteCount + * + * @return string + */ + public function nextBytes($byteCount){ + $bytes = ""; + while(strlen($bytes) < $byteCount){ + $this->z = 36969 * ($this->z & 65535) + ($this->z >> 16); + $this->w = 18000 * ($this->w & 65535) + ($this->w >> 16); + $bytes .= pack("N", ($this->z << 16) + $this->w); + } + + return substr($bytes, 0, $byteCount); + } + + /** + * Returns a random boolean + * + * @return bool + */ + public function nextBoolean(){ + return ($this->nextSignedInt() & 0x01) === 0; + } + + /** + * Returns a random integer between $start and $end + * + * @param int $start default 0 + * @param int $end default PHP_INT_MAX + * + * @return int + */ + public function nextRange($start = 0, $end = PHP_INT_MAX){ + return $start + ($this->nextInt() % ($end + 1 - $start)); + } + +} \ No newline at end of file diff --git a/src/pocketmine/utils/TextFormat.php b/src/pocketmine/utils/TextFormat.php new file mode 100644 index 000000000..16dd1cba0 --- /dev/null +++ b/src/pocketmine/utils/TextFormat.php @@ -0,0 +1,281 @@ +"; + ++$tokens; + break; + case TextFormat::OBFUSCATED: + //$newString .= ""; + //++$tokens; + break; + case TextFormat::ITALIC: + $newString .= ""; + ++$tokens; + break; + case TextFormat::UNDERLINE: + $newString .= ""; + ++$tokens; + break; + case TextFormat::STRIKETHROUGH: + $newString .= ""; + ++$tokens; + break; + case TextFormat::RESET: + $newString .= str_repeat("", $tokens); + $tokens = 0; + break; + + //Colors + case TextFormat::BLACK: + $newString .= ""; + ++$tokens; + break; + case TextFormat::DARK_BLUE: + $newString .= ""; + ++$tokens; + break; + case TextFormat::DARK_GREEN: + $newString .= ""; + ++$tokens; + break; + case TextFormat::DARK_AQUA: + $newString .= ""; + ++$tokens; + break; + case TextFormat::DARK_RED: + $newString .= ""; + ++$tokens; + break; + case TextFormat::DARK_PURPLE: + $newString .= ""; + ++$tokens; + break; + case TextFormat::GOLD: + $newString .= ""; + ++$tokens; + break; + case TextFormat::GRAY: + $newString .= ""; + ++$tokens; + break; + case TextFormat::DARK_GRAY: + $newString .= ""; + ++$tokens; + break; + case TextFormat::BLUE: + $newString .= ""; + ++$tokens; + break; + case TextFormat::GREEN: + $newString .= ""; + ++$tokens; + break; + case TextFormat::AQUA: + $newString .= ""; + ++$tokens; + break; + case TextFormat::RED: + $newString .= ""; + ++$tokens; + break; + case TextFormat::LIGHT_PURPLE: + $newString .= ""; + ++$tokens; + break; + case TextFormat::YELLOW: + $newString .= ""; + ++$tokens; + break; + case TextFormat::WHITE: + $newString .= ""; + ++$tokens; + break; + default: + $newString .= $token; + break; + } + } + + $newString .= str_repeat("", $tokens); + + return $newString; + } + + /** + * Returns a string with colorized ANSI Escape codes + * + * @param $string + * + * @return string + */ + public static function toANSI($string){ + if(!is_array($string)){ + $string = self::tokenize($string); + } + $newString = ""; + foreach($string as $token){ + switch($token){ + case TextFormat::BOLD: + break; + case TextFormat::OBFUSCATED: + $newString .= "\x1b[8m"; + break; + case TextFormat::ITALIC: + $newString .= "\x1b[3m"; + break; + case TextFormat::UNDERLINE: + $newString .= "\x1b[4m"; + break; + case TextFormat::STRIKETHROUGH: + $newString .= "\x1b[9m"; + break; + case TextFormat::RESET: + $newString .= "\x1b[0m"; + break; + + //Colors + case TextFormat::BLACK: + $newString .= "\x1b[30m"; + break; + case TextFormat::DARK_BLUE: + $newString .= "\x1b[34m"; + break; + case TextFormat::DARK_GREEN: + $newString .= "\x1b[32m"; + break; + case TextFormat::DARK_AQUA: + $newString .= "\x1b[36m"; + break; + case TextFormat::DARK_RED: + $newString .= "\x1b[31m"; + break; + case TextFormat::DARK_PURPLE: + $newString .= "\x1b[35m"; + break; + case TextFormat::GOLD: + $newString .= "\x1b[33m"; + break; + case TextFormat::GRAY: + $newString .= "\x1b[37m"; + break; + case TextFormat::DARK_GRAY: + $newString .= "\x1b[30;1m"; + break; + case TextFormat::BLUE: + $newString .= "\x1b[34;1m"; + break; + case TextFormat::GREEN: + $newString .= "\x1b[32;1m"; + break; + case TextFormat::AQUA: + $newString .= "\x1b[36;1m"; + break; + case TextFormat::RED: + $newString .= "\x1b[31;1m"; + break; + case TextFormat::LIGHT_PURPLE: + $newString .= "\x1b[35;1m"; + break; + case TextFormat::YELLOW: + $newString .= "\x1b[33;1m"; + break; + case TextFormat::WHITE: + $newString .= "\x1b[37;1m"; + break; + default: + $newString .= $token; + break; + } + } + + return $newString; + } + +} \ No newline at end of file diff --git a/src/pocketmine/utils/Utils.php b/src/pocketmine/utils/Utils.php new file mode 100644 index 000000000..a5daf896b --- /dev/null +++ b/src/pocketmine/utils/Utils.php @@ -0,0 +1,829 @@ + $v){ + if($v == "00-00-00-00-00-00"){ + unset($matches[1][$i]); + } + } + $machine .= implode(" ", $matches[1]); //Mac Addresses + } + } + $data = $machine . PHP_MAXPATHLEN; + $data .= PHP_INT_MAX; + $data .= PHP_INT_SIZE; + $data .= get_current_user(); + foreach(get_loaded_extensions() as $ext){ + $data .= $ext . ":" . phpversion($ext); + } + + return hash("md5", $machine, $raw) . hash("sha512", $data, $raw); + } + + /** + * Gets the External IP using an external service, it is cached + * + * @param bool $force default false, force IP check even when cached + * + * @return string + */ + + public static function getIP($force = false){ + if(Utils::$online === false){ + return false; + }elseif(Utils::$ip !== false and $force !== true){ + return Utils::$ip; + } + $ip = trim(strip_tags(Utils::getURL("http://checkip.dyndns.org/"))); + if(preg_match('#Current IP Address\: ([0-9a-fA-F\:\.]*)#', $ip, $matches) > 0){ + Utils::$ip = $matches[1]; + }else{ + $ip = Utils::getURL("http://www.checkip.org/"); + if(preg_match('#">([0-9a-fA-F\:\.]*)#', $ip, $matches) > 0){ + Utils::$ip = $matches[1]; + }else{ + $ip = Utils::getURL("http://checkmyip.org/"); + if(preg_match('#Your IP address is ([0-9a-fA-F\:\.]*)#', $ip, $matches) > 0){ + Utils::$ip = $matches[1]; + }else{ + $ip = trim(Utils::getURL("http://ifconfig.me/ip")); + if($ip != ""){ + Utils::$ip = $ip; + }else{ + return false; + } + } + } + } + + return Utils::$ip; + + } + + /** + * Returns the current Operating System + * Windows => win + * MacOS => mac + * iOS => ios + * Android => android + * Linux => Linux + * BSD => bsd + * Other => other + * + * @return string + */ + public static function getOS(){ + $uname = php_uname("s"); + if(stripos($uname, "Darwin") !== false){ + if(strpos(php_uname("m"), "iP") === 0){ + return "ios"; + }else{ + return "mac"; + } + }elseif(stripos($uname, "Win") !== false or $uname === "Msys"){ + return "win"; + }elseif(stripos($uname, "Linux") !== false){ + if(@file_exists("/system/build.prop")){ + return "android"; + }else{ + return "linux"; + } + }elseif(stripos($uname, "BSD") !== false or $uname === "DragonFly"){ + return "bsd"; + }else{ + return "other"; + } + } + + /** + * Returns a prettified hexdump + * + * @param string $bin + * + * @return string + */ + public static function hexdump($bin){ + $output = ""; + $bin = str_split($bin, 16); + foreach($bin as $counter => $line){ + $hex = chunk_split(chunk_split(str_pad(bin2hex($line), 32, " ", STR_PAD_RIGHT), 2, " "), 24, " "); + $ascii = preg_replace('#([^\x20-\x7E])#', ".", $line); + $output .= str_pad(dechex($counter << 4), 4, "0", STR_PAD_LEFT) . " " . $hex . " " . $ascii . PHP_EOL; + } + + return $output; + } + + + /** + * Returns a string that can be printed, replaces non-printable characters + * + * @param $str + * + * @return string + */ + public static function printable($str){ + if(!is_string($str)){ + return gettype($str); + } + + return preg_replace('#([^\x20-\x7E])#', '.', $str); + } + + /** + * Reads a 3-byte big-endian number + * + * @param $str + * + * @return mixed + */ + public static function readTriad($str){ + list(, $unpacked) = @unpack("N", "\x00" . $str); + + return $unpacked; + } + + /** + * Writes a 3-byte big-endian number + * + * @param $value + * + * @return string + */ + public static function writeTriad($value){ + return substr(pack("N", $value), 1); + } + + /** + * Writes a coded metadata string + * TODO: Replace and move this to entity + * + * @param $data + * + * @return string + */ + public static function writeMetadata($data){ + $m = ""; + foreach($data as $bottom => $d){ + $m .= chr(($d["type"] << 5) | ($bottom & 0b00011111)); + switch($d["type"]){ + case 0: + $m .= Utils::writeByte($d["value"]); + break; + case 1: + $m .= Utils::writeLShort($d["value"]); + break; + case 2: + $m .= Utils::writeLInt($d["value"]); + break; + case 3: + $m .= Utils::writeLFloat($d["value"]); + break; + case 4: + $m .= Utils::writeLShort(strlen($d["value"])); + $m .= $data["value"]; + break; + case 5: + $m .= Utils::writeLShort($d["value"][0]); + $m .= Utils::writeByte($d["value"][1]); + $m .= Utils::writeLShort($d["value"][2]); + break; + case 6: + for($i = 0; $i < 3; ++$i){ + $m .= Utils::writeLInt($d["value"][$i]); + } + break; + } + } + $m .= "\x7f"; + + return $m; + } + + /** + * Writes a Item to binary (short id, byte Count, short Damage) + * + * @param Item $item + * + * @return string + */ + public static function writeSlot(Item $item){ + return Utils::writeShort($item->getID()) . chr($item->getCount()) . Utils::writeShort($item->getMetadata()); + } + + /** + * Reads a binary Item, returns an Item object + * + * @param $ob + * + * @return Item + */ + + public static function readSlot($ob){ + $id = Utils::readShort($ob->get(2)); + $cnt = ord($ob->get(1)); + + return Item::get( + $id, + Utils::readShort($ob->get(2)), + $cnt + ); + } + + /** + * Reads a metadata coded string + * TODO: Change + * + * @param $value + * @param bool $types + * + * @return array + */ + public static function readMetadata($value, $types = false){ + $offset = 0; + $m = array(); + $b = ord($value{$offset}); + ++$offset; + while($b !== 127 and isset($value{$offset})){ + $bottom = $b & 0x1F; + $type = $b >> 5; + switch($type){ + case 0: + $r = Utils::readByte($value{$offset}); + ++$offset; + break; + case 1: + $r = Utils::readLShort(substr($value, $offset, 2)); + $offset += 2; + break; + case 2: + $r = Utils::readLInt(substr($value, $offset, 4)); + $offset += 4; + break; + case 3: + $r = Utils::readLFloat(substr($value, $offset, 4)); + $offset += 4; + break; + case 4: + $len = Utils::readLShort(substr($value, $offset, 2)); + $offset += 2; + $r = substr($value, $offset, $len); + $offset += $len; + break; + case 5: + $r = array(); + $r[] = Utils::readLShort(substr($value, $offset, 2)); + $offset += 2; + $r[] = ord($value{$offset}); + ++$offset; + $r[] = Utils::readLShort(substr($value, $offset, 2)); + $offset += 2; + break; + case 6: + $r = array(); + for($i = 0; $i < 3; ++$i){ + $r[] = Utils::readLInt(substr($value, $offset, 4)); + $offset += 4; + } + break; + + } + if($types === true){ + $m[$bottom] = array($r, $type); + }else{ + $m[$bottom] = $r; + } + $b = ord($value{$offset}); + ++$offset; + } + + return $m; + } + + public static function readDataArray($str, $len = 10, &$offset = null){ + $data = array(); + $offset = 0; + for($i = 1; $i <= $len and isset($str{$offset}); ++$i){ + $l = Utils::readTriad(substr($str, $offset, 3)); + $offset += 3; + $data[] = substr($str, $offset, $l); + $offset += $l; + } + + return $data; + } + + public static function writeDataArray($data){ + $raw = ""; + foreach($data as $v){ + $raw .= Utils::writeTriad(strlen($v)); + $raw .= $v; + } + + return $raw; + } + + /** + * This function tries to get all the entropy available in PHP, and distills it to get a good RNG. + * + * + * @param int $length default 16, Number of bytes to generate + * @param bool $secure default true, Generate secure distilled bytes, slower + * @param bool $raw default true, returns a binary string if true, or an hexadecimal one + * @param string $startEntropy default null, adds more initial entropy + * @param int &$rounds Will be set to the number of rounds taken + * @param int &$drop Will be set to the amount of dropped bytes + * + * @return string + */ + public static function getRandomBytes($length = 16, $secure = true, $raw = true, $startEntropy = "", &$rounds = 0, &$drop = 0){ + static $lastRandom = ""; + $output = ""; + $length = abs((int) $length); + $secureValue = ""; + $rounds = 0; + $drop = 0; + while(!isset($output{$length - 1})){ + //some entropy, but works ^^ + $weakEntropy = array( + is_array($startEntropy) ? implode($startEntropy) : $startEntropy, + serialize(@stat(__FILE__)), + __DIR__, + PHP_OS, + microtime(), + (string) lcg_value(), + (string) PHP_MAXPATHLEN, + PHP_SAPI, + (string) PHP_INT_MAX . "." . PHP_INT_SIZE, + serialize($_SERVER), + serialize(get_defined_constants()), + get_current_user(), + serialize(ini_get_all()), + (string) memory_get_usage() . "." . memory_get_peak_usage(), + php_uname(), + phpversion(), + extension_loaded("gmp") ? gmp_strval(gmp_random(4)) : microtime(), + zend_version(), + (string) getmypid(), + (string) getmyuid(), + (string) mt_rand(), + (string) getmyinode(), + (string) getmygid(), + (string) rand(), + function_exists("zend_thread_id") ? ((string) zend_thread_id()) : microtime(), + function_exists("getrusage") ? @implode(getrusage()) : microtime(), + function_exists("sys_getloadavg") ? @implode(sys_getloadavg()) : microtime(), + serialize(get_loaded_extensions()), + sys_get_temp_dir(), + (string) disk_free_space("."), + (string) disk_total_space("."), + uniqid(microtime(), true), + file_exists("/proc/cpuinfo") ? file_get_contents("/proc/cpuinfo") : microtime(), + ); + + shuffle($weakEntropy); + $value = hash("sha512", implode($weakEntropy), true); + $lastRandom .= $value; + foreach($weakEntropy as $k => $c){ //mixing entropy values with XOR and hash randomness extractor + $value ^= hash("sha256", $c . microtime() . $k, true) . hash("sha256", mt_rand() . microtime() . $k . $c, true); + $value ^= hash("sha512", ((string) lcg_value()) . $c . microtime() . $k, true); + } + unset($weakEntropy); + + if($secure === true){ + $strongEntropyValues = array( + is_array($startEntropy) ? hash("sha512", $startEntropy[($rounds + $drop) % count($startEntropy)], true) : hash("sha512", $startEntropy, true), //Get a random index of the startEntropy, or just read it + file_exists("/dev/urandom") ? fread(fopen("/dev/urandom", "rb"), 64) : str_repeat("\x00", 64), + (function_exists("openssl_random_pseudo_bytes") and version_compare(PHP_VERSION, "5.3.4", ">=")) ? openssl_random_pseudo_bytes(64) : str_repeat("\x00", 64), + function_exists("mcrypt_create_iv") ? mcrypt_create_iv(64, MCRYPT_DEV_URANDOM) : str_repeat("\x00", 64), + $value, + ); + $strongEntropy = array_pop($strongEntropyValues); + foreach($strongEntropyValues as $value){ + $strongEntropy = $strongEntropy ^ $value; + } + $value = ""; + //Von Neumann randomness extractor, increases entropy + $bitcnt = 0; + for($j = 0; $j < 64; ++$j){ + $a = ord($strongEntropy{$j}); + for($i = 0; $i < 8; $i += 2){ + $b = ($a & (1 << $i)) > 0 ? 1 : 0; + if($b != (($a & (1 << ($i + 1))) > 0 ? 1 : 0)){ + $secureValue |= $b << $bitcnt; + if($bitcnt == 7){ + $value .= chr($secureValue); + $secureValue = 0; + $bitcnt = 0; + }else{ + ++$bitcnt; + } + ++$drop; + }else{ + $drop += 2; + } + } + } + } + $output .= substr($value, 0, min($length - strlen($output), $length)); + unset($value); + ++$rounds; + } + $lastRandom = hash("sha512", $lastRandom, true); + + return $raw === false ? bin2hex($output) : $output; + } + + /* + public static function angle3D($pos1, $pos2){ + $X = $pos1["x"] - $pos2["x"]; + $Z = $pos1["z"] - $pos2["z"]; + $dXZ = sqrt(pow($X, 2) + pow($Z, 2)); + $Y = $pos1["y"] - $pos2["y"]; + $hAngle = rad2deg(atan2($Z, $X) - M_PI_2); + $vAngle = rad2deg(-atan2($Y, $dXZ)); + + return array("yaw" => $hAngle, "pitch" => $vAngle); + }*/ + + /** + * GETs an URL using cURL + * + * @param $page + * @param int $timeout default 10 + * + * @return bool|mixed + */ + public static function getURL($page, $timeout = 10){ + if(Utils::$online === false){ + return false; + } + + $ch = curl_init($page); + curl_setopt($ch, CURLOPT_HTTPHEADER, array("User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:12.0) Gecko/20100101 Firefox/12.0 PocketMine-MP")); + curl_setopt($ch, CURLOPT_AUTOREFERER, true); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); + curl_setopt($ch, CURLOPT_FORBID_REUSE, 1); + curl_setopt($ch, CURLOPT_FRESH_CONNECT, 1); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, (int) $timeout); + $ret = curl_exec($ch); + curl_close($ch); + + return $ret; + } + + /** + * POSTs data to an URL + * + * @param $page + * @param array|string $args + * @param int $timeout + * + * @return bool|mixed + */ + public static function postURL($page, $args, $timeout = 10){ + if(Utils::$online === false){ + return false; + } + + $ch = curl_init($page); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); + curl_setopt($ch, CURLOPT_FORBID_REUSE, 1); + curl_setopt($ch, CURLOPT_FRESH_CONNECT, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, $args); + curl_setopt($ch, CURLOPT_AUTOREFERER, true); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, array("User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:12.0) Gecko/20100101 Firefox/12.0 PocketMine-MP")); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, (int) $timeout); + $ret = curl_exec($ch); + curl_close($ch); + + return $ret; + } + + /** + * Reads a byte boolean + * + * @param $b + * + * @return bool + */ + public static function readBool($b){ + return Utils::readByte($b, false) === 0 ? false : true; + } + + /** + * Writes a byte boolean + * + * @param $b + * + * @return bool|string + */ + public static function writeBool($b){ + return Utils::writeByte($b === true ? 1 : 0); + } + + /** + * Reads an unsigned/signed byte + * + * @param $c + * @param bool $signed + * + * @return int + */ + public static function readByte($c, $signed = true){ + $b = ord($c{0}); + if($signed === true and ($b & 0x80) === 0x80){ //calculate Two's complement + $b = -0x80 + ($b & 0x7f); + } + + return $b; + } + + /** + * Writes an unsigned/signed byte + * + * @param $c + * + * @return bool|string + */ + public static function writeByte($c){ + if($c > 0xff){ + return false; + } + if($c < 0 and $c >= -0x80){ + $c = 0xff + $c + 1; + } + + return chr($c); + } + + /** + * Reads a 16-bit signed/unsigned big-endian number + * + * @param $str + * @param bool $signed + * + * @return int + */ + public static function readShort($str, $signed = true){ + list(, $unpacked) = @unpack("n", $str); + if($unpacked > 0x7fff and $signed === true){ + $unpacked -= 0x10000; // Convert unsigned short to signed short + } + + return $unpacked; + } + + /** + * Writes a 16-bit signed/unsigned big-endian number + * + * @param $value + * + * @return string + */ + public static function writeShort($value){ + if($value < 0){ + $value += 0x10000; + } + + return pack("n", $value); + } + + /** + * Reads a 16-bit signed/unsigned little-endian number + * + * @param $str + * @param bool $signed + * + * @return int + */ + public static function readLShort($str, $signed = true){ + list(, $unpacked) = @unpack("v", $str); + if($unpacked > 0x7fff and $signed === true){ + $unpacked -= 0x10000; // Convert unsigned short to signed short + } + + return $unpacked; + } + + /** + * Writes a 16-bit signed/unsigned little-endian number + * + * @param $value + * + * @return string + */ + public static function writeLShort($value){ + if($value < 0){ + $value += 0x10000; + } + + return pack("v", $value); + } + + public static function readInt($str){ + list(, $unpacked) = @unpack("N", $str); + if($unpacked > 2147483647){ + $unpacked -= 4294967296; + } + + return (int) $unpacked; + } + + public static function writeInt($value){ + return pack("N", $value); + } + + public static function readLInt($str){ + list(, $unpacked) = @unpack("V", $str); + if($unpacked >= 2147483648){ + $unpacked -= 4294967296; + } + + return (int) $unpacked; + } + + public static function writeLInt($value){ + return pack("V", $value); + } + + public static function readFloat($str){ + list(, $value) = ENDIANNESS === Utils::BIG_ENDIAN ? @unpack("f", $str) : @unpack("f", strrev($str)); + + return $value; + } + + public static function writeFloat($value){ + return ENDIANNESS === Utils::BIG_ENDIAN ? pack("f", $value) : strrev(pack("f", $value)); + } + + public static function readLFloat($str){ + list(, $value) = ENDIANNESS === Utils::BIG_ENDIAN ? @unpack("f", strrev($str)) : @unpack("f", $str); + + return $value; + } + + public static function writeLFloat($value){ + return ENDIANNESS === Utils::BIG_ENDIAN ? strrev(pack("f", $value)) : pack("f", $value); + } + + public static function printFloat($value){ + return preg_replace("/(\.\d+?)0+$/", "$1", sprintf("%F", $value)); + } + + public static function readDouble($str){ + list(, $value) = ENDIANNESS === Utils::BIG_ENDIAN ? @unpack("d", $str) : @unpack("d", strrev($str)); + + return $value; + } + + public static function writeDouble($value){ + return ENDIANNESS === Utils::BIG_ENDIAN ? pack("d", $value) : strrev(pack("d", $value)); + } + + public static function readLDouble($str){ + list(, $value) = ENDIANNESS === Utils::BIG_ENDIAN ? @unpack("d", strrev($str)) : @unpack("d", $str); + + return $value; + } + + public static function writeLDouble($value){ + return ENDIANNESS === Utils::BIG_ENDIAN ? strrev(pack("d", $value)) : pack("d", $value); + } + + public static function readLong($x, $signed = true){ + $value = "0"; + if($signed === true){ + $negative = ((ord($x{0}) & 0x80) === 0x80) ? true : false; + if($negative){ + $x = ~$x; + } + }else{ + $negative = false; + } + + for($i = 0; $i < 8; $i += 4){ + $value = bcmul($value, "4294967296", 0); //4294967296 == 2^32 + $value = bcadd($value, 0x1000000 * ord(@$x{$i}) + ((ord(@$x{$i + 1}) << 16) | (ord(@$x{$i + 2}) << 8) | ord(@$x{$i + 3})), 0); + } + + return ($negative === true ? "-" . $value : $value); + } + + public static function writeLong($value){ + $x = ""; + if($value{0} === "-"){ + $negative = true; + $value = bcadd($value, "1"); + if($value{0} === "-"){ + $value = substr($value, 1); + } + }else{ + $negative = false; + } + while(bccomp($value, "0", 0) > 0){ + $temp = bcmod($value, "16777216"); + $x = chr($temp >> 16) . chr($temp >> 8) . chr($temp) . $x; + $value = bcdiv($value, "16777216", 0); + } + $x = str_pad(substr($x, 0, 8), 8, "\x00", STR_PAD_LEFT); + if($negative === true){ + $x = ~$x; + } + + return $x; + } + + public static function readLLong($str){ + return Utils::readLong(strrev($str)); + } + + public static function writeLLong($value){ + return strrev(Utils::writeLong($value)); + } + + +} + +define("pocketmine\\utils\\ENDIANNESS", (pack("d", 1) === "\77\360\0\0\0\0\0\0" ? Utils::BIG_ENDIAN : Utils::LITTLE_ENDIAN)); \ No newline at end of file diff --git a/src/pocketmine/utils/VersionString.php b/src/pocketmine/utils/VersionString.php new file mode 100644 index 000000000..33a18451d --- /dev/null +++ b/src/pocketmine/utils/VersionString.php @@ -0,0 +1,113 @@ + 0, + "a" => 0, + "beta" => 1, + "b" => 1, + "final" => 2, + "f" => 2, + ); + private $stage; + private $major; + private $release; + private $minor; + private $development = false; + private $generation; + + public function __construct($version = \pocketmine\VERSION){ + if(is_int($version)){ + $this->minor = $version & 0x1F; + $this->major = ($version >> 5) & 0x0F; + $this->generation = ($version >> 9) & 0x0F; + $this->stage = array_search(($version >> 13) & 0x0F, VersionString::$stageOrder, true); + }else{ + $version = preg_split("/([A-Za-z]*)[ _\-]([0-9]*)\.([0-9]*)\.{0,1}([0-9]*)(dev|)/", $version, -1, PREG_SPLIT_DELIM_CAPTURE); + $this->stage = strtolower($version[1]); //0-15 + $this->generation = (int) $version[2]; //0-15 + $this->major = (int) $version[3]; //0-15 + $this->minor = (int) $version[4]; //0-31 + $this->development = $version[5] === "dev" ? true : false; + } + } + + public function getNumber(){ + return (int) (VersionString::$stageOrder[$this->stage] << 13) + ($this->generation << 9) + ($this->major << 5) + $this->minor; + } + + public function getStage(){ + return $this->stage; + } + + public function getGeneration(){ + return $this->generation; + } + + public function getMajor(){ + return $this->major; + } + + public function getMinor(){ + return $this->minor; + } + + public function getRelease(){ + return $this->generation . "." . $this->major . "." . $this->minor; + } + + public function isDev(){ + return $this->development === true; + } + + public function get(){ + return ucfirst($this->stage) . "_" . $this->getRelease() . ($this->development === true ? "dev" : ""); + } + + public function __toString(){ + return $this->get(); + } + + public function compare($target, $diff = false){ + if(($target instanceof VersionString) === false){ + $target = new VersionString($target); + } + $number = $this->getNumber(); + $tNumber = $target->getNumber(); + if($diff === true){ + return $tNumber - $number; + } + if($number > $tNumber){ + return -1; //Target is older + }elseif($number < $tNumber){ + return 1; //Target is newer + }else{ + return 0; //Same version + } + } +} \ No newline at end of file diff --git a/src/pocketmine/wizard/Installer.php b/src/pocketmine/wizard/Installer.php new file mode 100644 index 000000000..d7cf2e6d1 --- /dev/null +++ b/src/pocketmine/wizard/Installer.php @@ -0,0 +1,205 @@ + $native){ + echo " $native => $short\n"; + } + do{ + echo "[?] Language (en): "; + $lang = strtolower($this->getInput("en")); + if(!isset(InstallerLang::$languages[$lang])){ + echo "[!] Couldn't find the language\n"; + $lang = false; + } + }while($lang == false); + $this->lang = new InstallerLang($lang); + echo "[*] " . $this->lang->language_has_been_selected . "\n"; + echo "[?] " . $this->lang->skip_installer . " (y/N): "; + if(strtolower($this->getInput()) === "y"){ + return; + } + echo "\n"; + $this->welcome(); + $this->generateBaseConfig(); + $this->generateUserFiles(); + + $this->networkFunctions(); + + $this->endWizard(); + } + + private function welcome(){ + echo $this->lang->welcome_to_pocketmine . "\n"; + echo <<lang->accept_license . " (y/N): "; + if(strtolower($this->getInput("n")) != "y"){ + echo "[!] " . $this->lang->you_have_to_accept_the_license . "\n"; + sleep(5); + exit(0); + } + echo "[*] " . $this->lang->setting_up_server_now . "\n"; + echo "[*] " . $this->lang->default_values_info . "\n"; + echo "[*] " . $this->lang->server_properties . "\n"; + + } + + private function generateBaseConfig(){ + $config = new Config(\pocketmine\DATA . "server.properties", Config::PROPERTIES); + echo "[?] " . $this->lang->name_your_server . " (" . self::DEFAULT_NAME . "): "; + $config->set("server-name", $this->getInput(self::DEFAULT_NAME)); + echo "[*] " . $this->lang->port_warning . "\n"; + do{ + echo "[?] " . $this->lang->server_port . " (" . self::DEFAULT_PORT . "): "; + $port = (int) $this->getInput(self::DEFAULT_PORT); + if($port <= 0 or $port > 65535){ + echo "[!] " . $this->lang->invalid_port . "\n"; + } + }while($port <= 0 or $port > 65535); + $config->set("server-port", $port); + echo "[*] " . $this->lang->ram_warning . "\n"; + echo "[?] " . $this->lang->server_ram . " (" . self::DEFAULT_MEMORY . "): "; + $config->set("memory-limit", ((int) $this->getInput(self::DEFAULT_MEMORY)) . "M"); + echo "[*] " . $this->lang->gamemode_info . "\n"; + do{ + echo "[?] " . $this->lang->default_gamemode . ": (" . self::DEFAULT_GAMEMODE . "): "; + $gamemode = (int) $this->getInput(self::DEFAULT_GAMEMODE); + }while($gamemode < 0 or $gamemode > 3); + $config->set("gamemode", $gamemode); + echo "[?] " . $this->lang->max_players . " (" . self::DEFAULT_PLAYERS . "): "; + $config->set("max-players", (int) $this->getInput(self::DEFAULT_PLAYERS)); + echo "[*] " . $this->lang->spawn_protection_info . "\n"; + echo "[?] " . $this->lang->spawn_protection . " (Y/n): "; + if(strtolower($this->getInput("y")) == "n"){ + $config->set("spawn-protection", -1); + }else{ + $config->set("spawn-protection", 16); + } + $config->save(); + } + + private function generateUserFiles(){ + echo "[*] " . $this->lang->op_info . "\n"; + echo "[?] " . $this->lang->op_who . ": "; + $op = strtolower($this->getInput("")); + if($op === ""){ + echo "[!] " . $this->lang->op_warning . "\n"; + }else{ + $ops = new Config(\pocketmine\DATA . "ops.txt", Config::ENUM); + $ops->set($op, true); + $ops->save(); + } + echo "[*] " . $this->lang->whitelist_info . "\n"; + echo "[?] " . $this->lang->whitelist_enable . " (y/N): "; + $config = new Config(\pocketmine\DATA . "server.properties", Config::PROPERTIES); + if(strtolower($this->getInput("n")) === "y"){ + echo "[!] " . $this->lang->whitelist_warning . "\n"; + $config->set("white-list", true); + }else{ + $config->set("white-list", false); + } + $config->save(); + } + + private function networkFunctions(){ + $config = new Config(\pocketmine\DATA . "server.properties", Config::PROPERTIES); + echo "[!] " . $this->lang->query_warning1 . "\n"; + echo "[!] " . $this->lang->query_warning2 . "\n"; + echo "[?] " . $this->lang->query_disable . " (y/N): "; + if(strtolower($this->getInput("n")) === "y"){ + $config->set("enable-query", false); + }else{ + $config->set("enable-query", true); + } + + echo "[*] " . $this->lang->rcon_info . "\n"; + echo "[?] " . $this->lang->rcon_enable . " (y/N): "; + if(strtolower($this->getInput("n")) === "y"){ + $config->set("enable-rcon", true); + $password = substr(base64_encode(Utils::getRandomBytes(20, false)), 3, 10); + $config->set("rcon.password", $password); + echo "[*] " . $this->lang->rcon_password . ": " . $password . "\n"; + }else{ + $config->set("enable-rcon", false); + } + + echo "[*] " . $this->lang->usage_info . "\n"; + echo "[?] " . $this->lang->usage_disable . " (y/N): "; + if(strtolower($this->getInput("n")) === "y"){ + $config->set("send-usage", false); + }else{ + $config->set("send-usage", true); + } + $config->save(); + + + echo "[*] " . $this->lang->ip_get . "\n"; + + $externalIP = Utils::getIP(); + $internalIP = gethostbyname(trim(`hostname`)); + + echo "[!] " . $this->lang->get("ip_warning", array("{{EXTERNAL_IP}}", "{{INTERNAL_IP}}"), array($externalIP, $internalIP)) . "\n"; + echo "[!] " . $this->lang->ip_confirm; + $this->getInput(); + } + + private function endWizard(){ + echo "[*] " . $this->lang->you_have_finished . "\n"; + echo "[*] " . $this->lang->pocketmine_plugins . "\n"; + echo "[*] " . $this->lang->pocketmine_will_start . "\n\n\n"; + sleep(4); + } + + private function getInput($default = ""){ + $input = trim(fgets(STDIN)); + + return $input === "" ? $default : $input; + } + + +} \ No newline at end of file diff --git a/src/pocketmine/wizard/InstallerLang.php b/src/pocketmine/wizard/InstallerLang.php new file mode 100644 index 000000000..fc8c3bee9 --- /dev/null +++ b/src/pocketmine/wizard/InstallerLang.php @@ -0,0 +1,114 @@ + "English", + "es" => "Español", + "zh" => "中文", + "ru" => "Pyccĸий", + "ja" => "日本語", + "de" => "Deutsch", + //"vi" => "Tiếng Việt", + "ko" => "한국어", + "fr" => "Français", + "it" => "Italiano", + //"lv" => "Latviešu", + "nl" => "Nederlands", + //"pt" => "Português", + "sv" => "Svenska", + "fi" => "Suomi", + "tr" => "Türkçe", + //"et" => "Eesti", + ); + private $texts = array(); + private $lang; + private $langfile; + + public function __construct($lang = ""){ + if(file_exists(\pocketmine\PATH . "src/lang/Installer/" . $lang . ".ini")){ + $this->lang = $lang; + $this->langfile = \pocketmine\PATH . "src/lang/Installer/" . $lang . ".ini"; + }else{ + $l = glob(\pocketmine\PATH . "src/lang/Installer/" . $lang . "_*.ini"); + if(count($l) > 0){ + $files = array(); + foreach($l as $file){ + $files[$file] = filesize($file); + } + arsort($files); + reset($files); + $l = key($files); + $l = substr($l, strrpos($l, "/") + 1, -4); + $this->lang = isset(self::$languages[$l]) ? $l : $lang; + $this->langfile = \pocketmine\PATH . "src/lang/Installer/" . $l . ".ini"; + }else{ + $this->lang = "en"; + $this->langfile = \pocketmine\PATH . "src/lang/Installer/en.ini"; + } + } + + $this->loadLang(\pocketmine\PATH . "src/lang/Installer/en.ini", "en"); + if($this->lang !== "en"){ + $this->loadLang($this->langfile, $this->lang); + } + + } + + public function getLang(){ + return ($this->lang); + } + + public function loadLang($langfile, $lang = "en"){ + $this->texts[$lang] = array(); + $texts = explode("\n", str_replace(array("\r", "\/\/"), array("", "//"), file_get_contents($langfile))); + foreach($texts as $line){ + $line = trim($line); + if($line === ""){ + continue; + } + $line = explode("=", $line); + $this->texts[$lang][array_shift($line)] = str_replace(array("\\n", "\\N",), "\n", implode("=", $line)); + } + } + + public function get($name, $search = array(), $replace = array()){ + if(!isset($this->texts[$this->lang][$name])){ + if($this->lang !== "en" and isset($this->texts["en"][$name])){ + return $this->texts["en"][$name]; + }else{ + return $name; + } + }elseif(count($search) > 0){ + return str_replace($search, $replace, $this->texts[$this->lang][$name]); + }else{ + return $this->texts[$this->lang][$name]; + } + } + + public function __get($name){ + return $this->get($name); + } + +} \ No newline at end of file diff --git a/src/spl/SplAutoloader.php b/src/spl/SplAutoloader.php new file mode 100644 index 000000000..8a44a62bd --- /dev/null +++ b/src/spl/SplAutoloader.php @@ -0,0 +1,64 @@ + + */ +interface SplAutoloader{ + /** + * Defines autoloader to work silently if resource is not found. + * + * @const + */ + const MODE_SILENT = 0; + + /** + * Defines autoloader to work normally (requiring an un-existent resource). + * + * @const + */ + const MODE_NORMAL = 1; + + /** + * Defines autoloader to work in debug mode, loading file and validating requested resource. + * + * @const + */ + const MODE_DEBUG = 2; + + /** + * Define the autoloader work mode. + * + * @param integer $mode Autoloader work mode. + */ + public function setMode($mode); + + /** + * Add a new resource lookup path. + * + * @param string $resourceName Resource name, namespace or prefix. + * @param mixed $resourcePath Resource single path or multiple paths (array). + */ + public function add($resourceName, $resourcePath = null); + + /** + * Load a resource through provided resource name. + * + * @param string $resourceName Resource name. + */ + public function load($resourceName); + + /** + * Register this as an autoloader instance. + * + * @param boolean $prepend Whether to prepend the autoloader or not in autoloader's list. + */ + public function register($prepend = false); + + /** + * Unregister this autoloader instance. + * + */ + public function unregister(); +} diff --git a/src/spl/SplClassLoader.php b/src/spl/SplClassLoader.php new file mode 100644 index 000000000..d081b8d95 --- /dev/null +++ b/src/spl/SplClassLoader.php @@ -0,0 +1,218 @@ +setMode(\SplClassLoader::MODE_NORMAL); + * + * // Add a namespace of classes + * $classLoader->add('Doctrine', array( + * '/path/to/doctrine-common', '/path/to/doctrine-dbal', '/path/to/doctrine-orm' + * )); + * + * // Add a prefix + * $classLoader->add('Swift', '/path/to/swift'); + * + * // Add a prefix through PEAR1 convention, requiring include_path lookup + * $classLoader->add('PEAR'); + * + * // Allow to PHP use the include_path for file path lookup + * $classLoader->setIncludePathLookup(true); + * + * // Possibility to change the default php file extension + * $classLoader->setFileExtension('.php'); + * + * // Register the autoloader, prepending it in the stack + * $classLoader->register(true); + * + * @author Guilherme Blanco + * @author Jonathan H. Wage + * @author Roman S. Borschel + * @author Matthew Weier O'Phinney + * @author Kris Wallsmith + * @author Fabien Potencier + */ +class SplClassLoader implements SplAutoloader{ + /** @var string */ + private $fileExtension = '.php'; + + /** @var boolean */ + private $includePathLookup = false; + + /** @var array */ + private $resources = array(); + + /** @var integer */ + private $mode = self::MODE_NORMAL; + + /** + * {@inheritdoc} + */ + public function setMode($mode){ + if($mode & self::MODE_SILENT && $mode & self::MODE_NORMAL){ + throw new \InvalidArgumentException( + sprintf('Cannot have %s working normally and silently at the same time!', __CLASS__) + ); + } + + $this->mode = $mode; + } + + /** + * Define the file extension of resource files in the path of this class loader. + * + * @param string $fileExtension + */ + public function setFileExtension($fileExtension){ + $this->fileExtension = $fileExtension; + } + + /** + * Retrieve the file extension of resource files in the path of this class loader. + * + * @return string + */ + public function getFileExtension(){ + return $this->fileExtension; + } + + /** + * Turns on searching the include for class files. Allows easy loading installed PEAR packages. + * + * @param boolean $includePathLookup + */ + public function setIncludePathLookup($includePathLookup){ + $this->includePathLookup = $includePathLookup; + } + + /** + * Gets the base include path for all class files in the namespace of this class loader. + * + * @return boolean + */ + public function getIncludePathLookup(){ + return $this->includePathLookup; + } + + /** + * {@inheritdoc} + */ + public function register($prepend = false){ + spl_autoload_register(array($this, 'load'), true, $prepend); + } + + /** + * {@inheritdoc} + */ + public function unregister(){ + spl_autoload_unregister(array($this, 'load')); + } + + /** + * {@inheritdoc} + */ + public function add($resource, $resourcePath = null){ + $this->resources[$resource] = (array) $resourcePath; + } + + /** + * {@inheritdoc} + */ + public function load($resourceName){ + $resourceAbsolutePath = $this->getResourceAbsolutePath($resourceName); + + switch(true){ + case ($this->mode & self::MODE_SILENT): + if($resourceAbsolutePath !== false){ + require $resourceAbsolutePath; + } + break; + + case ($this->mode & self::MODE_NORMAL): + default: + require $resourceAbsolutePath; + break; + } + + if($this->mode & self::MODE_DEBUG && !$this->isResourceDeclared($resourceName)){ + throw new \RuntimeException( + sprintf('Autoloader expected resource "%s" to be declared in file "%s".', $resourceName, $resourceAbsolutePath) + ); + } + } + + /** + * Transform resource name into its absolute resource path representation. + * + * @param string $resourceName + * + * @return string Resource absolute path. + */ + private function getResourceAbsolutePath($resourceName){ + $resourceRelativePath = $this->getResourceRelativePath($resourceName); + + foreach($this->resources as $resource => $resourcesPath){ + if(strpos($resourceName, $resource) !== 0){ + continue; + } + + foreach($resourcesPath as $resourcePath){ + $resourceAbsolutePath = $resourcePath . DIRECTORY_SEPARATOR . $resourceRelativePath; + + if(is_file($resourceAbsolutePath)){ + return $resourceAbsolutePath; + } + } + } + + if($this->includePathLookup && ($resourceAbsolutePath = stream_resolve_include_path($resourceRelativePath)) !== false){ + return $resourceAbsolutePath; + } + + return false; + } + /** + * Transform resource name into its relative resource path representation. + * + * @param string $resourceName + * + * @return string Resource relative path. + */ + private function getResourceRelativePath($resourceName){ + // We always work with FQCN in this context + $resourceName = ltrim($resourceName, '\\'); + $resourcePath = ''; + + if(($lastNamespacePosition = strrpos($resourceName, '\\')) !== false){ + // Namespaced resource name + $resourceNamespace = substr($resourceName, 0, $lastNamespacePosition); + $resourceName = substr($resourceName, $lastNamespacePosition + 1); + $resourcePath = str_replace('\\', DIRECTORY_SEPARATOR, $resourceNamespace) . DIRECTORY_SEPARATOR; + } + + return $resourcePath . str_replace('_', DIRECTORY_SEPARATOR, $resourceName) . $this->fileExtension; + } + + /** + * Check if resource is declared in user space. + * + * @param string $resourceName + * + * @return boolean + */ + private function isResourceDeclared($resourceName){ + return class_exists($resourceName, false) + || interface_exists($resourceName, false) + || (function_exists('trait_exists') && trait_exists($resourceName, false)); + } +}