mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-05-17 11:18:52 +00:00
Merge branch 'minor-next' into major-next
This commit is contained in:
commit
d98adf127f
12
composer.lock
generated
12
composer.lock
generated
@ -1937,16 +1937,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpunit/phpunit",
|
"name": "phpunit/phpunit",
|
||||||
"version": "10.2.3",
|
"version": "10.2.5",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/sebastianbergmann/phpunit.git",
|
"url": "https://github.com/sebastianbergmann/phpunit.git",
|
||||||
"reference": "35c8cac1734ede2ae354a6644f7088356ff5b08e"
|
"reference": "15a89f123d8ca9c1e1598d6d87a56a8bf28c72cd"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/35c8cac1734ede2ae354a6644f7088356ff5b08e",
|
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/15a89f123d8ca9c1e1598d6d87a56a8bf28c72cd",
|
||||||
"reference": "35c8cac1734ede2ae354a6644f7088356ff5b08e",
|
"reference": "15a89f123d8ca9c1e1598d6d87a56a8bf28c72cd",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@ -2018,7 +2018,7 @@
|
|||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
|
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
|
||||||
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
|
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
|
||||||
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.2.3"
|
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.2.5"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@ -2034,7 +2034,7 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2023-06-30T06:17:38+00:00"
|
"time": "2023-07-14T04:18:47+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "sebastian/cli-parser",
|
"name": "sebastian/cli-parser",
|
||||||
|
@ -23,9 +23,9 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace pocketmine\block;
|
namespace pocketmine\block;
|
||||||
|
|
||||||
|
use pocketmine\block\utils\FortuneDropHelper;
|
||||||
use pocketmine\item\Item;
|
use pocketmine\item\Item;
|
||||||
use pocketmine\item\VanillaItems;
|
use pocketmine\item\VanillaItems;
|
||||||
use function mt_rand;
|
|
||||||
|
|
||||||
class Beetroot extends Crops{
|
class Beetroot extends Crops{
|
||||||
|
|
||||||
@ -33,7 +33,7 @@ class Beetroot extends Crops{
|
|||||||
if($this->age >= self::MAX_AGE){
|
if($this->age >= self::MAX_AGE){
|
||||||
return [
|
return [
|
||||||
VanillaItems::BEETROOT(),
|
VanillaItems::BEETROOT(),
|
||||||
VanillaItems::BEETROOT_SEEDS()->setCount(mt_rand(0, 3))
|
VanillaItems::BEETROOT_SEEDS()->setCount(FortuneDropHelper::binomial($item, 0))
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,15 +23,15 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace pocketmine\block;
|
namespace pocketmine\block;
|
||||||
|
|
||||||
|
use pocketmine\block\utils\FortuneDropHelper;
|
||||||
use pocketmine\item\Item;
|
use pocketmine\item\Item;
|
||||||
use pocketmine\item\VanillaItems;
|
use pocketmine\item\VanillaItems;
|
||||||
use function mt_rand;
|
|
||||||
|
|
||||||
class Carrot extends Crops{
|
class Carrot extends Crops{
|
||||||
|
|
||||||
public function getDropsForCompatibleTool(Item $item) : array{
|
public function getDropsForCompatibleTool(Item $item) : array{
|
||||||
return [
|
return [
|
||||||
VanillaItems::CARROT()->setCount($this->age >= self::MAX_AGE ? mt_rand(1, 4) : 1)
|
VanillaItems::CARROT()->setCount($this->age >= self::MAX_AGE ? FortuneDropHelper::binomial($item, 1) : 1)
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace pocketmine\block;
|
namespace pocketmine\block;
|
||||||
|
|
||||||
|
use pocketmine\block\utils\FortuneDropHelper;
|
||||||
use pocketmine\item\Item;
|
use pocketmine\item\Item;
|
||||||
use pocketmine\item\VanillaItems;
|
use pocketmine\item\VanillaItems;
|
||||||
use function mt_rand;
|
use function mt_rand;
|
||||||
@ -31,7 +32,7 @@ class CoalOre extends Opaque{
|
|||||||
|
|
||||||
public function getDropsForCompatibleTool(Item $item) : array{
|
public function getDropsForCompatibleTool(Item $item) : array{
|
||||||
return [
|
return [
|
||||||
VanillaItems::COAL()
|
VanillaItems::COAL()->setCount(FortuneDropHelper::weighted($item, min: 1, maxBase: 1))
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,13 +23,16 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace pocketmine\block;
|
namespace pocketmine\block;
|
||||||
|
|
||||||
|
use pocketmine\block\utils\FortuneDropHelper;
|
||||||
use pocketmine\item\Item;
|
use pocketmine\item\Item;
|
||||||
use pocketmine\item\VanillaItems;
|
use pocketmine\item\VanillaItems;
|
||||||
|
|
||||||
final class CopperOre extends Opaque{
|
final class CopperOre extends Opaque{
|
||||||
|
|
||||||
public function getDropsForCompatibleTool(Item $item) : array{
|
public function getDropsForCompatibleTool(Item $item) : array{
|
||||||
return [VanillaItems::RAW_COPPER()];
|
return [
|
||||||
|
VanillaItems::RAW_COPPER()->setCount(FortuneDropHelper::weighted($item, min: 2, maxBase: 5)),
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isAffectedBySilkTouch() : bool{ return true; }
|
public function isAffectedBySilkTouch() : bool{ return true; }
|
||||||
|
@ -23,6 +23,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace pocketmine\block;
|
namespace pocketmine\block;
|
||||||
|
|
||||||
|
use pocketmine\block\utils\FortuneDropHelper;
|
||||||
use pocketmine\item\Item;
|
use pocketmine\item\Item;
|
||||||
use pocketmine\item\VanillaItems;
|
use pocketmine\item\VanillaItems;
|
||||||
use function mt_rand;
|
use function mt_rand;
|
||||||
@ -31,7 +32,7 @@ class DiamondOre extends Opaque{
|
|||||||
|
|
||||||
public function getDropsForCompatibleTool(Item $item) : array{
|
public function getDropsForCompatibleTool(Item $item) : array{
|
||||||
return [
|
return [
|
||||||
VanillaItems::DIAMOND()
|
VanillaItems::DIAMOND()->setCount(FortuneDropHelper::weighted($item, min: 1, maxBase: 1))
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,9 +23,8 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace pocketmine\block;
|
namespace pocketmine\block;
|
||||||
|
|
||||||
|
use pocketmine\block\utils\FortuneDropHelper;
|
||||||
use pocketmine\item\Item;
|
use pocketmine\item\Item;
|
||||||
use pocketmine\item\VanillaItems;
|
|
||||||
use function mt_rand;
|
|
||||||
|
|
||||||
class DoubleTallGrass extends DoublePlant{
|
class DoubleTallGrass extends DoublePlant{
|
||||||
|
|
||||||
@ -34,8 +33,8 @@ class DoubleTallGrass extends DoublePlant{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function getDropsForIncompatibleTool(Item $item) : array{
|
public function getDropsForIncompatibleTool(Item $item) : array{
|
||||||
if($this->top && mt_rand(0, 7) === 0){
|
if($this->top){
|
||||||
return [VanillaItems::WHEAT_SEEDS()];
|
return FortuneDropHelper::grass($item);
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace pocketmine\block;
|
namespace pocketmine\block;
|
||||||
|
|
||||||
|
use pocketmine\block\utils\FortuneDropHelper;
|
||||||
use pocketmine\item\Item;
|
use pocketmine\item\Item;
|
||||||
use pocketmine\item\VanillaItems;
|
use pocketmine\item\VanillaItems;
|
||||||
use function mt_rand;
|
use function mt_rand;
|
||||||
@ -31,7 +32,7 @@ class EmeraldOre extends Opaque{
|
|||||||
|
|
||||||
public function getDropsForCompatibleTool(Item $item) : array{
|
public function getDropsForCompatibleTool(Item $item) : array{
|
||||||
return [
|
return [
|
||||||
VanillaItems::EMERALD()
|
VanillaItems::EMERALD()->setCount(FortuneDropHelper::weighted($item, min: 1, maxBase: 1))
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace pocketmine\block;
|
namespace pocketmine\block;
|
||||||
|
|
||||||
|
use pocketmine\block\utils\FortuneDropHelper;
|
||||||
use pocketmine\item\Item;
|
use pocketmine\item\Item;
|
||||||
use pocketmine\item\VanillaItems;
|
use pocketmine\item\VanillaItems;
|
||||||
use function mt_rand;
|
use function mt_rand;
|
||||||
@ -30,7 +31,7 @@ use function mt_rand;
|
|||||||
final class GildedBlackstone extends Opaque{
|
final class GildedBlackstone extends Opaque{
|
||||||
|
|
||||||
public function getDropsForCompatibleTool(Item $item) : array{
|
public function getDropsForCompatibleTool(Item $item) : array{
|
||||||
if(mt_rand(1, 10) === 1){
|
if(FortuneDropHelper::bonusChanceDivisor($item, 10, 3)){
|
||||||
return [VanillaItems::GOLD_NUGGET()->setCount(mt_rand(2, 5))];
|
return [VanillaItems::GOLD_NUGGET()->setCount(mt_rand(2, 5))];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,9 +23,10 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace pocketmine\block;
|
namespace pocketmine\block;
|
||||||
|
|
||||||
|
use pocketmine\block\utils\FortuneDropHelper;
|
||||||
use pocketmine\item\Item;
|
use pocketmine\item\Item;
|
||||||
use pocketmine\item\VanillaItems;
|
use pocketmine\item\VanillaItems;
|
||||||
use function mt_rand;
|
use function min;
|
||||||
|
|
||||||
class Glowstone extends Transparent{
|
class Glowstone extends Transparent{
|
||||||
|
|
||||||
@ -35,7 +36,7 @@ class Glowstone extends Transparent{
|
|||||||
|
|
||||||
public function getDropsForCompatibleTool(Item $item) : array{
|
public function getDropsForCompatibleTool(Item $item) : array{
|
||||||
return [
|
return [
|
||||||
VanillaItems::GLOWSTONE_DUST()->setCount(mt_rand(2, 4))
|
VanillaItems::GLOWSTONE_DUST()->setCount(min(4, FortuneDropHelper::discrete($item, 2, 4)))
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,15 +25,15 @@ namespace pocketmine\block;
|
|||||||
|
|
||||||
use pocketmine\block\utils\Fallable;
|
use pocketmine\block\utils\Fallable;
|
||||||
use pocketmine\block\utils\FallableTrait;
|
use pocketmine\block\utils\FallableTrait;
|
||||||
|
use pocketmine\block\utils\FortuneDropHelper;
|
||||||
use pocketmine\item\Item;
|
use pocketmine\item\Item;
|
||||||
use pocketmine\item\VanillaItems;
|
use pocketmine\item\VanillaItems;
|
||||||
use function mt_rand;
|
|
||||||
|
|
||||||
class Gravel extends Opaque implements Fallable{
|
class Gravel extends Opaque implements Fallable{
|
||||||
use FallableTrait;
|
use FallableTrait;
|
||||||
|
|
||||||
public function getDropsForCompatibleTool(Item $item) : array{
|
public function getDropsForCompatibleTool(Item $item) : array{
|
||||||
if(mt_rand(1, 10) === 1){
|
if(FortuneDropHelper::bonusChanceDivisor($item, 10, 3)){
|
||||||
return [
|
return [
|
||||||
VanillaItems::FLINT()
|
VanillaItems::FLINT()
|
||||||
];
|
];
|
||||||
|
@ -23,6 +23,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace pocketmine\block;
|
namespace pocketmine\block;
|
||||||
|
|
||||||
|
use pocketmine\block\utils\FortuneDropHelper;
|
||||||
use pocketmine\item\Item;
|
use pocketmine\item\Item;
|
||||||
use pocketmine\item\VanillaItems;
|
use pocketmine\item\VanillaItems;
|
||||||
use function mt_rand;
|
use function mt_rand;
|
||||||
@ -31,7 +32,7 @@ class LapisOre extends Opaque{
|
|||||||
|
|
||||||
public function getDropsForCompatibleTool(Item $item) : array{
|
public function getDropsForCompatibleTool(Item $item) : array{
|
||||||
return [
|
return [
|
||||||
VanillaItems::LAPIS_LAZULI()->setCount(mt_rand(4, 8))
|
VanillaItems::LAPIS_LAZULI()->setCount(FortuneDropHelper::weighted($item, min: 4, maxBase: 9))
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace pocketmine\block;
|
namespace pocketmine\block;
|
||||||
|
|
||||||
|
use pocketmine\block\utils\FortuneDropHelper;
|
||||||
use pocketmine\block\utils\LeavesType;
|
use pocketmine\block\utils\LeavesType;
|
||||||
use pocketmine\block\utils\SupportType;
|
use pocketmine\block\utils\SupportType;
|
||||||
use pocketmine\data\runtime\RuntimeDataDescriber;
|
use pocketmine\data\runtime\RuntimeDataDescriber;
|
||||||
@ -138,7 +139,8 @@ class Leaves extends Transparent{
|
|||||||
}
|
}
|
||||||
|
|
||||||
$drops = [];
|
$drops = [];
|
||||||
if(mt_rand(1, 20) === 1){ //Saplings
|
if(FortuneDropHelper::bonusChanceDivisor($item, 20, 4)){ //Saplings
|
||||||
|
// TODO: according to the wiki, the jungle saplings have a different drop rate
|
||||||
$sapling = (match($this->leavesType){
|
$sapling = (match($this->leavesType){
|
||||||
LeavesType::ACACIA() => VanillaBlocks::ACACIA_SAPLING(),
|
LeavesType::ACACIA() => VanillaBlocks::ACACIA_SAPLING(),
|
||||||
LeavesType::BIRCH() => VanillaBlocks::BIRCH_SAPLING(),
|
LeavesType::BIRCH() => VanillaBlocks::BIRCH_SAPLING(),
|
||||||
@ -155,10 +157,13 @@ class Leaves extends Transparent{
|
|||||||
$drops[] = $sapling;
|
$drops[] = $sapling;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(($this->leavesType->equals(LeavesType::OAK()) || $this->leavesType->equals(LeavesType::DARK_OAK())) && mt_rand(1, 200) === 1){ //Apples
|
if(
|
||||||
|
($this->leavesType->equals(LeavesType::OAK()) || $this->leavesType->equals(LeavesType::DARK_OAK())) &&
|
||||||
|
FortuneDropHelper::bonusChanceDivisor($item, 200, 20)
|
||||||
|
){ //Apples
|
||||||
$drops[] = VanillaItems::APPLE();
|
$drops[] = VanillaItems::APPLE();
|
||||||
}
|
}
|
||||||
if(mt_rand(1, 50) === 1){
|
if(FortuneDropHelper::bonusChanceDivisor($item, 50, 5)){
|
||||||
$drops[] = VanillaItems::STICK()->setCount(mt_rand(1, 2));
|
$drops[] = VanillaItems::STICK()->setCount(mt_rand(1, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,15 +23,16 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace pocketmine\block;
|
namespace pocketmine\block;
|
||||||
|
|
||||||
|
use pocketmine\block\utils\FortuneDropHelper;
|
||||||
use pocketmine\item\Item;
|
use pocketmine\item\Item;
|
||||||
use pocketmine\item\VanillaItems;
|
use pocketmine\item\VanillaItems;
|
||||||
use function mt_rand;
|
use function min;
|
||||||
|
|
||||||
class Melon extends Opaque{
|
class Melon extends Opaque{
|
||||||
|
|
||||||
public function getDropsForCompatibleTool(Item $item) : array{
|
public function getDropsForCompatibleTool(Item $item) : array{
|
||||||
return [
|
return [
|
||||||
VanillaItems::MELON()->setCount(mt_rand(3, 7))
|
VanillaItems::MELON()->setCount(min(9, FortuneDropHelper::discrete($item, 3, 7)))
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,14 +23,14 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace pocketmine\block;
|
namespace pocketmine\block;
|
||||||
|
|
||||||
|
use pocketmine\block\utils\FortuneDropHelper;
|
||||||
use pocketmine\item\Item;
|
use pocketmine\item\Item;
|
||||||
use pocketmine\item\VanillaItems;
|
use pocketmine\item\VanillaItems;
|
||||||
use function mt_rand;
|
|
||||||
|
|
||||||
final class NetherGoldOre extends Opaque{
|
final class NetherGoldOre extends Opaque{
|
||||||
|
|
||||||
public function getDropsForCompatibleTool(Item $item) : array{
|
public function getDropsForCompatibleTool(Item $item) : array{
|
||||||
return [VanillaItems::GOLD_NUGGET()->setCount(mt_rand(2, 6))];
|
return [VanillaItems::GOLD_NUGGET()->setCount(FortuneDropHelper::weighted($item, min: 2, maxBase: 6))];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isAffectedBySilkTouch() : bool{ return true; }
|
public function isAffectedBySilkTouch() : bool{ return true; }
|
||||||
|
@ -23,6 +23,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace pocketmine\block;
|
namespace pocketmine\block;
|
||||||
|
|
||||||
|
use pocketmine\block\utils\FortuneDropHelper;
|
||||||
use pocketmine\item\Item;
|
use pocketmine\item\Item;
|
||||||
use pocketmine\item\VanillaItems;
|
use pocketmine\item\VanillaItems;
|
||||||
use function mt_rand;
|
use function mt_rand;
|
||||||
@ -31,7 +32,7 @@ class NetherQuartzOre extends Opaque{
|
|||||||
|
|
||||||
public function getDropsForCompatibleTool(Item $item) : array{
|
public function getDropsForCompatibleTool(Item $item) : array{
|
||||||
return [
|
return [
|
||||||
VanillaItems::NETHER_QUARTZ()
|
VanillaItems::NETHER_QUARTZ()->setCount(FortuneDropHelper::weighted($item, min: 1, maxBase: 1))
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace pocketmine\block;
|
namespace pocketmine\block;
|
||||||
|
|
||||||
|
use pocketmine\block\utils\FortuneDropHelper;
|
||||||
use pocketmine\block\utils\SupportType;
|
use pocketmine\block\utils\SupportType;
|
||||||
use pocketmine\data\runtime\RuntimeDataDescriber;
|
use pocketmine\data\runtime\RuntimeDataDescriber;
|
||||||
use pocketmine\entity\Entity;
|
use pocketmine\entity\Entity;
|
||||||
@ -179,7 +180,7 @@ class NetherVines extends Flowable{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function getDropsForCompatibleTool(Item $item) : array{
|
public function getDropsForCompatibleTool(Item $item) : array{
|
||||||
if(($item->getBlockToolType() & BlockToolType::SHEARS) !== 0 || mt_rand(1, 3) === 1){
|
if(($item->getBlockToolType() & BlockToolType::SHEARS) !== 0 || FortuneDropHelper::bonusChanceFixed($item, 1 / 3, 2 / 9)){
|
||||||
return [$this->asItem()];
|
return [$this->asItem()];
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
|
@ -23,6 +23,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace pocketmine\block;
|
namespace pocketmine\block;
|
||||||
|
|
||||||
|
use pocketmine\block\utils\FortuneDropHelper;
|
||||||
use pocketmine\data\runtime\RuntimeDataDescriber;
|
use pocketmine\data\runtime\RuntimeDataDescriber;
|
||||||
use pocketmine\event\block\BlockGrowEvent;
|
use pocketmine\event\block\BlockGrowEvent;
|
||||||
use pocketmine\item\Item;
|
use pocketmine\item\Item;
|
||||||
@ -85,7 +86,7 @@ class NetherWartPlant extends Flowable{
|
|||||||
|
|
||||||
public function getDropsForCompatibleTool(Item $item) : array{
|
public function getDropsForCompatibleTool(Item $item) : array{
|
||||||
return [
|
return [
|
||||||
$this->asItem()->setCount($this->age === self::MAX_AGE ? mt_rand(2, 4) : 1)
|
$this->asItem()->setCount($this->age === self::MAX_AGE ? FortuneDropHelper::discrete($item, 2, 4) : 1)
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace pocketmine\block;
|
namespace pocketmine\block;
|
||||||
|
|
||||||
|
use pocketmine\block\utils\FortuneDropHelper;
|
||||||
use pocketmine\item\Item;
|
use pocketmine\item\Item;
|
||||||
use pocketmine\item\VanillaItems;
|
use pocketmine\item\VanillaItems;
|
||||||
use function mt_rand;
|
use function mt_rand;
|
||||||
@ -31,7 +32,8 @@ class Potato extends Crops{
|
|||||||
|
|
||||||
public function getDropsForCompatibleTool(Item $item) : array{
|
public function getDropsForCompatibleTool(Item $item) : array{
|
||||||
$result = [
|
$result = [
|
||||||
VanillaItems::POTATO()->setCount($this->age >= self::MAX_AGE ? mt_rand(1, 5) : 1)
|
//min/max would be 2-5 in Java
|
||||||
|
VanillaItems::POTATO()->setCount($this->age >= self::MAX_AGE ? FortuneDropHelper::binomial($item, 1) : 1)
|
||||||
];
|
];
|
||||||
if($this->age >= self::MAX_AGE && mt_rand(0, 49) === 0){
|
if($this->age >= self::MAX_AGE && mt_rand(0, 49) === 0){
|
||||||
$result[] = VanillaItems::POISONOUS_POTATO();
|
$result[] = VanillaItems::POISONOUS_POTATO();
|
||||||
|
@ -23,6 +23,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace pocketmine\block;
|
namespace pocketmine\block;
|
||||||
|
|
||||||
|
use pocketmine\block\utils\FortuneDropHelper;
|
||||||
use pocketmine\data\runtime\RuntimeDataDescriber;
|
use pocketmine\data\runtime\RuntimeDataDescriber;
|
||||||
use pocketmine\item\Item;
|
use pocketmine\item\Item;
|
||||||
use pocketmine\item\VanillaItems;
|
use pocketmine\item\VanillaItems;
|
||||||
@ -81,7 +82,7 @@ class RedstoneOre extends Opaque{
|
|||||||
|
|
||||||
public function getDropsForCompatibleTool(Item $item) : array{
|
public function getDropsForCompatibleTool(Item $item) : array{
|
||||||
return [
|
return [
|
||||||
VanillaItems::REDSTONE_DUST()->setCount(mt_rand(4, 5))
|
VanillaItems::REDSTONE_DUST()->setCount(FortuneDropHelper::discrete($item, 4, 5))
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,8 +23,10 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace pocketmine\block;
|
namespace pocketmine\block;
|
||||||
|
|
||||||
|
use pocketmine\block\utils\FortuneDropHelper;
|
||||||
use pocketmine\item\Item;
|
use pocketmine\item\Item;
|
||||||
use pocketmine\item\VanillaItems;
|
use pocketmine\item\VanillaItems;
|
||||||
|
use function min;
|
||||||
|
|
||||||
class SeaLantern extends Transparent{
|
class SeaLantern extends Transparent{
|
||||||
|
|
||||||
@ -34,7 +36,7 @@ class SeaLantern extends Transparent{
|
|||||||
|
|
||||||
public function getDropsForCompatibleTool(Item $item) : array{
|
public function getDropsForCompatibleTool(Item $item) : array{
|
||||||
return [
|
return [
|
||||||
VanillaItems::PRISMARINE_CRYSTALS()->setCount(3)
|
VanillaItems::PRISMARINE_CRYSTALS()->setCount(min(5, FortuneDropHelper::discrete($item, 2, 3)))
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace pocketmine\block;
|
namespace pocketmine\block;
|
||||||
|
|
||||||
|
use pocketmine\block\utils\FortuneDropHelper;
|
||||||
use pocketmine\data\runtime\RuntimeDataDescriber;
|
use pocketmine\data\runtime\RuntimeDataDescriber;
|
||||||
use pocketmine\entity\Entity;
|
use pocketmine\entity\Entity;
|
||||||
use pocketmine\entity\Living;
|
use pocketmine\entity\Living;
|
||||||
@ -108,13 +109,14 @@ class SweetBerryBush extends Flowable{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function getDropsForCompatibleTool(Item $item) : array{
|
public function getDropsForCompatibleTool(Item $item) : array{
|
||||||
if(($dropAmount = $this->getBerryDropAmount()) > 0){
|
$count = match($this->age){
|
||||||
return [
|
self::STAGE_MATURE => FortuneDropHelper::discrete($item, 2, 3),
|
||||||
$this->asItem()->setCount($dropAmount)
|
self::STAGE_BUSH_SOME_BERRIES => FortuneDropHelper::discrete($item, 1, 2),
|
||||||
];
|
default => 0
|
||||||
}
|
};
|
||||||
|
return [
|
||||||
return [];
|
$this->asItem()->setCount($count)
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function onNearbyBlockChange() : void{
|
public function onNearbyBlockChange() : void{
|
||||||
|
@ -23,13 +23,12 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace pocketmine\block;
|
namespace pocketmine\block;
|
||||||
|
|
||||||
|
use pocketmine\block\utils\FortuneDropHelper;
|
||||||
use pocketmine\item\Item;
|
use pocketmine\item\Item;
|
||||||
use pocketmine\item\VanillaItems;
|
|
||||||
use pocketmine\math\Facing;
|
use pocketmine\math\Facing;
|
||||||
use pocketmine\math\Vector3;
|
use pocketmine\math\Vector3;
|
||||||
use pocketmine\player\Player;
|
use pocketmine\player\Player;
|
||||||
use pocketmine\world\BlockTransaction;
|
use pocketmine\world\BlockTransaction;
|
||||||
use function mt_rand;
|
|
||||||
|
|
||||||
class TallGrass extends Flowable{
|
class TallGrass extends Flowable{
|
||||||
|
|
||||||
@ -56,13 +55,7 @@ class TallGrass extends Flowable{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function getDropsForIncompatibleTool(Item $item) : array{
|
public function getDropsForIncompatibleTool(Item $item) : array{
|
||||||
if(mt_rand(0, 15) === 0){
|
return FortuneDropHelper::grass($item);
|
||||||
return [
|
|
||||||
VanillaItems::WHEAT_SEEDS()
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getFlameEncouragement() : int{
|
public function getFlameEncouragement() : int{
|
||||||
|
@ -23,9 +23,9 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace pocketmine\block;
|
namespace pocketmine\block;
|
||||||
|
|
||||||
|
use pocketmine\block\utils\FortuneDropHelper;
|
||||||
use pocketmine\item\Item;
|
use pocketmine\item\Item;
|
||||||
use pocketmine\item\VanillaItems;
|
use pocketmine\item\VanillaItems;
|
||||||
use function mt_rand;
|
|
||||||
|
|
||||||
class Wheat extends Crops{
|
class Wheat extends Crops{
|
||||||
|
|
||||||
@ -33,7 +33,7 @@ class Wheat extends Crops{
|
|||||||
if($this->age >= self::MAX_AGE){
|
if($this->age >= self::MAX_AGE){
|
||||||
return [
|
return [
|
||||||
VanillaItems::WHEAT(),
|
VanillaItems::WHEAT(),
|
||||||
VanillaItems::WHEAT_SEEDS()->setCount(mt_rand(0, 3))
|
VanillaItems::WHEAT_SEEDS()->setCount(FortuneDropHelper::binomial($item, 0))
|
||||||
];
|
];
|
||||||
}else{
|
}else{
|
||||||
return [
|
return [
|
||||||
|
150
src/block/utils/FortuneDropHelper.php
Normal file
150
src/block/utils/FortuneDropHelper.php
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* ____ _ _ __ __ _ __ __ ____
|
||||||
|
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||||
|
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||||
|
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||||
|
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* @author PocketMine Team
|
||||||
|
* @link http://www.pocketmine.net/
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace pocketmine\block\utils;
|
||||||
|
|
||||||
|
use pocketmine\item\enchantment\VanillaEnchantments;
|
||||||
|
use pocketmine\item\Item;
|
||||||
|
use pocketmine\item\VanillaItems;
|
||||||
|
use function max;
|
||||||
|
use function min;
|
||||||
|
use function mt_getrandmax;
|
||||||
|
use function mt_rand;
|
||||||
|
|
||||||
|
final class FortuneDropHelper{
|
||||||
|
/**
|
||||||
|
* If a random number between 0-1 is greater than 2/(level+2), this multiplies the max drop amount by level+1, and
|
||||||
|
* picks a random amount between the minimum and multiplied maximum. Each level of fortune increases the chance of
|
||||||
|
* fortune activation, and also increases the maximum drop limit when activated.
|
||||||
|
*
|
||||||
|
* Otherwise, returns a random amount of the item between the minimum and original maximum.
|
||||||
|
*
|
||||||
|
* @param Item $usedItem The item used to break the block
|
||||||
|
* @param int $min Minimum amount
|
||||||
|
* @param int $maxBase Maximum amount, as if fortune level was 0
|
||||||
|
*
|
||||||
|
* @return int the number of items to drop
|
||||||
|
*/
|
||||||
|
public static function weighted(Item $usedItem, int $min, int $maxBase) : int{
|
||||||
|
if($maxBase < $min){
|
||||||
|
throw new \InvalidArgumentException("Maximum drop amount must be greater than or equal to minimum drop amount");
|
||||||
|
}
|
||||||
|
|
||||||
|
$fortuneLevel = $usedItem->getEnchantmentLevel(VanillaEnchantments::FORTUNE());
|
||||||
|
|
||||||
|
return mt_rand($min,
|
||||||
|
$fortuneLevel > 0 && mt_rand() / mt_getrandmax() > 2 / ($fortuneLevel + 2) ?
|
||||||
|
$maxBase * ($fortuneLevel + 1) :
|
||||||
|
$maxBase
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increases the drop amount according to a binomial distribution. The function will roll maxBase+level times, and add 1
|
||||||
|
* if a random number between 0-1 is less than the given probability. Each level of fortune adds one extra roll.
|
||||||
|
*
|
||||||
|
* As many as maxBase+level items can be dropped. This applies even if the fortune level is 0.
|
||||||
|
*
|
||||||
|
* @param float $chance The chance of adding 1 to the amount for each roll, must be in the range 0-1
|
||||||
|
* @param int $min Minimum amount
|
||||||
|
* @param int $minRolls Number of rolls if fortune level is 0, added to fortune level to calculate total rolls
|
||||||
|
*
|
||||||
|
* @return int the number of items to drop
|
||||||
|
*/
|
||||||
|
public static function binomial(Item $usedItem, int $min, int $minRolls = 3, float $chance = 4 / 7) : int{
|
||||||
|
$fortuneLevel = $usedItem->getEnchantmentLevel(VanillaEnchantments::FORTUNE());
|
||||||
|
|
||||||
|
$count = $min;
|
||||||
|
$rolls = $minRolls + $fortuneLevel;
|
||||||
|
for($i = 0; $i < $rolls; ++$i){
|
||||||
|
if(mt_rand() / mt_getrandmax() < $chance){
|
||||||
|
++$count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Grass have a fixed chance to drop wheat seed.
|
||||||
|
* Fortune level increases the maximum number of seeds that can be dropped.
|
||||||
|
* A discrete uniform distribution is used to determine the number of seeds dropped.
|
||||||
|
*
|
||||||
|
* TODO: I'm not sure this really belongs here, but it's preferable not to duplicate this code between grass and
|
||||||
|
* tall grass.
|
||||||
|
*
|
||||||
|
* @return Item[]
|
||||||
|
*/
|
||||||
|
public static function grass(Item $usedItem) : array{
|
||||||
|
if(FortuneDropHelper::bonusChanceDivisor($usedItem, 8, 2)){
|
||||||
|
return [
|
||||||
|
VanillaItems::WHEAT_SEEDS()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the fortune level to the base max and picks a random number between the minimim and adjusted maximum.
|
||||||
|
* Each amount in the range has an equal chance of being picked.
|
||||||
|
*
|
||||||
|
* @param int $maxBase Maximum base amount, as if the fortune level was 0
|
||||||
|
*
|
||||||
|
* @return int the number of items to drop
|
||||||
|
*/
|
||||||
|
public static function discrete(Item $usedItem, int $min, int $maxBase) : int{
|
||||||
|
if($maxBase < $min){
|
||||||
|
throw new \InvalidArgumentException("Minimum base drop amount must be less than or equal to maximum base drop amount");
|
||||||
|
}
|
||||||
|
|
||||||
|
$max = $maxBase + $usedItem->getEnchantmentLevel(VanillaEnchantments::FORTUNE());
|
||||||
|
return mt_rand($min, $max);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates a chance of getting an extra bonus drop by reducing the chance divisor by a given amount per fortune
|
||||||
|
* level.
|
||||||
|
*
|
||||||
|
* @param int $divisorBase The number to divide 1 by to get the chance, as if the fortune level was 0
|
||||||
|
* @param int $divisorSubtractPerLevel The amount to subtract from the divisor for each level of fortune
|
||||||
|
*
|
||||||
|
* @return bool whether the bonus drop should be added
|
||||||
|
*/
|
||||||
|
public static function bonusChanceDivisor(Item $usedItem, int $divisorBase, int $divisorSubtractPerLevel) : bool{
|
||||||
|
$fortuneLevel = $usedItem->getEnchantmentLevel(VanillaEnchantments::FORTUNE());
|
||||||
|
return mt_rand(1, max(1, $divisorBase - ($fortuneLevel * $divisorSubtractPerLevel))) === 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates a chance of getting an extra bonus drop by increasing the chance by a fixed amount per fortune level.
|
||||||
|
*
|
||||||
|
* @param float $chanceBase The base chance of getting a bonus drop, as if the fortune level was 0
|
||||||
|
* @param float $addedChancePerLevel The amount to add to the chance for each level of fortune
|
||||||
|
*/
|
||||||
|
public static function bonusChanceFixed(Item $usedItem, float $chanceBase, float $addedChancePerLevel) : bool{
|
||||||
|
$fortuneLevel = $usedItem->getEnchantmentLevel(VanillaEnchantments::FORTUNE());
|
||||||
|
$chance = min(1, $chanceBase + ($fortuneLevel * $addedChancePerLevel));
|
||||||
|
return mt_rand() / mt_getrandmax() < $chance;
|
||||||
|
}
|
||||||
|
}
|
@ -62,6 +62,7 @@ final class EnchantmentIdMap{
|
|||||||
$this->register(EnchantmentIds::FIRE_ASPECT, VanillaEnchantments::FIRE_ASPECT());
|
$this->register(EnchantmentIds::FIRE_ASPECT, VanillaEnchantments::FIRE_ASPECT());
|
||||||
|
|
||||||
$this->register(EnchantmentIds::EFFICIENCY, VanillaEnchantments::EFFICIENCY());
|
$this->register(EnchantmentIds::EFFICIENCY, VanillaEnchantments::EFFICIENCY());
|
||||||
|
$this->register(EnchantmentIds::FORTUNE, VanillaEnchantments::FORTUNE());
|
||||||
$this->register(EnchantmentIds::SILK_TOUCH, VanillaEnchantments::SILK_TOUCH());
|
$this->register(EnchantmentIds::SILK_TOUCH, VanillaEnchantments::SILK_TOUCH());
|
||||||
$this->register(EnchantmentIds::UNBREAKING, VanillaEnchantments::UNBREAKING());
|
$this->register(EnchantmentIds::UNBREAKING, VanillaEnchantments::UNBREAKING());
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace pocketmine\entity\animation;
|
namespace pocketmine\entity\animation;
|
||||||
|
|
||||||
use pocketmine\entity\Human;
|
use pocketmine\entity\Living;
|
||||||
use pocketmine\item\Item;
|
use pocketmine\item\Item;
|
||||||
use pocketmine\network\mcpe\convert\TypeConverter;
|
use pocketmine\network\mcpe\convert\TypeConverter;
|
||||||
use pocketmine\network\mcpe\protocol\ActorEventPacket;
|
use pocketmine\network\mcpe\protocol\ActorEventPacket;
|
||||||
@ -32,7 +32,7 @@ use pocketmine\network\mcpe\protocol\types\ActorEvent;
|
|||||||
final class ConsumingItemAnimation implements Animation{
|
final class ConsumingItemAnimation implements Animation{
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private Human $human, //TODO: maybe this can be expanded to more than just player entities?
|
private Living $entity,
|
||||||
private Item $item
|
private Item $item
|
||||||
){}
|
){}
|
||||||
|
|
||||||
@ -40,7 +40,7 @@ final class ConsumingItemAnimation implements Animation{
|
|||||||
[$netId, $netData] = TypeConverter::getInstance()->getItemTranslator()->toNetworkId($this->item);
|
[$netId, $netData] = TypeConverter::getInstance()->getItemTranslator()->toNetworkId($this->item);
|
||||||
return [
|
return [
|
||||||
//TODO: need to check the data values
|
//TODO: need to check the data values
|
||||||
ActorEventPacket::create($this->human->getId(), ActorEvent::EATING_ITEM, ($netId << 16) | $netData)
|
ActorEventPacket::create($this->entity->getId(), ActorEvent::EATING_ITEM, ($netId << 16) | $netData)
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,6 +43,7 @@ final class StringToEnchantmentParser extends StringToTParser{
|
|||||||
$result->register("fire_aspect", fn() => VanillaEnchantments::FIRE_ASPECT());
|
$result->register("fire_aspect", fn() => VanillaEnchantments::FIRE_ASPECT());
|
||||||
$result->register("fire_protection", fn() => VanillaEnchantments::FIRE_PROTECTION());
|
$result->register("fire_protection", fn() => VanillaEnchantments::FIRE_PROTECTION());
|
||||||
$result->register("flame", fn() => VanillaEnchantments::FLAME());
|
$result->register("flame", fn() => VanillaEnchantments::FLAME());
|
||||||
|
$result->register("fortune", fn() => VanillaEnchantments::FORTUNE());
|
||||||
$result->register("infinity", fn() => VanillaEnchantments::INFINITY());
|
$result->register("infinity", fn() => VanillaEnchantments::INFINITY());
|
||||||
$result->register("knockback", fn() => VanillaEnchantments::KNOCKBACK());
|
$result->register("knockback", fn() => VanillaEnchantments::KNOCKBACK());
|
||||||
$result->register("mending", fn() => VanillaEnchantments::MENDING());
|
$result->register("mending", fn() => VanillaEnchantments::MENDING());
|
||||||
|
@ -39,6 +39,7 @@ use pocketmine\utils\RegistryTrait;
|
|||||||
* @method static FireAspectEnchantment FIRE_ASPECT()
|
* @method static FireAspectEnchantment FIRE_ASPECT()
|
||||||
* @method static ProtectionEnchantment FIRE_PROTECTION()
|
* @method static ProtectionEnchantment FIRE_PROTECTION()
|
||||||
* @method static Enchantment FLAME()
|
* @method static Enchantment FLAME()
|
||||||
|
* @method static Enchantment FORTUNE()
|
||||||
* @method static Enchantment INFINITY()
|
* @method static Enchantment INFINITY()
|
||||||
* @method static KnockbackEnchantment KNOCKBACK()
|
* @method static KnockbackEnchantment KNOCKBACK()
|
||||||
* @method static Enchantment MENDING()
|
* @method static Enchantment MENDING()
|
||||||
@ -85,6 +86,7 @@ final class VanillaEnchantments{
|
|||||||
self::register("FIRE_ASPECT", new FireAspectEnchantment(KnownTranslationFactory::enchantment_fire(), Rarity::RARE, ItemFlags::SWORD, ItemFlags::NONE, 2));
|
self::register("FIRE_ASPECT", new FireAspectEnchantment(KnownTranslationFactory::enchantment_fire(), Rarity::RARE, ItemFlags::SWORD, ItemFlags::NONE, 2));
|
||||||
|
|
||||||
self::register("EFFICIENCY", new Enchantment(KnownTranslationFactory::enchantment_digging(), Rarity::COMMON, ItemFlags::DIG, ItemFlags::SHEARS, 5));
|
self::register("EFFICIENCY", new Enchantment(KnownTranslationFactory::enchantment_digging(), Rarity::COMMON, ItemFlags::DIG, ItemFlags::SHEARS, 5));
|
||||||
|
self::register("FORTUNE", new Enchantment(KnownTranslationFactory::enchantment_lootBonusDigger(), Rarity::RARE, ItemFlags::DIG, ItemFlags::NONE, 3));
|
||||||
self::register("SILK_TOUCH", new Enchantment(KnownTranslationFactory::enchantment_untouching(), Rarity::MYTHIC, ItemFlags::DIG, ItemFlags::SHEARS, 1));
|
self::register("SILK_TOUCH", new Enchantment(KnownTranslationFactory::enchantment_untouching(), Rarity::MYTHIC, ItemFlags::DIG, ItemFlags::SHEARS, 1));
|
||||||
self::register("UNBREAKING", new Enchantment(KnownTranslationFactory::enchantment_durability(), Rarity::UNCOMMON, ItemFlags::DIG | ItemFlags::ARMOR | ItemFlags::FISHING_ROD | ItemFlags::BOW, ItemFlags::TOOL | ItemFlags::CARROT_STICK | ItemFlags::ELYTRA, 3));
|
self::register("UNBREAKING", new Enchantment(KnownTranslationFactory::enchantment_durability(), Rarity::UNCOMMON, ItemFlags::DIG | ItemFlags::ARMOR | ItemFlags::FISHING_ROD | ItemFlags::BOW, ItemFlags::TOOL | ItemFlags::CARROT_STICK | ItemFlags::ELYTRA, 3));
|
||||||
|
|
||||||
|
@ -59,7 +59,7 @@ abstract class StringToTParser{
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tries to parse the specified string into an enchantment.
|
* Tries to parse the specified string into a corresponding instance of T.
|
||||||
* @phpstan-return T|null
|
* @phpstan-return T|null
|
||||||
*/
|
*/
|
||||||
public function parse(string $input){
|
public function parse(string $input){
|
||||||
|
Loading…
x
Reference in New Issue
Block a user