PocketMine-MP 5.21.2 git-b2aa6396c3cc2cafdd815eacc360e1ad89599899
Loading...
Searching...
No Matches
EnchantingHelper.php
1<?php
2
3/*
4 *
5 * ____ _ _ __ __ _ __ __ ____
6 * | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
7 * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
8 * | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
9 * |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
10 *
11 * This program is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU Lesser General Public License as published by
13 * the Free Software Foundation, either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * @author PocketMine Team
17 * @link http://www.pocketmine.net/
18 *
19 *
20 */
21
22declare(strict_types=1);
23
24namespace pocketmine\item\enchantment;
25
34use function abs;
35use function array_filter;
36use function chr;
37use function count;
38use function floor;
39use function max;
40use function min;
41use function mt_rand;
42use function ord;
43use function round;
44
48final class EnchantingHelper{
49 private const MAX_BOOKSHELF_COUNT = 15;
50
51 private function __construct(){
52 //NOOP
53 }
54
58 public static function generateSeed() : int{
59 return mt_rand(Limits::INT32_MIN, Limits::INT32_MAX);
60 }
61
65 public static function enchantItem(Item $item, array $enchantments) : Item{
66 $resultItem = $item->getTypeId() === ItemTypeIds::BOOK ? Items::ENCHANTED_BOOK() : clone $item;
67
68 foreach($enchantments as $enchantment){
69 $resultItem->addEnchantment($enchantment);
70 }
71
72 return $resultItem;
73 }
74
78 public static function generateOptions(Position $tablePos, Item $input, int $seed) : array{
79 if($input->isNull() || $input->hasEnchantments()){
80 return [];
81 }
82
83 $random = new Random($seed);
84
85 $bookshelfCount = self::countBookshelves($tablePos);
86 $baseRequiredLevel = $random->nextRange(1, 8) + ($bookshelfCount >> 1) + $random->nextRange(0, $bookshelfCount);
87 $topRequiredLevel = (int) floor(max($baseRequiredLevel / 3, 1));
88 $middleRequiredLevel = (int) floor($baseRequiredLevel * 2 / 3 + 1);
89 $bottomRequiredLevel = max($baseRequiredLevel, $bookshelfCount * 2);
90
91 return [
92 self::createOption($random, $input, $topRequiredLevel),
93 self::createOption($random, $input, $middleRequiredLevel),
94 self::createOption($random, $input, $bottomRequiredLevel),
95 ];
96 }
97
98 private static function countBookshelves(Position $tablePos) : int{
99 $bookshelfCount = 0;
100 $world = $tablePos->getWorld();
101
102 for($x = -2; $x <= 2; $x++){
103 for($z = -2; $z <= 2; $z++){
104 // We only check blocks at a distance of 2 blocks from the enchanting table
105 if(abs($x) !== 2 && abs($z) !== 2){
106 continue;
107 }
108
109 // Ensure the space between the bookshelf stack at this X/Z and the enchanting table is empty
110 for($y = 0; $y <= 1; $y++){
111 // Calculate the coordinates of the space between the bookshelf and the enchanting table
112 $spaceX = max(min($x, 1), -1);
113 $spaceZ = max(min($z, 1), -1);
114 $spaceBlock = $world->getBlock($tablePos->add($spaceX, $y, $spaceZ));
115 if($spaceBlock->getTypeId() !== BlockTypeIds::AIR){
116 continue 2;
117 }
118 }
119
120 // Finally, check the number of bookshelves at the current position
121 for($y = 0; $y <= 1; $y++){
122 $block = $world->getBlock($tablePos->add($x, $y, $z));
123 if($block->getTypeId() === BlockTypeIds::BOOKSHELF){
124 $bookshelfCount++;
125 if($bookshelfCount === self::MAX_BOOKSHELF_COUNT){
126 return $bookshelfCount;
127 }
128 }
129 }
130 }
131 }
132
133 return $bookshelfCount;
134 }
135
136 private static function createOption(Random $random, Item $inputItem, int $requiredXpLevel) : EnchantingOption{
137 $enchantingPower = $requiredXpLevel;
138
139 $enchantability = $inputItem->getEnchantability();
140 $enchantingPower = $enchantingPower + $random->nextRange(0, $enchantability >> 2) + $random->nextRange(0, $enchantability >> 2) + 1;
141 // Random bonus for enchanting power between 0.85 and 1.15
142 $bonus = 1 + ($random->nextFloat() + $random->nextFloat() - 1) * 0.15;
143 $enchantingPower = (int) round($enchantingPower * $bonus);
144
145 $resultEnchantments = [];
146 $availableEnchantments = self::getAvailableEnchantments($enchantingPower, $inputItem);
147
148 $lastEnchantment = self::getRandomWeightedEnchantment($random, $availableEnchantments);
149 if($lastEnchantment !== null){
150 $resultEnchantments[] = $lastEnchantment;
151
152 // With probability (power + 1) / 50, continue adding enchantments
153 while($random->nextFloat() <= ($enchantingPower + 1) / 50){
154 // Remove from the list of available enchantments anything that conflicts
155 // with previously-chosen enchantments
156 $availableEnchantments = array_filter(
157 $availableEnchantments,
158 function(EnchantmentInstance $e) use ($lastEnchantment){
159 return $e->getType() !== $lastEnchantment->getType() &&
160 $e->getType()->isCompatibleWith($lastEnchantment->getType());
161 }
162 );
163
164 $lastEnchantment = self::getRandomWeightedEnchantment($random, $availableEnchantments);
165 if($lastEnchantment === null){
166 break;
167 }
168
169 $resultEnchantments[] = $lastEnchantment;
170 $enchantingPower >>= 1;
171 }
172 }
173
174 return new EnchantingOption($requiredXpLevel, self::getRandomOptionName($random), $resultEnchantments);
175 }
176
180 private static function getAvailableEnchantments(int $enchantingPower, Item $item) : array{
181 $list = [];
182
183 foreach(EnchantmentRegistry::getInstance()->getPrimaryEnchantmentsForItem($item) as $enchantment){
184 for($lvl = $enchantment->getMaxLevel(); $lvl > 0; $lvl--){
185 if($enchantingPower >= $enchantment->getMinEnchantingPower($lvl) &&
186 $enchantingPower <= $enchantment->getMaxEnchantingPower($lvl)
187 ){
188 $list[] = new EnchantmentInstance($enchantment, $lvl);
189 break;
190 }
191 }
192 }
193
194 return $list;
195 }
196
200 private static function getRandomWeightedEnchantment(Random $random, array $enchantments) : ?EnchantmentInstance{
201 if(count($enchantments) === 0){
202 return null;
203 }
204
205 $totalWeight = 0;
206 foreach($enchantments as $enchantment){
207 $totalWeight += $enchantment->getType()->getRarity();
208 }
209
210 $result = null;
211 $randomWeight = $random->nextRange(1, $totalWeight);
212
213 foreach($enchantments as $enchantment){
214 $randomWeight -= $enchantment->getType()->getRarity();
215
216 if($randomWeight <= 0){
217 $result = $enchantment;
218 break;
219 }
220 }
221
222 return $result;
223 }
224
225 private static function getRandomOptionName(Random $random) : string{
226 $name = "";
227 for($i = $random->nextRange(5, 15); $i > 0; $i--){
228 $name .= chr($random->nextRange(ord("a"), ord("z")));
229 }
230
231 return $name;
232 }
233}
static generateOptions(Position $tablePos, Item $input, int $seed)
static enchantItem(Item $item, array $enchantments)