Introduce EntityFactory

This contains all of the static stuff that was previously embedded in the Entity static root. This solves a bunch of problems like circular dependencies between parent and child classes, encapsulating logic and reducing the size of the enormous Entity.php.
This commit is contained in:
Dylan K. Taylor
2019-01-06 23:54:29 +00:00
parent b1cef8509a
commit 7d827a1c65
13 changed files with 298 additions and 247 deletions

View File

@ -28,18 +28,6 @@ namespace pocketmine\entity;
use pocketmine\block\Block;
use pocketmine\block\Water;
use pocketmine\entity\object\ExperienceOrb;
use pocketmine\entity\object\FallingBlock;
use pocketmine\entity\object\ItemEntity;
use pocketmine\entity\object\Painting;
use pocketmine\entity\object\PaintingMotive;
use pocketmine\entity\object\PrimedTNT;
use pocketmine\entity\projectile\Arrow;
use pocketmine\entity\projectile\Egg;
use pocketmine\entity\projectile\EnderPearl;
use pocketmine\entity\projectile\ExperienceBottle;
use pocketmine\entity\projectile\Snowball;
use pocketmine\entity\projectile\SplashPotion;
use pocketmine\event\entity\EntityDamageEvent;
use pocketmine\event\entity\EntityDespawnEvent;
use pocketmine\event\entity\EntityLevelChangeEvent;
@ -61,7 +49,6 @@ use pocketmine\metadata\MetadataValue;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\DoubleTag;
use pocketmine\nbt\tag\FloatTag;
use pocketmine\nbt\tag\IntTag;
use pocketmine\nbt\tag\ListTag;
use pocketmine\nbt\tag\StringTag;
use pocketmine\network\mcpe\protocol\AddEntityPacket;
@ -76,23 +63,17 @@ use pocketmine\plugin\Plugin;
use pocketmine\Server;
use pocketmine\timings\Timings;
use pocketmine\timings\TimingsHandler;
use pocketmine\utils\Utils;
use function abs;
use function array_keys;
use function assert;
use function cos;
use function count;
use function current;
use function deg2rad;
use function floor;
use function get_class;
use function in_array;
use function is_a;
use function is_array;
use function is_infinite;
use function is_nan;
use function lcg_value;
use function reset;
use function sin;
use const M_PI_2;
@ -279,194 +260,6 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
public const DATA_FLAG_OVER_SCAFFOLDING = 69;
public const DATA_FLAG_FALL_THROUGH_SCAFFOLDING = 70;
public static $entityCount = 1;
/** @var Entity[] */
private static $knownEntities = [];
/** @var string[][] */
private static $saveNames = [];
/** @var string[] base class => currently used class for construction */
private static $classMapping = [];
/**
* Called on server startup to register default entity types.
*/
public static function init() : void{
//define legacy save IDs first - use them for saving for maximum compatibility with Minecraft PC
//TODO: index them by version to allow proper multi-save compatibility
self::register(Arrow::class, false, ['Arrow', 'minecraft:arrow']);
self::register(Egg::class, false, ['Egg', 'minecraft:egg']);
self::register(EnderPearl::class, false, ['ThrownEnderpearl', 'minecraft:ender_pearl']);
self::register(ExperienceBottle::class, false, ['ThrownExpBottle', 'minecraft:xp_bottle']);
self::register(ExperienceOrb::class, false, ['XPOrb', 'minecraft:xp_orb']);
self::register(FallingBlock::class, false, ['FallingSand', 'minecraft:falling_block']);
self::register(ItemEntity::class, false, ['Item', 'minecraft:item']);
self::register(Painting::class, false, ['Painting', 'minecraft:painting']);
self::register(PrimedTNT::class, false, ['PrimedTnt', 'PrimedTNT', 'minecraft:tnt']);
self::register(Snowball::class, false, ['Snowball', 'minecraft:snowball']);
self::register(SplashPotion::class, false, ['ThrownPotion', 'minecraft:potion', 'thrownpotion']);
self::register(Squid::class, false, ['Squid', 'minecraft:squid']);
self::register(Villager::class, false, ['Villager', 'minecraft:villager']);
self::register(Zombie::class, false, ['Zombie', 'minecraft:zombie']);
self::register(Human::class, true);
Attribute::init();
Effect::init();
PaintingMotive::init();
}
/**
* Creates an entity with the specified type, level and NBT, with optional additional arguments to pass to the
* entity's constructor.
*
* TODO: make this NBT-independent
*
* @param string $baseClass
* @param Level $level
* @param CompoundTag $nbt
* @param mixed ...$args
*
* @return Entity instanceof $baseClass
* @throws \InvalidArgumentException if the class doesn't exist or is not registered
*/
public static function create(string $baseClass, Level $level, CompoundTag $nbt, ...$args) : Entity{
if(isset(self::$classMapping[$baseClass])){
$class = self::$classMapping[$baseClass];
assert(is_a($class, $baseClass, true));
/**
* @var Entity $entity
* @see Entity::__construct()
*/
$entity = new $class($level, $nbt, ...$args);
return $entity;
}
throw new \InvalidArgumentException("Class $baseClass is not a registered entity");
}
/**
* Creates an entity from data stored on a chunk.
* @internal
*
* @param Level $level
* @param CompoundTag $nbt
*
* @return Entity|null
* @throws \RuntimeException
*/
public static function createFromData(Level $level, CompoundTag $nbt) : ?Entity{
$saveId = $nbt->getTag("id");
$baseClass = null;
if($saveId instanceof StringTag){
$baseClass = self::$knownEntities[$saveId->getValue()] ?? null;
}elseif($saveId instanceof IntTag){ //legacy MCPE format
$baseClass = self::$knownEntities[$saveId->getValue() & 0xff] ?? null;
}
if($baseClass === null){
return null;
}
$class = self::$classMapping[$baseClass];
assert(is_a($class, $baseClass, true));
/**
* @var Entity $entity
* @see Entity::__construct()
*/
$entity = new $class($level, $nbt);
return $entity;
}
/**
* Registers an entity type into the index.
*
* @param string $className Class that extends Entity
* @param bool $force Force registration even if the entity does not have a valid network ID
* @param string[] $saveNames An array of save names which this entity might be saved under. Defaults to the short name of the class itself if empty.
*
* NOTE: The first save name in the $saveNames array will be used when saving the entity to disk. The reflection
* name of the class will be appended to the end and only used if no other save names are specified.
*
* @throws \InvalidArgumentException
*/
public static function register(string $className, bool $force = false, array $saveNames = []) : void{
Utils::testValidInstance($className, Entity::class);
/** @var Entity $className */
if($className::NETWORK_ID !== -1){
self::$knownEntities[$className::NETWORK_ID] = $className;
}elseif(!$force){
throw new \InvalidArgumentException("Class $className does not declare a valid NETWORK_ID and not force-registering");
}
self::$classMapping[$className] = $className;
$shortName = (new \ReflectionClass($className))->getShortName();
if(!in_array($shortName, $saveNames, true)){
$saveNames[] = $shortName;
}
foreach($saveNames as $name){
self::$knownEntities[$name] = $className;
}
self::$saveNames[$className] = $saveNames;
}
/**
* Registers a class override for the given class. When a new entity is constructed using the factory, the new class
* will be used instead of the base class.
*
* @param string $baseClass Already-registered entity class to override
* @param string $newClass Class which extends the base class
*
* @throws \InvalidArgumentException
*/
public static function override(string $baseClass, string $newClass) : void{
if(!isset(self::$classMapping[$baseClass])){
throw new \InvalidArgumentException("Class $baseClass is not a registered entity");
}
Utils::testValidInstance($newClass, $baseClass);
self::$classMapping[$baseClass] = $newClass;
}
/**
* Returns an array of all registered entity classpaths.
*
* @return string[]
*/
public static function getKnownTypes() : array{
return array_keys(self::$classMapping);
}
/**
* Helper function which creates minimal NBT needed to spawn an entity.
*
* @param Vector3 $pos
* @param Vector3|null $motion
* @param float $yaw
* @param float $pitch
*
* @return CompoundTag
*/
public static function createBaseNBT(Vector3 $pos, ?Vector3 $motion = null, float $yaw = 0.0, float $pitch = 0.0) : CompoundTag{
return new CompoundTag("", [
new ListTag("Pos", [
new DoubleTag("", $pos->x),
new DoubleTag("", $pos->y),
new DoubleTag("", $pos->z)
]),
new ListTag("Motion", [
new DoubleTag("", $motion ? $motion->x : 0.0),
new DoubleTag("", $motion ? $motion->y : 0.0),
new DoubleTag("", $motion ? $motion->z : 0.0)
]),
new ListTag("Rotation", [
new FloatTag("", $yaw),
new FloatTag("", $pitch)
])
]);
}
/**
* @var Player[]
@ -591,7 +384,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
$this->eyeHeight = $this->height / 2 + 0.1;
}
$this->id = Entity::$entityCount++;
$this->id = EntityFactory::nextRuntimeId();
$this->server = $level->getServer();
/** @var float[] $pos */
@ -924,23 +717,10 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
$this->savedWithChunk = $value;
}
/**
* Returns the short save name
*
* @return string
*/
public function getSaveId() : string{
if(!isset(self::$saveNames[static::class])){
throw new \InvalidStateException("Entity is not registered");
}
reset(self::$saveNames[static::class]);
return current(self::$saveNames[static::class]);
}
public function saveNBT() : CompoundTag{
$nbt = new CompoundTag();
if(!($this instanceof Player)){
$nbt->setString("id", $this->getSaveId());
$nbt->setString("id", EntityFactory::getSaveId(get_class($this)));
if($this->getNameTag() !== ""){
$nbt->setString("CustomName", $this->getNameTag());