PocketMine-MP 5.37.1 git-cef37e7835c666594588f957a47b27d521c6a58e
Loading...
Searching...
No Matches
EntityFactory.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\entity;
25
60use pocketmine\utils\SingletonTrait;
63use function count;
64use function reset;
65
70final class EntityFactory{
71 use SingletonTrait;
72
73 public const TAG_IDENTIFIER = "identifier"; //TAG_String
74 public const TAG_LEGACY_ID = "id"; //TAG_Int
75
80 private array $creationFuncs = [];
85 private array $saveNames = [];
86
87 public function __construct(){
88 //define legacy save IDs first - use them for saving for maximum compatibility with Minecraft PC
89 //TODO: index them by version to allow proper multi-save compatibility
90
91 $this->register(AreaEffectCloud::class, function(World $world, CompoundTag $nbt) : AreaEffectCloud{
92 return new AreaEffectCloud(Helper::parseLocation($nbt, $world), $nbt);
93 }, ['AreaEffectCloud', 'minecraft:area_effect_cloud']);
94
95 $this->register(Arrow::class, function(World $world, CompoundTag $nbt) : Arrow{
96 return new Arrow(Helper::parseLocation($nbt, $world), null, $nbt->getByte(Arrow::TAG_CRIT, 0) === 1, $nbt);
97 }, ['Arrow', 'minecraft:arrow']);
98
99 $this->register(Egg::class, function(World $world, CompoundTag $nbt) : Egg{
100 return new Egg(Helper::parseLocation($nbt, $world), null, $nbt);
101 }, ['Egg', 'minecraft:egg']);
102
103 $this->register(EndCrystal::class, function(World $world, CompoundTag $nbt) : EndCrystal{
104 return new EndCrystal(Helper::parseLocation($nbt, $world), $nbt);
105 }, ['EnderCrystal', 'minecraft:ender_crystal']);
106
107 $this->register(EnderPearl::class, function(World $world, CompoundTag $nbt) : EnderPearl{
108 return new EnderPearl(Helper::parseLocation($nbt, $world), null, $nbt);
109 }, ['ThrownEnderpearl', 'minecraft:ender_pearl']);
110
111 $this->register(ExperienceBottle::class, function(World $world, CompoundTag $nbt) : ExperienceBottle{
112 return new ExperienceBottle(Helper::parseLocation($nbt, $world), null, $nbt);
113 }, ['ThrownExpBottle', 'minecraft:xp_bottle']);
114
115 $this->register(ExperienceOrb::class, function(World $world, CompoundTag $nbt) : ExperienceOrb{
116 $value = 1;
117 if(($valuePcTag = $nbt->getTag(ExperienceOrb::TAG_VALUE_PC)) instanceof ShortTag){ //PC
118 $value = $valuePcTag->getValue();
119 }elseif(($valuePeTag = $nbt->getTag(ExperienceOrb::TAG_VALUE_PE)) instanceof IntTag){ //PE save format
120 $value = $valuePeTag->getValue();
121 }
122
123 return new ExperienceOrb(Helper::parseLocation($nbt, $world), $value, $nbt);
124 }, ['XPOrb', 'minecraft:xp_orb']);
125
126 $this->register(FallingBlock::class, function(World $world, CompoundTag $nbt) : FallingBlock{
127 return new FallingBlock(Helper::parseLocation($nbt, $world), FallingBlock::parseBlockNBT(RuntimeBlockStateRegistry::getInstance(), $nbt), $nbt);
128 }, ['FallingSand', 'minecraft:falling_block']);
129
130 $this->register(IceBomb::class, function(World $world, CompoundTag $nbt) : IceBomb{
131 return new IceBomb(Helper::parseLocation($nbt, $world), null, $nbt);
132 }, ['minecraft:ice_bomb']);
133
134 $this->register(ItemEntity::class, function(World $world, CompoundTag $nbt) : ItemEntity{
135 $itemTag = $nbt->getCompoundTag(ItemEntity::TAG_ITEM);
136 if($itemTag === null){
137 throw new SavedDataLoadingException("Expected \"" . ItemEntity::TAG_ITEM . "\" NBT tag not found");
138 }
139
140 $item = Item::nbtDeserialize($itemTag);
141 if($item->isNull()){
142 throw new SavedDataLoadingException("Item is invalid");
143 }
144 return new ItemEntity(Helper::parseLocation($nbt, $world), $item, $nbt);
145 }, ['Item', 'minecraft:item']);
146
147 $this->register(Painting::class, function(World $world, CompoundTag $nbt) : Painting{
148 $motive = PaintingMotive::getMotiveByName($nbt->getString(Painting::TAG_MOTIVE));
149 if($motive === null){
150 throw new SavedDataLoadingException("Unknown painting motive");
151 }
152 $blockIn = new Vector3($nbt->getInt(Painting::TAG_TILE_X), $nbt->getInt(Painting::TAG_TILE_Y), $nbt->getInt(Painting::TAG_TILE_Z));
153 if(($directionTag = $nbt->getTag(Painting::TAG_DIRECTION_BE)) instanceof ByteTag){
154 $facing = Painting::DATA_TO_FACING[$directionTag->getValue()] ?? Facing::NORTH;
155 }elseif(($facingTag = $nbt->getTag(Painting::TAG_FACING_JE)) instanceof ByteTag){
156 $facing = Painting::DATA_TO_FACING[$facingTag->getValue()] ?? Facing::NORTH;
157 }else{
158 throw new SavedDataLoadingException("Missing facing info");
159 }
160
161 return new Painting(Helper::parseLocation($nbt, $world), $blockIn, $facing, $motive, $nbt);
162 }, ['Painting', 'minecraft:painting']);
163
164 $this->register(PrimedTNT::class, function(World $world, CompoundTag $nbt) : PrimedTNT{
165 return new PrimedTNT(Helper::parseLocation($nbt, $world), $nbt);
166 }, ['PrimedTnt', 'PrimedTNT', 'minecraft:tnt']);
167
168 $this->register(Snowball::class, function(World $world, CompoundTag $nbt) : Snowball{
169 return new Snowball(Helper::parseLocation($nbt, $world), null, $nbt);
170 }, ['Snowball', 'minecraft:snowball']);
171
172 $this->register(SplashPotion::class, function(World $world, CompoundTag $nbt) : SplashPotion{
173 $potionType = PotionTypeIdMap::getInstance()->fromId($nbt->getShort(SplashPotion::TAG_POTION_ID, PotionTypeIds::WATER));
174 if($potionType === null){
175 throw new SavedDataLoadingException("No such potion type");
176 }
177 return new SplashPotion(Helper::parseLocation($nbt, $world), null, $potionType, $nbt);
178 }, ['ThrownPotion', 'minecraft:potion', 'thrownpotion']);
179
180 $this->register(Trident::class, function(World $world, CompoundTag $nbt) : Trident{
181 $itemTag = $nbt->getCompoundTag(Trident::TAG_ITEM);
182 if($itemTag === null){
183 throw new SavedDataLoadingException("Expected \"" . Trident::TAG_ITEM . "\" NBT tag not found");
184 }
185
186 $item = Item::nbtDeserialize($itemTag);
187 if($item->isNull()){
188 throw new SavedDataLoadingException("Trident item is invalid");
189 }
190 return new Trident(Helper::parseLocation($nbt, $world), $item, null, $nbt);
191 }, [
192 'minecraft:trident', //java
193 'minecraft:thrown_trident', //bedrock
194 'Trident', //backwards compat for people who used #4547 before it was merged, since it was sitting around for 4 years...
195 'ThrownTrident' //as above
196 ]);
197
198 $this->register(Squid::class, function(World $world, CompoundTag $nbt) : Squid{
199 return new Squid(Helper::parseLocation($nbt, $world), $nbt);
200 }, ['Squid', 'minecraft:squid']);
201
202 $this->register(Villager::class, function(World $world, CompoundTag $nbt) : Villager{
203 return new Villager(Helper::parseLocation($nbt, $world), $nbt);
204 }, ['Villager', 'minecraft:villager']);
205
206 $this->register(Zombie::class, function(World $world, CompoundTag $nbt) : Zombie{
207 return new Zombie(Helper::parseLocation($nbt, $world), $nbt);
208 }, ['Zombie', 'minecraft:zombie']);
209
210 $this->register(Human::class, function(World $world, CompoundTag $nbt) : Human{
211 return new Human(Helper::parseLocation($nbt, $world), Human::parseSkinNBT($nbt), $nbt);
212 }, ['Human']);
213 }
214
228 public function register(string $className, \Closure $creationFunc, array $saveNames) : void{
229 if(count($saveNames) === 0){
230 throw new \InvalidArgumentException("At least one save name must be provided");
231 }
232 Utils::testValidInstance($className, Entity::class);
233 Utils::validateCallableSignature(new CallbackType(
234 new ReturnType(Entity::class),
235 new ParameterType("world", World::class),
236 new ParameterType("nbt", CompoundTag::class)
237 ), $creationFunc);
238
239 foreach($saveNames as $name){
240 $this->creationFuncs[$name] = $creationFunc;
241 }
242
243 $this->saveNames[$className] = reset($saveNames);
244 }
245
249 public function isRegistered(string $class) : bool{
250 return isset($this->saveNames[$class]);
251 }
252
259 public function createFromData(World $world, CompoundTag $nbt) : ?Entity{
260 try{
261 $saveId = $nbt->getTag(self::TAG_IDENTIFIER) ?? $nbt->getTag(self::TAG_LEGACY_ID);
262 $func = null;
263 if($saveId instanceof StringTag){
264 $func = $this->creationFuncs[$saveId->getValue()] ?? null;
265 }elseif($saveId instanceof IntTag){ //legacy MCPE format
266 $stringId = LegacyEntityIdToStringIdMap::getInstance()->legacyToString($saveId->getValue() & 0xff);
267 $func = $stringId !== null ? $this->creationFuncs[$stringId] ?? null : null;
268 }
269 if($func === null){
270 return null;
271 }
273 $entity = $func($world, $nbt);
274
275 return $entity;
276 }catch(NbtException $e){
277 throw new SavedDataLoadingException($e->getMessage(), 0, $e);
278 }
279 }
280
281 public function injectSaveId(string $class, CompoundTag $saveData) : void{
282 if(isset($this->saveNames[$class])){
283 $saveData->setTag(self::TAG_IDENTIFIER, new StringTag($this->saveNames[$class]));
284 }else{
285 throw new \InvalidArgumentException("Entity $class is not registered");
286 }
287 }
288
292 public function getSaveId(string $class) : string{
293 if(isset($this->saveNames[$class])){
294 return $this->saveNames[$class];
295 }
296 throw new \InvalidArgumentException("Entity $class is not registered");
297 }
298}
createFromData(World $world, CompoundTag $nbt)
static parseSkinNBT(CompoundTag $nbt)
Definition Human.php:128