diff --git a/src/IvanCraft623/MobPlugin/MobPlugin.php b/src/IvanCraft623/MobPlugin/MobPlugin.php index 63b2f17..fe8a069 100644 --- a/src/IvanCraft623/MobPlugin/MobPlugin.php +++ b/src/IvanCraft623/MobPlugin/MobPlugin.php @@ -34,6 +34,7 @@ use IvanCraft623\MobPlugin\entity\monster\Enderman; use IvanCraft623\MobPlugin\entity\monster\Endermite; use IvanCraft623\MobPlugin\entity\monster\Slime; +use IvanCraft623\MobPlugin\entity\monster\Spider; use IvanCraft623\MobPlugin\item\ExtraItemRegisterHelper; use pocketmine\entity\AttributeFactory; @@ -119,5 +120,9 @@ private function registerEntities() : void{ $factory->register(Enderman::class, function(World $world, CompoundTag $nbt) : Enderman{ return new Enderman(Helper::parseLocation($nbt, $world), $nbt); }, ['minecraft:enderman', 'Enderman']); + + $factory->register(Spider::class, function(World $world, CompoundTag $nbt) : Spider{ + return new Spider(Helper::parseLocation($nbt, $world), $nbt); + }, ['minecraft:spider', 'Spider']); } } diff --git a/src/IvanCraft623/MobPlugin/entity/Living.php b/src/IvanCraft623/MobPlugin/entity/Living.php index 52244af..87a20e7 100644 --- a/src/IvanCraft623/MobPlugin/entity/Living.php +++ b/src/IvanCraft623/MobPlugin/entity/Living.php @@ -26,6 +26,7 @@ use IvanCraft623\MobPlugin\entity\ai\Brain; use IvanCraft623\MobPlugin\inventory\MobInventory; use IvanCraft623\MobPlugin\MobPlugin; +use IvanCraft623\MobPlugin\utils\Utils; use pocketmine\block\Block; use pocketmine\block\Liquid; @@ -38,24 +39,20 @@ use pocketmine\event\entity\EntityDamageEvent; use pocketmine\inventory\CallbackInventoryListener; use pocketmine\inventory\Inventory; -use pocketmine\item\enchantment\VanillaEnchantments; use pocketmine\item\Item; use pocketmine\math\Vector3; use pocketmine\math\VoxelRayTrace; use pocketmine\nbt\NBT; use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\ListTag; -use pocketmine\network\mcpe\convert\TypeConverter; use pocketmine\network\mcpe\protocol\MobEquipmentPacket; use pocketmine\network\mcpe\protocol\types\inventory\ContainerIds; use pocketmine\network\mcpe\protocol\types\inventory\ItemStackWrapper; use pocketmine\player\Player; use pocketmine\Server; use pocketmine\utils\Random; -use function array_filter; -use function array_key_exists; -use function array_merge; -use function array_values; +use pocketmine\world\Position; +use function count; use function floor; use function min; @@ -399,4 +396,8 @@ protected function checkBlockIntersections() : void{ public function onInsideBlock(Block $block) : bool{ return false; } + + public function getLightLevelDependentMagicValue() : float{ + return Utils::getLightLevelDependentMagicValue(Position::fromObject($this->getEyePos(), $this->location->world)); + } } diff --git a/src/IvanCraft623/MobPlugin/entity/Mob.php b/src/IvanCraft623/MobPlugin/entity/Mob.php index d0c53c3..8fba502 100644 --- a/src/IvanCraft623/MobPlugin/entity/Mob.php +++ b/src/IvanCraft623/MobPlugin/entity/Mob.php @@ -56,6 +56,8 @@ use pocketmine\world\sound\ItemBreakSound; use pocketmine\world\World; use function assert; +use function count; +use function lcg_value; use function max; abstract class Mob extends Living { @@ -144,7 +146,7 @@ protected function addAttributes() : void{ } public function createNavigation() : PathNavigation{ - return new GroundPathNavigation($this, $this->getWorld()); + return new GroundPathNavigation($this); } public function getLookControl() : LookControl { @@ -380,9 +382,19 @@ public function tickAi() : void{ public function travel(Vector3 $movementInput) : void{ // TODO: More complex movement suff :P $motion = Utils::movementInputToMotion($movementInput, $this->location->yaw, $this->getMovementSpeed()); + + //Climb stuff + if ($this->isCollidedHorizontally && $this->onClimbable()) { + $motion->y = 0.2 - $this->motion->y; + } + $this->addMotion($motion->x, $motion->y, $motion->z); } + protected function onClimbable() : bool{ + return false; + } + protected function updateControlFlags() : void{ // TODO! } diff --git a/src/IvanCraft623/MobPlugin/entity/NeutralMobTrait.php b/src/IvanCraft623/MobPlugin/entity/NeutralMobTrait.php index fce19cc..908d736 100644 --- a/src/IvanCraft623/MobPlugin/entity/NeutralMobTrait.php +++ b/src/IvanCraft623/MobPlugin/entity/NeutralMobTrait.php @@ -27,7 +27,6 @@ use pocketmine\entity\Human; use pocketmine\entity\Living as PMLiving; use pocketmine\event\entity\EntityDamageByEntityEvent; -use pocketmine\world\World; trait NeutralMobTrait { diff --git a/src/IvanCraft623/MobPlugin/entity/ai/Brain.php b/src/IvanCraft623/MobPlugin/entity/ai/Brain.php index a000906..7f85c3e 100644 --- a/src/IvanCraft623/MobPlugin/entity/ai/Brain.php +++ b/src/IvanCraft623/MobPlugin/entity/ai/Brain.php @@ -292,7 +292,7 @@ public function addActivityWithBehaviorPairs(Activity $activity, array $behavior } /** - * @param Behavior[] $behaviors + * @param Behavior[] $behaviors * @param Pair[] $conditions */ public function addActivityWithConditions(Activity $activity, int $startPriority, array $behaviors, array $conditions) : void { @@ -300,9 +300,9 @@ public function addActivityWithConditions(Activity $activity, int $startPriority } /** - * @param Pair[] $behaviorPairs + * @param Pair[] $behaviorPairs * @param Pair[] $conditions - * @param MemoryModuleType[] $memoryModules + * @param MemoryModuleType[] $memoryModules */ public function addActivityAndRemoveMemoriesWhenStopped(Activity $activity, array $behaviorPairs, array $conditions, array $memoryModules) : void { $this->activityRequirements[$activity->id()] = $conditions; diff --git a/src/IvanCraft623/MobPlugin/entity/ai/goal/LeapAtTargetGoal.php b/src/IvanCraft623/MobPlugin/entity/ai/goal/LeapAtTargetGoal.php new file mode 100644 index 0000000..bbce85b --- /dev/null +++ b/src/IvanCraft623/MobPlugin/entity/ai/goal/LeapAtTargetGoal.php @@ -0,0 +1,86 @@ +setFlags(Goal::FLAG_JUMP, Goal::FLAG_MOVE); + } + + public function canUse() : bool{ + //TODO: Check there is no passanger controlling movement + + if (!$this->mob->isOnGround()) { + return false; + } + + $target = $this->mob->getTargetEntity(); + if ($target === null) { + return false; + } + + $distanceSquared = $this->mob->getLocation()->distanceSquared($target->getLocation()); + if ($distanceSquared < 2 ** 2 || $distanceSquared > 4 ** 2) { + return false; + } + + if ($this->mob->getRandom()->nextBoundedInt($this->reducedTickDelay(5)) === 0) { + $this->target = $target; + return true; + } + + return false; + } + + public function canContinueToUse() : bool{ + return !$this->mob->isOnGround(); + } + + public function start() : void{ + $position = $this->mob->getLocation(); + $targetPosition = $this->target->getLocation(); + + $leap = new Vector3($targetPosition->x - $position->x, 0.0, $targetPosition->z - $position->z); + if ($leap->lengthSquared() > 0.0000001) { + $leap = $leap->normalize()->multiply(0.4)->addVector($this->mob->getMotion()->multiply(0.2)); + } + $leap->y = $this->yMotion; + + $this->mob->setMotion($leap); + } + + public function stop() : void{ + unset($this->target); + } +} diff --git a/src/IvanCraft623/MobPlugin/entity/ai/goal/RandomTeleportGoal.php b/src/IvanCraft623/MobPlugin/entity/ai/goal/RandomTeleportGoal.php index f6d81e2..5073188 100644 --- a/src/IvanCraft623/MobPlugin/entity/ai/goal/RandomTeleportGoal.php +++ b/src/IvanCraft623/MobPlugin/entity/ai/goal/RandomTeleportGoal.php @@ -23,15 +23,13 @@ namespace IvanCraft623\MobPlugin\entity\ai\goal; -use IvanCraft623\MobPlugin\entity\ai\goal\Goal; use IvanCraft623\MobPlugin\entity\Mob; -use pocketmine\player\Player; -use pocketmine\entity\Living; +use pocketmine\block\Liquid; use pocketmine\math\Vector3; use pocketmine\world\Position; -use pocketmine\world\World; -use pocketmine\block\Liquid; +use function min; +use function mt_rand; class RandomTeleportGoal extends Goal { @@ -138,7 +136,7 @@ public function getRandomTeleportPosition(Position $origin, Vector3 $teleportRan } //TODO: Use $entity->canStandAt() instead of this, but is somehow broken - for ($extraY = 1; $extraY <= 3; $extraY++) { + for ($extraY = 1; $extraY <= 3; $extraY++) { if (($block = $world->getBlockAt($x, $y + $extraY, $z))->isSolid() || $block instanceof Liquid) { continue 2; } diff --git a/src/IvanCraft623/MobPlugin/entity/ai/goal/enderman/FreezeWhenLookedAt.php b/src/IvanCraft623/MobPlugin/entity/ai/goal/enderman/FreezeWhenLookedAt.php index 3c555e6..aebcfe6 100644 --- a/src/IvanCraft623/MobPlugin/entity/ai/goal/enderman/FreezeWhenLookedAt.php +++ b/src/IvanCraft623/MobPlugin/entity/ai/goal/enderman/FreezeWhenLookedAt.php @@ -27,7 +27,6 @@ use IvanCraft623\MobPlugin\entity\monster\Enderman; use pocketmine\player\Player; -use pocketmine\entity\Living; class FreezeWhenLookedAt extends Goal { diff --git a/src/IvanCraft623/MobPlugin/entity/ai/goal/enderman/LeaveBlockGoal.php b/src/IvanCraft623/MobPlugin/entity/ai/goal/enderman/LeaveBlockGoal.php index da53ba0..f07845c 100644 --- a/src/IvanCraft623/MobPlugin/entity/ai/goal/enderman/LeaveBlockGoal.php +++ b/src/IvanCraft623/MobPlugin/entity/ai/goal/enderman/LeaveBlockGoal.php @@ -26,13 +26,8 @@ use IvanCraft623\MobPlugin\entity\ai\goal\Goal; use IvanCraft623\MobPlugin\entity\monster\Enderman; -use pocketmine\player\Player; -use pocketmine\entity\Living; -use pocketmine\world\World; -use pocketmine\block\BlockTypeIds; -use pocketmine\block\Block; -use pocketmine\math\AxisAlignedBB; use pocketmine\math\Vector3; +use function floor; class LeaveBlockGoal extends Goal { diff --git a/src/IvanCraft623/MobPlugin/entity/ai/goal/enderman/LookForStaringPlayerGoal.php b/src/IvanCraft623/MobPlugin/entity/ai/goal/enderman/LookForStaringPlayerGoal.php index 58a8d2f..f980902 100644 --- a/src/IvanCraft623/MobPlugin/entity/ai/goal/enderman/LookForStaringPlayerGoal.php +++ b/src/IvanCraft623/MobPlugin/entity/ai/goal/enderman/LookForStaringPlayerGoal.php @@ -24,16 +24,14 @@ namespace IvanCraft623\MobPlugin\entity\ai\goal\enderman; use IvanCraft623\MobPlugin\entity\ai\goal\target\TargetGoal; -use IvanCraft623\MobPlugin\entity\ai\goal\Goal; -use IvanCraft623\MobPlugin\entity\monster\Enderman; use IvanCraft623\MobPlugin\entity\ai\targeting\TargetingConditions; +use IvanCraft623\MobPlugin\entity\monster\Enderman; use pocketmine\entity\Entity; use pocketmine\math\AxisAlignedBB; use pocketmine\player\Player; -use pocketmine\entity\Living as PMLiving; -use Closure; +use function array_reduce; class LookForStaringPlayerGoal extends TargetGoal { diff --git a/src/IvanCraft623/MobPlugin/entity/ai/goal/enderman/TakeBlockGoal.php b/src/IvanCraft623/MobPlugin/entity/ai/goal/enderman/TakeBlockGoal.php index f2833ef..c57a880 100644 --- a/src/IvanCraft623/MobPlugin/entity/ai/goal/enderman/TakeBlockGoal.php +++ b/src/IvanCraft623/MobPlugin/entity/ai/goal/enderman/TakeBlockGoal.php @@ -26,15 +26,11 @@ use IvanCraft623\MobPlugin\entity\ai\goal\Goal; use IvanCraft623\MobPlugin\entity\monster\Enderman; -use pocketmine\player\Player; -use pocketmine\entity\Living; -use pocketmine\math\Vector3; -use pocketmine\world\World; -use pocketmine\world\BlockTransaction; -use pocketmine\block\VanillaBlocks; use pocketmine\block\BlockTypeIds; -use pocketmine\math\AxisAlignedBB; +use pocketmine\block\VanillaBlocks; +use pocketmine\math\Vector3; use pocketmine\math\VoxelRayTrace; +use function floor; class TakeBlockGoal extends Goal { diff --git a/src/IvanCraft623/MobPlugin/entity/ai/navigation/GroundPathNavigation.php b/src/IvanCraft623/MobPlugin/entity/ai/navigation/GroundPathNavigation.php index b731acd..71e2d56 100644 --- a/src/IvanCraft623/MobPlugin/entity/ai/navigation/GroundPathNavigation.php +++ b/src/IvanCraft623/MobPlugin/entity/ai/navigation/GroundPathNavigation.php @@ -56,10 +56,11 @@ public function getTempMobPosition() : Vector3 { } public function createPathToPosition(Vector3 $position, int $maxVisitedNodes, ?float $range = null) : ?Path{ - if ($this->world->getBlock($position)->getTypeId() === BlockTypeIds::AIR) { + $world = $this->getWorld(); + if ($world->getBlock($position)->getTypeId() === BlockTypeIds::AIR) { $currentPos = $position->down(); - while ($currentPos->y > World::Y_MIN && $this->world->getBlock($currentPos)->getTypeId() === BlockTypeIds::AIR) { + while ($currentPos->y > World::Y_MIN && $world->getBlock($currentPos)->getTypeId() === BlockTypeIds::AIR) { $currentPos = $currentPos->down(); } @@ -67,20 +68,20 @@ public function createPathToPosition(Vector3 $position, int $maxVisitedNodes, ?f return parent::createPathToPosition($currentPos->up(), $maxVisitedNodes, $range); } - while($currentPos->getY() < World::Y_MAX && $this->world->getBlock($currentPos)->getTypeId() === BlockTypeIds::AIR) { + while($currentPos->getY() < World::Y_MAX && $world->getBlock($currentPos)->getTypeId() === BlockTypeIds::AIR) { $currentPos = $currentPos->up(); } $position = $currentPos; } - if (!$this->world->getBlock($position)->isSolid()) { + if (!$world->getBlock($position)->isSolid()) { return parent::createPathToPosition($position, $maxVisitedNodes, $range); } $currentPos = $position->up(); - while($currentPos->getY() < World::Y_MAX && $this->world->getBlock($currentPos)->isSolid()) { + while($currentPos->getY() < World::Y_MAX && $world->getBlock($currentPos)->isSolid()) { $currentPos = $currentPos->up(); } @@ -91,11 +92,12 @@ public function getSurfaceY() : int{ $mobPos = $this->mob->getPosition(); if ($this->mob->isInWater() && $this->canFloat()) { $y = (int) $mobPos->getY(); - $block = $this->world->getBlock($mobPos); + $world = $this->getWorld(); + $block = $world->getBlock($mobPos); $distDiff = 0; while ($block instanceof Water) { - $block = $this->world->getBlockAt((int) floor($mobPos->x), ++$y, (int) floor($mobPos->z)); + $block = $world->getBlockAt((int) floor($mobPos->x), ++$y, (int) floor($mobPos->z)); if (++$distDiff > 16) { return (int) $mobPos->getY(); } @@ -116,13 +118,14 @@ protected function trimPath() : void{ if ($this->avoidSun) { $mobPos = $this->mob->getPosition(); - if ($this->world->getRealBlockSkyLightAt((int) floor($mobPos->x), (int) floor($mobPos->y + 0.5), (int) floor($mobPos->z)) >= 15) { + $world = $this->getWorld(); + if ($world->getRealBlockSkyLightAt((int) floor($mobPos->x), (int) floor($mobPos->y + 0.5), (int) floor($mobPos->z)) >= 15) { return; } for($i = 0; $i < $this->path->getNodeCount(); ++$i) { $node = $this->path->getNode($i); - if ($this->world->getRealBlockSkyLightAt($node->x(), $node->y(), $node->z()) >= 15) { + if ($world->getRealBlockSkyLightAt($node->x(), $node->y(), $node->z()) >= 15) { $this->path->truncateNodes($i); return; } diff --git a/src/IvanCraft623/MobPlugin/entity/ai/navigation/PathNavigation.php b/src/IvanCraft623/MobPlugin/entity/ai/navigation/PathNavigation.php index 94b800a..e61137d 100644 --- a/src/IvanCraft623/MobPlugin/entity/ai/navigation/PathNavigation.php +++ b/src/IvanCraft623/MobPlugin/entity/ai/navigation/PathNavigation.php @@ -54,8 +54,6 @@ abstract class PathNavigation { protected Mob $mob; - protected World $world; - protected ?Path $path = null; protected float $speedModifier; @@ -92,13 +90,15 @@ abstract class PathNavigation { protected bool $isStuck = false; - public function __construct(Mob $mob, World $world) { + public function __construct(Mob $mob) { $this->mob = $mob; - $this->world = $world; - ; $this->pathfinder = $this->createPathFinder((int) floor($this->mob->getFollowRange() * 16)); } + public function getWorld() : World{ + return $this->mob->getWorld(); + } + public function resetMaxVisitedNodesMultiplier() : void{ $this->maxVisitedNodesMultiplier = self::DEFAULT_MAX_VISITED_NODES_MULTIPLIER; } @@ -118,7 +118,7 @@ public function setSpeedModifier(float $speed) : void{ } public function recomputePath() : void{ - if (($time = $this->world->getServer()->getTick()) - $this->timeLastRecompute > self::MAX_TIME_RECOMPUTE) { + if (($time = $this->getWorld()->getServer()->getTick()) - $this->timeLastRecompute > self::MAX_TIME_RECOMPUTE) { if ($this->targetPosition !== null) { $this->path = null; $this->path = $this->createPathToPosition($this->targetPosition, $this->reachRange); @@ -163,7 +163,7 @@ public function createPath(array $positions, int $reach, ?float $maxDistanceFrom CustomTimings::$pathfinding->startTiming(); - $path = $this->pathfinder->findPath($this->world, $this->mob, $positions, $maxDistanceFromStart, $reach, $this->maxVisitedNodesMultiplier); + $path = $this->pathfinder->findPath($this->getWorld(), $this->mob, $positions, $maxDistanceFromStart, $reach, $this->maxVisitedNodesMultiplier); CustomTimings::$pathfinding->stopTiming(); @@ -251,7 +251,7 @@ public function tick() : void{ } protected function getGroundY(Vector3 $position) : float{ - return $this->world->getBlock($position->down())->getTypeId() === BlockTypeIds::AIR ? $position->y : WalkNodeEvaluator::getFloorLevelAt($this->world, $position); + return $this->getWorld()->getBlock($position->down())->getTypeId() === BlockTypeIds::AIR ? $position->y : WalkNodeEvaluator::getFloorLevelAt($this->getWorld(), $position); } protected function followThePath() : void{ @@ -329,7 +329,7 @@ public function doStuckDetection(Vector3 $position) : void{ if ($this->path !== null && !$this->path->isDone()) { $nextNodePos = $this->path->getNextNodePos(); - $time = $this->world->getTime(); + $time = $this->getWorld()->getTime(); if ($nextNodePos->equals($this->timeoutCachedNode)) { $this->timeoutTimer += $time - $this->lastTimeoutCheck; @@ -376,7 +376,7 @@ protected abstract function getTempMobPosition() : Vector3; protected abstract function canUpdatePath() : bool; protected function isInLiquid() : bool{ - foreach ($this->world->getCollisionBlocks($this->mob->getBoundingBox()) as $block) { + foreach ($this->getWorld()->getCollisionBlocks($this->mob->getBoundingBox()) as $block) { if ($block instanceof Liquid) { //TODO: waterlogging check and do not trigger with powder snow return true; @@ -388,9 +388,10 @@ protected function isInLiquid() : bool{ protected function trimPath() : void{ if ($this->path !== null) { + $world = $this->getWorld(); for ($i = 0; $i < $this->path->getNodeCount(); $i++) { $node = $this->path->getNode($i); - if ($this->world->getBlock($node->asVector3()) instanceof FillableCauldron) { + if ($world->getBlock($node->asVector3()) instanceof FillableCauldron) { $this->path->replaceNode($i, $node->cloneAndMove($node->x(), $node->y() + 1, $node->z())); $nextNode = $i + 1 < $this->path->getNodeCount() ? $this->path->getNode($i + 1) : null; @@ -431,7 +432,7 @@ protected static function isClearForMovementBetween(Mob $mob, Vector3 $from, Vec } public function isStableDestination(Vector3 $position) : bool{ - return $this->world->getBlock($position->down())->isSolid(); + return $this->getWorld()->getBlock($position->down())->isSolid(); } public function getNodeEvaluator() : NodeEvaluator{ diff --git a/src/IvanCraft623/MobPlugin/entity/ai/navigation/WallClimberNavigation.php b/src/IvanCraft623/MobPlugin/entity/ai/navigation/WallClimberNavigation.php new file mode 100644 index 0000000..60983c9 --- /dev/null +++ b/src/IvanCraft623/MobPlugin/entity/ai/navigation/WallClimberNavigation.php @@ -0,0 +1,77 @@ +pathToPosition = $position->floor(); + $this->speedModifier = 1; + + return parent::createPathToPosition($position, $reach, $maxDistanceFromStart); + } + + public function moveToEntity(Entity $target, float $speedModifier) : bool{ + $path = $this->createPathToEntity($target, 0); + if ($path !== null) { + return $this->moveToPath($path, $speedModifier); + } + + $this->pathToPosition = $target->getPosition()->floor(); + $this->speedModifier = $speedModifier; + + return true; + } + + public function tick() : void{ + if (!$this->isDone()) { + parent::tick(); + return; + } + + if ($this->pathToPosition === null) { + return; + } + + $mobPosition = $this->mob->getLocation(); + $mobWidthSqr = $this->mob->getSize()->getWidth() ** 2; + $targetPosition = $this->pathToPosition->add(0.5, 0.5, 0.5); + if (!($targetPosition->distanceSquared($mobPosition) < $mobWidthSqr) && ( + !($mobPosition->y > $this->pathToPosition->y) || + !($targetPosition->withComponents(null, $mobPosition->y, null)->distanceSquared($mobPosition) < $mobWidthSqr) + ) + ) { + $this->mob->getMoveControl()->setWantedPosition($this->pathToPosition, $this->speedModifier); + } else { + $this->pathToPosition = null; + } + } +} diff --git a/src/IvanCraft623/MobPlugin/entity/monster/Enderman.php b/src/IvanCraft623/MobPlugin/entity/monster/Enderman.php index e4f4245..f9a8e9e 100644 --- a/src/IvanCraft623/MobPlugin/entity/monster/Enderman.php +++ b/src/IvanCraft623/MobPlugin/entity/monster/Enderman.php @@ -23,59 +23,56 @@ namespace IvanCraft623\MobPlugin\entity\monster; +use IvanCraft623\MobPlugin\entity\ai\goal\enderman\FreezeWhenLookedAt; +use IvanCraft623\MobPlugin\entity\ai\goal\enderman\LeaveBlockGoal; +use IvanCraft623\MobPlugin\entity\ai\goal\enderman\LookForStaringPlayerGoal; +use IvanCraft623\MobPlugin\entity\ai\goal\enderman\TakeBlockGoal; use IvanCraft623\MobPlugin\entity\ai\goal\FloatGoal; use IvanCraft623\MobPlugin\entity\ai\goal\LookAtEntityGoal; use IvanCraft623\MobPlugin\entity\ai\goal\MeleeAttackGoal; use IvanCraft623\MobPlugin\entity\ai\goal\RandomLookAroundGoal; use IvanCraft623\MobPlugin\entity\ai\goal\RandomTeleportGoal; -use IvanCraft623\MobPlugin\entity\ai\goal\enderman\TakeBlockGoal; -use IvanCraft623\MobPlugin\entity\ai\goal\enderman\FreezeWhenLookedAt; -use IvanCraft623\MobPlugin\entity\ai\goal\enderman\LeaveBlockGoal; -use IvanCraft623\MobPlugin\entity\ai\goal\enderman\LookForStaringPlayerGoal; use IvanCraft623\MobPlugin\entity\ai\goal\target\HurtByTargetGoal; use IvanCraft623\MobPlugin\entity\ai\goal\target\NearestAttackableGoal; use IvanCraft623\MobPlugin\entity\ai\goal\WaterAvoidingRandomStrollGoal; use IvanCraft623\MobPlugin\entity\NeutralMob; use IvanCraft623\MobPlugin\entity\NeutralMobTrait; +use IvanCraft623\MobPlugin\particle\TeleportTrailParticle; use IvanCraft623\MobPlugin\pathfinder\BlockPathTypes; use IvanCraft623\MobPlugin\sound\EntityStareSound; -use IvanCraft623\MobPlugin\particle\TeleportTrailParticle; -use pocketmine\entity\EntitySizeInfo; -use pocketmine\event\entity\EntityDamageByBlockEvent; -use pocketmine\event\entity\EntityDamageByEntityEvent; -use pocketmine\event\entity\EntityDamageByChildEntityEvent; -use pocketmine\event\entity\EntityDamageEvent; -use pocketmine\nbt\tag\CompoundTag; -use pocketmine\network\mcpe\protocol\types\entity\EntityIds; -use pocketmine\player\Player; -use pocketmine\entity\Living as PMLiving; use pocketmine\block\Block; -use pocketmine\block\WaterCauldron; -use pocketmine\block\Liquid; use pocketmine\block\BlockTypeIds; -use pocketmine\item\ItemTypeIds; -use pocketmine\math\Vector3; -use pocketmine\world\BlockTransaction; -use pocketmine\math\Facing; -use pocketmine\item\VanillaItems; -use pocketmine\world\sound\EndermanTeleportSound; -use pocketmine\block\Air; -use pocketmine\block\VanillaBlocks; +use pocketmine\block\Liquid; use pocketmine\block\RuntimeBlockStateRegistry; +use pocketmine\block\VanillaBlocks; +use pocketmine\block\WaterCauldron; use pocketmine\data\bedrock\block\BlockStateDeserializeException; -use pocketmine\data\bedrock\block\BlockStateNames; use pocketmine\data\SavedDataLoadingException; -use pocketmine\nbt\tag\ByteTag; +use pocketmine\entity\EntitySizeInfo; +use pocketmine\entity\Living as PMLiving; +use pocketmine\event\entity\EntityDamageByBlockEvent; +use pocketmine\event\entity\EntityDamageEvent; +use pocketmine\item\ItemTypeIds; +use pocketmine\item\VanillaItems; +use pocketmine\math\Facing; +use pocketmine\math\Vector3; +use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\IntTag; use pocketmine\nbt\tag\ShortTag; use pocketmine\network\mcpe\convert\TypeConverter; -use pocketmine\world\format\io\GlobalBlockStateHandlers; +use pocketmine\network\mcpe\protocol\types\entity\EntityIds; use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataCollection; use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataFlags; use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataProperties; +use pocketmine\player\Player; +use pocketmine\world\BlockTransaction; +use pocketmine\world\format\io\GlobalBlockStateHandlers; +use pocketmine\world\sound\EndermanTeleportSound; -use Closure; +use function array_merge; +use function count; +use function mt_rand; class Enderman extends Monster implements NeutralMob{ use NeutralMobTrait; diff --git a/src/IvanCraft623/MobPlugin/entity/monster/Spider.php b/src/IvanCraft623/MobPlugin/entity/monster/Spider.php new file mode 100644 index 0000000..3b41f35 --- /dev/null +++ b/src/IvanCraft623/MobPlugin/entity/monster/Spider.php @@ -0,0 +1,169 @@ +goalSelector->addGoal(1, new FloatGoal($this)); + $this->goalSelector->addGoal(3, new LeapAtTargetGoal($this, 0.4)); + + $this->goalSelector->addGoal(4, new class($this) extends MeleeAttackGoal { + public function __construct(Spider $mob) { + parent::__construct($mob, 1, true); + } + + public function canUse() : bool{ + return parent::canUse(); //TODO: check there is no passenger controlling it + } + + public function canContinueToUse() : bool{ + $lightMagicValue = $this->mob->getLightLevelDependentMagicValue(); + if ($lightMagicValue >= 0.5 && $this->mob->getRandom()->nextBoundedInt(100) === 0) { + $this->mob->setTargetEntity(null); + return false; + } + + return parent::canContinueToUse(); + } + }); + + $this->goalSelector->addGoal(5, new WaterAvoidingRandomStrollGoal($this, 0.8)); + $this->goalSelector->addGoal(7, new LookAtEntityGoal($this, Player::class, 8)); + $this->goalSelector->addGoal(8, new RandomLookAroundGoal($this)); + + $this->targetSelector->addGoal(1, new HurtByTargetGoal($this)); + + $this->targetSelector->addGoal(2, new class($this) extends NearestAttackableGoal { + public function __construct(Spider $mob) { + parent::__construct(entity: $mob, targetType: Player::class); //TODO: attack golems too! + } + + public function canUse() : bool{ + return $this->entity->getLightLevelDependentMagicValue() >= 0.5 ? false : parent::canUse(); + } + }); + } + + protected function initProperties() : void{ + parent::initProperties(); + + $this->setMaxHealth(16); + } + + public function getDefaultMovementSpeed() : float{ + return 0.3; + } + + public function createNavigation() : WallClimberNavigation{ + return new WallClimberNavigation($this); + } + + public function getXpDropAmount() : int{ + return $this->hasBeenDamagedByPlayer() ? 5 : 0; + } + + public function getDrops() : array{ + $drops = parent::getDrops(); + $drops[] = VanillaItems::STRING()->setCount(mt_rand(0, 2)); //TODO: looting... + + if ($this->hasBeenDamagedByPlayer()) { + $drops[] = VanillaItems::SPIDER_EYE()->setCount(mt_rand(0, 1)); //TODO: looting... + } + + return $drops; + } + + protected function entityBaseTick(int $tickDiff = 1) : bool{ + $hasUpdate = parent::entityBaseTick($tickDiff); + + if ($this->isCollidedHorizontally !== $this->isClimbing) { + $this->setClimbing($this->isCollidedHorizontally); + + $hasUpdate = true; + } + + return $hasUpdate; + } + + public function isClimbing() : bool{ + return $this->isClimbing; + } + + public function setClimbing(bool $value) : void{ + $this->isClimbing = $value; + + $this->networkPropertiesDirty = true; + } + + protected function onClimbable() : bool{ + return $this->isClimbing; + } + + protected function syncNetworkData(EntityMetadataCollection $properties) : void{ + parent::syncNetworkData($properties); + + $properties->setGenericFlag(EntityMetadataFlags::WALLCLIMBING, $this->isClimbing); + $properties->setGenericFlag(EntityMetadataFlags::CAN_CLIMB, true); + } + + //TODO: riding and jokey stuff! + + //TODO: spawn rules code +} diff --git a/src/IvanCraft623/MobPlugin/item/ExtraItemRegisterHelper.php b/src/IvanCraft623/MobPlugin/item/ExtraItemRegisterHelper.php index 5c992ca..e3036ad 100644 --- a/src/IvanCraft623/MobPlugin/item/ExtraItemRegisterHelper.php +++ b/src/IvanCraft623/MobPlugin/item/ExtraItemRegisterHelper.php @@ -56,6 +56,7 @@ public static function registerItems() : void{ self::registerSimpleItem(ItemTypeNames::BAT_SPAWN_EGG, ExtraVanillaItems::BAT_SPAWN_EGG(), ["bat_spawn_egg"]); self::registerSimpleItem(ItemTypeNames::SLIME_SPAWN_EGG, ExtraVanillaItems::SLIME_SPAWN_EGG(), ["slime_spawn_egg"]); self::registerSimpleItem(ItemTypeNames::ENDERMAN_SPAWN_EGG, ExtraVanillaItems::ENDERMAN_SPAWN_EGG(), ["enderman_spawn_egg"]); + self::registerSimpleItem(ItemTypeNames::SPIDER_SPAWN_EGG, ExtraVanillaItems::SPIDER_SPAWN_EGG(), ["spider_spawn_egg"]); } /** diff --git a/src/IvanCraft623/MobPlugin/item/ExtraItemTypeIds.php b/src/IvanCraft623/MobPlugin/item/ExtraItemTypeIds.php index 7e9d76f..d51f07e 100644 --- a/src/IvanCraft623/MobPlugin/item/ExtraItemTypeIds.php +++ b/src/IvanCraft623/MobPlugin/item/ExtraItemTypeIds.php @@ -43,6 +43,7 @@ * @method static int PIG_SPAWN_EGG() * @method static int SHEEP_SPAWN_EGG() * @method static int SLIME_SPAWN_EGG() + * @method static int SPIDER_SPAWN_EGG() */ final class ExtraItemTypeIds{ /** @@ -62,6 +63,7 @@ protected static function setup() : void { self::register("bat_spawn_egg"); self::register("slime_spawn_egg"); self::register("enderman_spawn_egg"); + self::register("spider_spawn_egg"); } private static function verifyName(string $name) : void{ diff --git a/src/IvanCraft623/MobPlugin/item/ExtraVanillaItems.php b/src/IvanCraft623/MobPlugin/item/ExtraVanillaItems.php index ec60f89..b6c12df 100644 --- a/src/IvanCraft623/MobPlugin/item/ExtraVanillaItems.php +++ b/src/IvanCraft623/MobPlugin/item/ExtraVanillaItems.php @@ -33,6 +33,7 @@ use IvanCraft623\MobPlugin\entity\monster\Enderman; use IvanCraft623\MobPlugin\entity\monster\Endermite; use IvanCraft623\MobPlugin\entity\monster\Slime; +use IvanCraft623\MobPlugin\entity\monster\Spider; use IvanCraft623\MobPlugin\item\ExtraItemTypeIds as Ids; use pocketmine\entity\Entity; @@ -60,6 +61,7 @@ * @method static \pocketmine\item\SpawnEgg PIG_SPAWN_EGG() * @method static \pocketmine\item\SpawnEgg SHEEP_SPAWN_EGG() * @method static \pocketmine\item\SpawnEgg SLIME_SPAWN_EGG() + * @method static \pocketmine\item\SpawnEgg SPIDER_SPAWN_EGG() */ final class ExtraVanillaItems{ use CloningRegistryTrait; @@ -147,5 +149,11 @@ protected function createEntity(World $world, Vector3 $pos, float $yaw, float $p return (new Enderman(Location::fromObject($pos, $world, $yaw, $pitch)))->setPersistent(); } }); + + self::register("spider_spawn_egg", new class(new IID(Ids::SPIDER_SPAWN_EGG()), "Spider Spawn Egg") extends SpawnEgg{ + protected function createEntity(World $world, Vector3 $pos, float $yaw, float $pitch) : Entity{ + return (new Spider(Location::fromObject($pos, $world, $yaw, $pitch)))->setPersistent(); + } + }); } } diff --git a/src/IvanCraft623/MobPlugin/particle/TeleportTrailParticle.php b/src/IvanCraft623/MobPlugin/particle/TeleportTrailParticle.php index 24f53a9..941b82e 100644 --- a/src/IvanCraft623/MobPlugin/particle/TeleportTrailParticle.php +++ b/src/IvanCraft623/MobPlugin/particle/TeleportTrailParticle.php @@ -24,10 +24,9 @@ namespace IvanCraft623\MobPlugin\particle; use pocketmine\math\Vector3; +use pocketmine\nbt\tag\CompoundTag; use pocketmine\network\mcpe\protocol\LevelEventGenericPacket; use pocketmine\network\mcpe\protocol\types\LevelEvent; -use pocketmine\network\mcpe\protocol\types\ParticleIds; -use pocketmine\nbt\tag\CompoundTag; use pocketmine\world\particle\Particle; class TeleportTrailParticle implements Particle{ diff --git a/src/IvanCraft623/MobPlugin/sound/MobWarningSound.php b/src/IvanCraft623/MobPlugin/sound/MobWarningSound.php index 36c0aa8..27fa2a2 100644 --- a/src/IvanCraft623/MobPlugin/sound/MobWarningSound.php +++ b/src/IvanCraft623/MobPlugin/sound/MobWarningSound.php @@ -23,8 +23,8 @@ namespace IvanCraft623\MobPlugin\sound; -use pocketmine\entity\Entity; use pocketmine\entity\Ageable; +use pocketmine\entity\Entity; use pocketmine\math\Vector3; use pocketmine\network\mcpe\protocol\LevelSoundEventPacket; use pocketmine\network\mcpe\protocol\types\LevelSoundEvent; diff --git a/src/IvanCraft623/MobPlugin/utils/Utils.php b/src/IvanCraft623/MobPlugin/utils/Utils.php index 45fd359..c5c3ddb 100644 --- a/src/IvanCraft623/MobPlugin/utils/Utils.php +++ b/src/IvanCraft623/MobPlugin/utils/Utils.php @@ -39,6 +39,7 @@ use pocketmine\item\VanillaItems; use pocketmine\math\Vector3; use pocketmine\player\Player; +use pocketmine\world\Position; use function abs; use function array_reduce; use function cos; @@ -298,7 +299,18 @@ public static function getAdjacentPositions(Vector3 $startingPosition, int $maxD } } + public static function getLightLevelDependentMagicValue(Position $pos) : float{ + $lightPercentage = $pos->getWorld()->getFullLight($pos) / 15; + + $ambientLight = 0; //TODO: 0.1 in nether + return (float) self::lerp($ambientLight, $lightPercentage / (4 - 3 * $lightPercentage), 1); + } + public static function signum(int|float $i) : int{ return $i <=> 0; } + + public static function lerp(int|float $start, int|float $end, int|float $t) : int|float { + return $end + $start * ($t - $end); + } }