From 2d8d6b2a0a4d4f936ff3652f692dfe817acf1cbe Mon Sep 17 00:00:00 2001 From: Brennan Ward Date: Tue, 27 Jun 2023 07:53:00 -0400 Subject: [PATCH] [1.20] Add events for checking spawn conditions (SpawnPlacementCheck and PositionCheck) (#9469) --- .../net/minecraft/util/SpawnUtil.java.patch | 11 + .../world/entity/SpawnPlacements.java.patch | 10 + .../world/level/BaseSpawner.java.patch | 9 +- .../world/level/NaturalSpawner.java.patch | 18 ++ .../event/ForgeEventFactory.java | 55 ++++- .../event/entity/living/MobSpawnEvent.java | 209 ++++++++++++++++-- 6 files changed, 296 insertions(+), 16 deletions(-) create mode 100644 patches/minecraft/net/minecraft/util/SpawnUtil.java.patch diff --git a/patches/minecraft/net/minecraft/util/SpawnUtil.java.patch b/patches/minecraft/net/minecraft/util/SpawnUtil.java.patch new file mode 100644 index 0000000000..e40bfe77e2 --- /dev/null +++ b/patches/minecraft/net/minecraft/util/SpawnUtil.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/util/SpawnUtil.java ++++ b/net/minecraft/util/SpawnUtil.java +@@ -23,7 +_,7 @@ + if (p_216406_.m_6857_().m_61937_(blockpos$mutableblockpos) && m_216398_(p_216406_, p_216410_, blockpos$mutableblockpos, p_216411_)) { + T t = p_216404_.m_262451_(p_216406_, (CompoundTag)null, (Consumer)null, blockpos$mutableblockpos, p_216405_, false, false); + if (t != null) { +- if (t.m_5545_(p_216406_, p_216405_) && t.m_6914_(p_216406_)) { ++ if (net.minecraftforge.event.ForgeEventFactory.checkSpawnPosition(t, p_216406_, p_216405_)) { + p_216406_.m_47205_(t); + return Optional.of(t); + } diff --git a/patches/minecraft/net/minecraft/world/entity/SpawnPlacements.java.patch b/patches/minecraft/net/minecraft/world/entity/SpawnPlacements.java.patch index ebf14c4b9f..adadd074a7 100644 --- a/patches/minecraft/net/minecraft/world/entity/SpawnPlacements.java.patch +++ b/patches/minecraft/net/minecraft/world/entity/SpawnPlacements.java.patch @@ -8,6 +8,16 @@ public static void m_21754_(EntityType p_21755_, SpawnPlacements.Type p_21756_, Heightmap.Types p_21757_, SpawnPlacements.SpawnPredicate p_21758_) { SpawnPlacements.Data spawnplacements$data = f_21750_.put(p_21755_, new SpawnPlacements.Data(p_21757_, p_21756_, p_21758_)); if (spawnplacements$data != null) { +@@ -61,7 +_,8 @@ + + public static boolean m_217074_(EntityType p_217075_, ServerLevelAccessor p_217076_, MobSpawnType p_217077_, BlockPos p_217078_, RandomSource p_217079_) { + SpawnPlacements.Data spawnplacements$data = f_21750_.get(p_217075_); +- return spawnplacements$data == null || spawnplacements$data.f_21769_.m_217080_((EntityType)p_217075_, p_217076_, p_217077_, p_217078_, p_217079_); ++ boolean vanillaResult = spawnplacements$data == null || spawnplacements$data.f_21769_.m_217080_((EntityType)p_217075_, p_217076_, p_217077_, p_217078_, p_217079_); ++ return net.minecraftforge.event.ForgeEventFactory.checkSpawnPlacements(p_217075_, p_217076_, p_217077_, p_217078_, p_217079_, vanillaResult); + } + + static { @@ -156,10 +_,35 @@ boolean m_217080_(EntityType p_217081_, ServerLevelAccessor p_217082_, MobSpawnType p_217083_, BlockPos p_217084_, RandomSource p_217085_); } diff --git a/patches/minecraft/net/minecraft/world/level/BaseSpawner.java.patch b/patches/minecraft/net/minecraft/world/level/BaseSpawner.java.patch index fafc30bfe3..ecd33cb651 100644 --- a/patches/minecraft/net/minecraft/world/level/BaseSpawner.java.patch +++ b/patches/minecraft/net/minecraft/world/level/BaseSpawner.java.patch @@ -1,12 +1,17 @@ --- a/net/minecraft/world/level/BaseSpawner.java +++ b/net/minecraft/world/level/BaseSpawner.java -@@ -135,8 +_,10 @@ +@@ -131,12 +_,14 @@ + entity.m_7678_(entity.m_20185_(), entity.m_20186_(), entity.m_20189_(), randomsource.m_188501_() * 360.0F, 0.0F); + if (entity instanceof Mob) { + Mob mob = (Mob)entity; +- if (spawndata.m_186574_().isEmpty() && !mob.m_5545_(p_151312_, MobSpawnType.SPAWNER) || !mob.m_6914_(p_151312_)) { ++ if (!net.minecraftforge.event.ForgeEventFactory.checkSpawnPositionSpawner(mob, p_151312_, MobSpawnType.SPAWNER, spawndata, this)) { continue; } - if (spawndata.m_186567_().m_128440_() == 1 && spawndata.m_186567_().m_128425_("id", 8)) { - ((Mob)entity).m_6518_(p_151312_, p_151312_.m_6436_(entity.m_20183_()), MobSpawnType.SPAWNER, (SpawnGroupData)null, (CompoundTag)null); -+ // Forge: Patch in the spawn event for spawners so it may be fired unconditionally, instead of only when vanilla normally would trigger it. ++ // Forge: Patch in FinalizeSpawn for spawners so it may be fired unconditionally, instead of only when vanilla normally would trigger it. + var event = net.minecraftforge.event.ForgeEventFactory.onFinalizeSpawnSpawner(mob, p_151312_, p_151312_.m_6436_(entity.m_20183_()), null, compoundtag, this); + if (event != null && spawndata.m_186567_().m_128440_() == 1 && spawndata.m_186567_().m_128425_("id", 8)) { + ((Mob)entity).m_6518_(p_151312_, event.getDifficulty(), event.getSpawnType(), event.getSpawnData(), event.getSpawnTag()); diff --git a/patches/minecraft/net/minecraft/world/level/NaturalSpawner.java.patch b/patches/minecraft/net/minecraft/world/level/NaturalSpawner.java.patch index 9311d401fa..acf2d37912 100644 --- a/patches/minecraft/net/minecraft/world/level/NaturalSpawner.java.patch +++ b/patches/minecraft/net/minecraft/world/level/NaturalSpawner.java.patch @@ -26,6 +26,15 @@ return; } +@@ -244,7 +_,7 @@ + if (p_46994_ > (double)(p_46993_.m_6095_().m_20674_().m_21611_() * p_46993_.m_6095_().m_20674_().m_21611_()) && p_46993_.m_6785_(p_46994_)) { + return false; + } else { +- return p_46993_.m_5545_(p_46992_, MobSpawnType.NATURAL) && p_46993_.m_6914_(p_46992_); ++ return net.minecraftforge.event.ForgeEventFactory.checkSpawnPosition(p_46993_, p_46992_, MobSpawnType.NATURAL); + } + } + @@ -258,7 +_,8 @@ } @@ -66,3 +75,12 @@ } } +@@ -366,7 +_,7 @@ + entity.m_7678_(d0, (double)blockpos.m_123342_(), d1, p_220454_.m_188501_() * 360.0F, 0.0F); + if (entity instanceof Mob) { + Mob mob = (Mob)entity; +- if (mob.m_5545_(p_220451_, MobSpawnType.CHUNK_GENERATION) && mob.m_6914_(p_220451_)) { ++ if (net.minecraftforge.event.ForgeEventFactory.checkSpawnPosition(mob, p_220451_, MobSpawnType.CHUNK_GENERATION)) { + spawngroupdata = mob.m_6518_(p_220451_, p_220451_.m_6436_(mob.m_20183_()), MobSpawnType.CHUNK_GENERATION, spawngroupdata, (CompoundTag)null); + p_220451_.m_47205_(mob); + flag = true; diff --git a/src/main/java/net/minecraftforge/event/ForgeEventFactory.java b/src/main/java/net/minecraftforge/event/ForgeEventFactory.java index fce09c2b0f..6b02d7fbd3 100644 --- a/src/main/java/net/minecraftforge/event/ForgeEventFactory.java +++ b/src/main/java/net/minecraftforge/event/ForgeEventFactory.java @@ -40,6 +40,7 @@ import net.minecraft.commands.Commands; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Pose; import net.minecraft.world.entity.SpawnGroupData; +import net.minecraft.world.entity.SpawnPlacements; import net.minecraft.world.entity.EntityDimensions; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.Mob; @@ -75,6 +76,7 @@ import net.minecraft.world.level.Explosion; import net.minecraft.world.level.GameRules; import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.ServerLevelAccessor; +import net.minecraft.world.level.SpawnData; import net.minecraft.world.level.Level; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.storage.ServerLevelData; @@ -103,6 +105,8 @@ import net.minecraftforge.event.entity.living.LivingHealEvent; import net.minecraftforge.event.entity.living.LivingPackSizeEvent; import net.minecraftforge.event.entity.living.MobSpawnEvent; import net.minecraftforge.event.entity.living.MobSpawnEvent.AllowDespawn; +import net.minecraftforge.event.entity.living.MobSpawnEvent.PositionCheck; +import net.minecraftforge.event.entity.living.MobSpawnEvent.SpawnPlacementCheck; import net.minecraftforge.event.entity.living.ZombieEvent.SummonAidEvent; import net.minecraftforge.event.entity.player.AdvancementEvent.AdvancementEarnEvent; import net.minecraftforge.event.entity.player.AdvancementEvent.AdvancementProgressEvent; @@ -187,6 +191,55 @@ public class ForgeEventFactory MinecraftForge.EVENT_BUS.post(new PlayerDestroyItemEvent(player, stack, hand)); } + /** + * Internal, should only be called via {@link SpawnPlacements#checkSpawnRules}. + * @see SpawnPlacementCheck + */ + @ApiStatus.Internal + public static boolean checkSpawnPlacements(EntityType entityType, ServerLevelAccessor level, MobSpawnType spawnType, BlockPos pos, RandomSource random, boolean defaultResult) + { + var event = new SpawnPlacementCheck(entityType, level, spawnType, pos, random, defaultResult); + MinecraftForge.EVENT_BUS.post(event); + return event.getResult() == Result.DEFAULT ? defaultResult : event.getResult() == Result.ALLOW; + } + + /** + * Checks if the current position of the passed mob is valid for spawning, by firing {@link PositionCheck}.
+ * The default check is to perform the logical and of {@link Mob#checkSpawnRules} and {@link Mob#checkSpawnObstruction}.
+ * @param mob The mob being spawned. + * @param level The level the mob will be added to, if successful. + * @param spawnType The spawn type of the spawn. + * @return True, if the position is valid, as determined by the contract of {@link PositionCheck}. + * @see PositionCheck + */ + public static boolean checkSpawnPosition(Mob mob, ServerLevelAccessor level, MobSpawnType spawnType) + { + var event = new PositionCheck(mob, level, spawnType, null); + MinecraftForge.EVENT_BUS.post(event); + if (event.getResult() == Result.DEFAULT) + { + return mob.checkSpawnRules(level, spawnType) && mob.checkSpawnObstruction(level); + } + return event.getResult() == Result.ALLOW; + } + + /** + * Specialized variant of {@link #checkSpawnPosition} for spawners, as they have slightly different checks. + * @see #CheckSpawnPosition + * @implNote See in-line comments about custom spawn rules. + */ + public static boolean checkSpawnPositionSpawner(Mob mob, ServerLevelAccessor level, MobSpawnType spawnType, SpawnData spawnData, BaseSpawner spawner) + { + var event = new PositionCheck(mob, level, spawnType, null); + MinecraftForge.EVENT_BUS.post(event); + if (event.getResult() == Result.DEFAULT) + { + // Spawners do not evaluate Mob#checkSpawnRules if any custom rules are present. This is despite the fact that these two methods do not check the same things. + return (spawnData.getCustomSpawnRules().isPresent() || mob.checkSpawnRules(level, spawnType)) && mob.checkSpawnObstruction(level); + } + return event.getResult() == Result.ALLOW; + } + /** * Vanilla calls to {@link Mob#finalizeSpawn} are replaced with calls to this method via coremod.
* Mods should call this method in place of calling {@link Mob#finalizeSpawn}. Super calls (from within overrides) should not be wrapped. @@ -236,7 +289,7 @@ public class ForgeEventFactory * Returns the FinalizeSpawn event instance, or null if it was canceled.
* This is separate since mob spawners perform special finalizeSpawn handling when NBT data is present, but we still want to fire the event.
* This overload is also the only way to pass through a {@link BaseSpawner} instance. - * @see MobSpawnEvent.FinalizeSpawn + * @see #onFinalizeSpawn */ @Nullable public static MobSpawnEvent.FinalizeSpawn onFinalizeSpawnSpawner(Mob mob, ServerLevelAccessor level, DifficultyInstance difficulty, @Nullable SpawnGroupData spawnData, @Nullable CompoundTag spawnTag, BaseSpawner spawner) diff --git a/src/main/java/net/minecraftforge/event/entity/living/MobSpawnEvent.java b/src/main/java/net/minecraftforge/event/entity/living/MobSpawnEvent.java index 6c6db13de2..471a10a80a 100644 --- a/src/main/java/net/minecraftforge/event/entity/living/MobSpawnEvent.java +++ b/src/main/java/net/minecraftforge/event/entity/living/MobSpawnEvent.java @@ -8,24 +8,38 @@ package net.minecraftforge.event.entity.living; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; +import net.minecraft.core.BlockPos; import net.minecraft.nbt.CompoundTag; import net.minecraft.server.level.WorldGenRegion; +import net.minecraft.util.RandomSource; import net.minecraft.world.DifficultyInstance; +import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.MobSpawnType; import net.minecraft.world.entity.SpawnGroupData; +import net.minecraft.world.entity.SpawnPlacements; import net.minecraft.world.level.BaseSpawner; import net.minecraft.world.level.ServerLevelAccessor; import net.minecraftforge.common.ForgeInternalHandler; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.event.ForgeEventFactory; import net.minecraftforge.event.entity.EntityEvent; +import net.minecraftforge.event.entity.SpawnPlacementRegisterEvent; import net.minecraftforge.eventbus.api.Cancelable; +import net.minecraftforge.eventbus.api.Event; import net.minecraftforge.fml.LogicalSide; /** - * The subclasses of this event are fired whenever a mob performs a spawning-related action. + * This class holds all events relating to the entire flow of mob spawns.
+ * Currently, the events have the following flow for any given mob spawn: + *

+ * Before the spawn is attempted {@link SpawnPlacementCheck} is fired, to determine if the spawn may occur based on mob-specific rules.
+ * After the entity is created {@link PositionCheck} is fired, to determine if the selected position is legal for the entity.
+ * If both checks succeeded, {@link FinalizeSpawn} is fired, which performs initialization on the newly-spawned entity.
+ * Finally, if the spawn was not cancelled via {@link FinalizeSpawn#setSpawnCancelled}, then {@link EntityJoinLevelEvent} is fired as the entity enters the world. + *

+ * {@link AllowDespawn} is not related to the mob spawn event flow, as it fires when a despawn is attempted. */ public abstract class MobSpawnEvent extends EntityEvent { @@ -34,11 +48,8 @@ public abstract class MobSpawnEvent extends EntityEvent private final double y; private final double z; - /** - * @apiNote Do not construct directly. Access via {@link ForgeEventFactory#onFinalizeSpawn}. - */ @ApiStatus.Internal - public MobSpawnEvent(Mob mob, ServerLevelAccessor level, double x, double y, double z) + protected MobSpawnEvent(Mob mob, ServerLevelAccessor level, double x, double y, double z) { super(mob); this.level = level; @@ -85,6 +96,166 @@ public abstract class MobSpawnEvent extends EntityEvent return this.z; } + /** + * This event is fired {@linkplain SpawnPlacements#checkSpawnRules when Spawn Placements (aka Spawn Rules) are checked}, before a mob attempts to spawn.
+ * Spawn Placement checks include light levels, slime chunks, grass blocks for animals, and others in the same vein.
+ * The purpose of this event is to permit runtime changes to any or all spawn placement logic without having to wrap the placement for each entity. + *

+ * This event {@linkplain HasResult has a result}.
+ * To change the result of this event, use {@link #setResult}. Results are interpreted in the following manner: + *

    + *
  • Allow - The check will succeed, and the spawn process will continue.
  • + *
  • Default - The value of the vanilla check will be used to determine success.
  • + *
  • Deny - The check will fail, and the spawn process will abort.
  • + *
+ * This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus}, + * only on the {@linkplain LogicalSide#SERVER logical server}. + *

+ * This event is not fired for mob spawners which utilize {@link CustomSpawnRules}, as they do not check spawn placements. + * @apiNote If your modifications are for a single entity, and do not vary at runtime, use {@link SpawnPlacementRegisterEvent}. + * @see SpawnPlacementRegisterEvent + */ + @HasResult + public static class SpawnPlacementCheck extends Event + { + private final EntityType entityType; + private final ServerLevelAccessor level; + private final MobSpawnType spawnType; + private final BlockPos pos; + private final RandomSource random; + private final boolean defaultResult; + + /** + * Internal. + * @see {@link SpawnPlacements#checkSpawnRules} for the single call site of this event. + */ + @ApiStatus.Internal + public SpawnPlacementCheck(EntityType entityType, ServerLevelAccessor level, MobSpawnType spawnType, BlockPos pos, RandomSource random, boolean defaultResult) + { + this.entityType = entityType; + this.level = level; + this.spawnType = spawnType; + this.pos = pos; + this.random = random; + this.defaultResult = defaultResult; + } + + /** + * @return The type of entity that checks are being performed for. + */ + public EntityType getEntityType() + { + return this.entityType; + } + + /** + * @return The level relating to the mob spawn action + */ + public ServerLevelAccessor getLevel() + { + return this.level; + } + + /** + * Retrieves the type of mob spawn that is happening. + * @return The mob spawn type. + * @see MobSpawnType + */ + public MobSpawnType getSpawnType() + { + return this.spawnType; + } + + /** + * @return The position where checks are being evaluated. + */ + public BlockPos getPos() + { + return this.pos; + } + + /** + * In all vanilla cases, this is equal to {@link ServerLevelAccessor#getRandom()}. + * @return The random source being used. + */ + public RandomSource getRandom() + { + return this.random; + } + + /** + * The default vanilla result is useful if an additional check wants to force {@link Result#ALLOW} only if the vanilla check would succeed. + * @return The result of the vanilla spawn placement check. + */ + public boolean getDefaultResult() + { + return this.defaultResult; + } + } + + /** + * This event is fired when a mob checks for a valid spawn position, after {@link SpawnPlacements#checkSpawnRules} has been evaluated.
+ * Conditions validated here include the following: + *

    + *
  • Obstruction - mobs inside blocks or fluids.
  • + *
  • Pathfinding - if the spawn block is valid for pathfinding.
  • + *
  • Sea Level - Ocelots check if the position is above sea level.
  • + *
  • Spawn Block - Ocelots check if the below block is grass or leaves.
  • + *
+ *

+ * These checks are performed by the vanilla methods {@link Mob#checkSpawnRules} and {@link Mob#checkSpawnObstruction}.
+ * The logical-and of both methods forms the default result of this event. + *

+ * This event {@linkplain HasResult has a result}.
+ * To change the result of this event, use {@link #setResult}. Results are interpreted in the following manner: + *

    + *
  • Allow - The position will be accepted, and the spawn process will continue.
  • + *
  • Default - The position will be accepted if {@link Mob#checkSpawnRules} and {@link Mob#checkSpawnObstruction} are both true.
  • + *
  • Deny - The position will not be accepted. The spawn process will abort, and further events will not be called.
  • + *
+ * This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus}, + * only on the {@linkplain LogicalSide#SERVER logical server}. + * + * @apiNote This event fires after Spawn Placement checks, which are the primary set of spawn checks. + * @see {@link SpawnPlacementRegisterEvent} To modify spawn placements statically at startup. + * @see {@link SpawnPlacementCheck} To modify the result of spawn placements at runtime. + */ + @HasResult + public static class PositionCheck extends MobSpawnEvent + { + @Nullable + private final BaseSpawner spawner; + private final MobSpawnType spawnType; + + public PositionCheck(Mob mob, ServerLevelAccessor level, MobSpawnType spawnType, @Nullable BaseSpawner spawner) + { + super(mob, level, mob.getX(), mob.getY(), mob.getZ()); + this.spawnType = spawnType; + this.spawner = spawner; + } + + /** + * Retrieves the underlying {@link BaseSpawner} instance if this mob was created by a Mob Spawner of some form. + * This is always null unless {@link #getSpawnType()} is {@link MobSpawnType#SPAWNER}, and may still be null even then. + * @return The BaseSpawner responsible for triggering the spawn, or null if none is available. + */ + @Nullable + public BaseSpawner getSpawner() + { + return spawner; + } + + /** + * Retrieves the type of mob spawn that is happening. + * @return The mob spawn type. + * @see MobSpawnType + */ + public MobSpawnType getSpawnType() + { + return this.spawnType; + } + } + /** * This event is fired before {@link Mob#finalizeSpawn} is called.
* This allows mods to control mob initialization.
@@ -101,12 +272,19 @@ public abstract class MobSpawnEvent extends EntityEvent public static class FinalizeSpawn extends MobSpawnEvent { private final MobSpawnType spawnType; - @Nullable private final BaseSpawner spawner; - - private DifficultyInstance difficulty; - @Nullable private SpawnGroupData spawnData; - @Nullable private CompoundTag spawnTag; + @Nullable + private final BaseSpawner spawner; + private DifficultyInstance difficulty; + @Nullable + private SpawnGroupData spawnData; + @Nullable + private CompoundTag spawnTag; + + /** + * @apiNote Do not construct directly. Access via {@link ForgeEventFactory#onFinalizeSpawn} / {@link ForgeEventFactory#onFinalizeSpawnSpawner}. + */ + @ApiStatus.Internal public FinalizeSpawn(Mob entity, ServerLevelAccessor level, double x, double y, double z, DifficultyInstance difficulty, MobSpawnType spawnType, @Nullable SpawnGroupData spawnData, @Nullable CompoundTag spawnTag, @Nullable BaseSpawner spawner) { super(entity, level, x, y, z); @@ -135,7 +313,7 @@ public abstract class MobSpawnEvent extends EntityEvent { this.difficulty = inst; } - + /** * Retrieves the type of mob spawn that happened (the event that caused the spawn). The enum names are self-explanatory. * @return The mob spawn type. @@ -206,7 +384,8 @@ public abstract class MobSpawnEvent extends EntityEvent * Usually that has no side effects, but callers should be aware. * @param cancel If the spawn should be cancelled (or not). */ - public void setSpawnCancelled(boolean cancel) { + public void setSpawnCancelled(boolean cancel) + { this.getEntity().setSpawnCancelled(cancel); } @@ -215,7 +394,8 @@ public abstract class MobSpawnEvent extends EntityEvent * @return If this mob's spawn is cancelled or not. * @implNote This is enforced in {@link ForgeInternalHandler#builtinMobSpawnBlocker} and a patch in {@link WorldGenRegion#addEntity} */ - public boolean isSpawnCancelled() { + public boolean isSpawnCancelled() + { return this.getEntity().isSpawnCancelled(); } } @@ -236,6 +416,8 @@ public abstract class MobSpawnEvent extends EntityEvent * @see LivingEntity#checkDespawn() * @author cpw */ + // TODO: 1.20 Move to standalone class, as it is unrelated to the complex mob spawning flow. + // Such a refactor will allow the BaseSpawner and MobSpawnType params to be hoisted to MobSpawnEvent. @HasResult public static class AllowDespawn extends MobSpawnEvent { @@ -244,4 +426,5 @@ public abstract class MobSpawnEvent extends EntityEvent super(mob, level, mob.getX(), mob.getY(), mob.getZ()); } } + }