2247 lines
70 KiB
Java
2247 lines
70 KiB
Java
package net.minecraft.world.entity.player;
|
|
|
|
import com.google.common.collect.ImmutableList;
|
|
import com.google.common.collect.ImmutableMap;
|
|
import com.google.common.collect.Lists;
|
|
import com.mojang.authlib.GameProfile;
|
|
import com.mojang.datafixers.util.Either;
|
|
import com.mojang.logging.LogUtils;
|
|
import java.util.Collection;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Optional;
|
|
import java.util.OptionalInt;
|
|
import java.util.function.Predicate;
|
|
import net.minecraft.Util;
|
|
import net.minecraft.advancements.CriteriaTriggers;
|
|
import net.minecraft.core.BlockPos;
|
|
import net.minecraft.core.Direction;
|
|
import net.minecraft.core.GlobalPos;
|
|
import net.minecraft.core.particles.ParticleTypes;
|
|
import net.minecraft.nbt.CompoundTag;
|
|
import net.minecraft.nbt.ListTag;
|
|
import net.minecraft.nbt.NbtOps;
|
|
import net.minecraft.nbt.NbtUtils;
|
|
import net.minecraft.network.chat.ClickEvent;
|
|
import net.minecraft.network.chat.Component;
|
|
import net.minecraft.network.chat.MutableComponent;
|
|
import net.minecraft.network.protocol.game.ClientboundSetEntityMotionPacket;
|
|
import net.minecraft.network.syncher.EntityDataAccessor;
|
|
import net.minecraft.network.syncher.EntityDataSerializers;
|
|
import net.minecraft.network.syncher.SynchedEntityData;
|
|
import net.minecraft.resources.ResourceLocation;
|
|
import net.minecraft.server.level.ServerLevel;
|
|
import net.minecraft.server.level.ServerPlayer;
|
|
import net.minecraft.sounds.SoundEvent;
|
|
import net.minecraft.sounds.SoundEvents;
|
|
import net.minecraft.sounds.SoundSource;
|
|
import net.minecraft.stats.Stat;
|
|
import net.minecraft.stats.Stats;
|
|
import net.minecraft.tags.BlockTags;
|
|
import net.minecraft.tags.DamageTypeTags;
|
|
import net.minecraft.tags.EntityTypeTags;
|
|
import net.minecraft.tags.FluidTags;
|
|
import net.minecraft.util.Mth;
|
|
import net.minecraft.util.Unit;
|
|
import net.minecraft.world.Container;
|
|
import net.minecraft.world.Difficulty;
|
|
import net.minecraft.world.InteractionHand;
|
|
import net.minecraft.world.InteractionResult;
|
|
import net.minecraft.world.MenuProvider;
|
|
import net.minecraft.world.damagesource.DamageSource;
|
|
import net.minecraft.world.effect.MobEffectInstance;
|
|
import net.minecraft.world.effect.MobEffectUtil;
|
|
import net.minecraft.world.effect.MobEffects;
|
|
import net.minecraft.world.entity.Entity;
|
|
import net.minecraft.world.entity.EntityAttachment;
|
|
import net.minecraft.world.entity.EntityAttachments;
|
|
import net.minecraft.world.entity.EntityDimensions;
|
|
import net.minecraft.world.entity.EntityType;
|
|
import net.minecraft.world.entity.EquipmentSlot;
|
|
import net.minecraft.world.entity.HumanoidArm;
|
|
import net.minecraft.world.entity.LivingEntity;
|
|
import net.minecraft.world.entity.MoverType;
|
|
import net.minecraft.world.entity.Pose;
|
|
import net.minecraft.world.entity.SlotAccess;
|
|
import net.minecraft.world.entity.TamableAnimal;
|
|
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
|
|
import net.minecraft.world.entity.ai.attributes.Attributes;
|
|
import net.minecraft.world.entity.animal.Parrot;
|
|
import net.minecraft.world.entity.animal.horse.AbstractHorse;
|
|
import net.minecraft.world.entity.boss.EnderDragonPart;
|
|
import net.minecraft.world.entity.decoration.ArmorStand;
|
|
import net.minecraft.world.entity.item.ItemEntity;
|
|
import net.minecraft.world.entity.monster.warden.WardenSpawnTracker;
|
|
import net.minecraft.world.entity.projectile.FishingHook;
|
|
import net.minecraft.world.entity.projectile.Projectile;
|
|
import net.minecraft.world.entity.projectile.ProjectileDeflection;
|
|
import net.minecraft.world.food.FoodData;
|
|
import net.minecraft.world.food.FoodProperties;
|
|
import net.minecraft.world.inventory.AbstractContainerMenu;
|
|
import net.minecraft.world.inventory.ClickAction;
|
|
import net.minecraft.world.inventory.InventoryMenu;
|
|
import net.minecraft.world.inventory.PlayerEnderChestContainer;
|
|
import net.minecraft.world.item.ElytraItem;
|
|
import net.minecraft.world.item.ItemCooldowns;
|
|
import net.minecraft.world.item.ItemStack;
|
|
import net.minecraft.world.item.Items;
|
|
import net.minecraft.world.item.ProjectileWeaponItem;
|
|
import net.minecraft.world.item.SwordItem;
|
|
import net.minecraft.world.item.crafting.RecipeHolder;
|
|
import net.minecraft.world.item.enchantment.EnchantmentEffectComponents;
|
|
import net.minecraft.world.item.enchantment.EnchantmentHelper;
|
|
import net.minecraft.world.item.trading.MerchantOffers;
|
|
import net.minecraft.world.level.BaseCommandBlock;
|
|
import net.minecraft.world.level.GameRules;
|
|
import net.minecraft.world.level.GameType;
|
|
import net.minecraft.world.level.Level;
|
|
import net.minecraft.world.level.block.entity.CommandBlockEntity;
|
|
import net.minecraft.world.level.block.entity.JigsawBlockEntity;
|
|
import net.minecraft.world.level.block.entity.SignBlockEntity;
|
|
import net.minecraft.world.level.block.entity.StructureBlockEntity;
|
|
import net.minecraft.world.level.block.state.BlockState;
|
|
import net.minecraft.world.level.block.state.pattern.BlockInWorld;
|
|
import net.minecraft.world.level.gameevent.GameEvent;
|
|
import net.minecraft.world.phys.AABB;
|
|
import net.minecraft.world.phys.Vec3;
|
|
import net.minecraft.world.scores.PlayerTeam;
|
|
import net.minecraft.world.scores.Scoreboard;
|
|
import net.minecraft.world.scores.Team;
|
|
import org.jetbrains.annotations.NotNull;
|
|
import org.jetbrains.annotations.Nullable;
|
|
import org.slf4j.Logger;
|
|
|
|
public abstract class Player extends LivingEntity {
|
|
private static final Logger LOGGER = LogUtils.getLogger();
|
|
public static final HumanoidArm DEFAULT_MAIN_HAND = HumanoidArm.RIGHT;
|
|
public static final int DEFAULT_MODEL_CUSTOMIZATION = 0;
|
|
public static final int MAX_HEALTH = 20;
|
|
public static final int SLEEP_DURATION = 100;
|
|
public static final int WAKE_UP_DURATION = 10;
|
|
public static final int ENDER_SLOT_OFFSET = 200;
|
|
public static final int HELD_ITEM_SLOT = 499;
|
|
public static final int CRAFTING_SLOT_OFFSET = 500;
|
|
public static final float DEFAULT_BLOCK_INTERACTION_RANGE = 4.5F;
|
|
public static final float DEFAULT_ENTITY_INTERACTION_RANGE = 3.0F;
|
|
public static final float CROUCH_BB_HEIGHT = 1.5F;
|
|
public static final float SWIMMING_BB_WIDTH = 0.6F;
|
|
public static final float SWIMMING_BB_HEIGHT = 0.6F;
|
|
public static final float DEFAULT_EYE_HEIGHT = 1.62F;
|
|
private static final int CURRENT_IMPULSE_CONTEXT_RESET_GRACE_TIME_TICKS = 40;
|
|
public static final Vec3 DEFAULT_VEHICLE_ATTACHMENT = new Vec3(0.0, 0.6, 0.0);
|
|
public static final EntityDimensions STANDING_DIMENSIONS = EntityDimensions.scalable(0.6F, 1.8F)
|
|
.withEyeHeight(1.62F)
|
|
.withAttachments(EntityAttachments.builder().attach(EntityAttachment.VEHICLE, DEFAULT_VEHICLE_ATTACHMENT));
|
|
private static final Map<Pose, EntityDimensions> POSES = ImmutableMap.<Pose, EntityDimensions>builder()
|
|
.put(Pose.STANDING, STANDING_DIMENSIONS)
|
|
.put(Pose.SLEEPING, SLEEPING_DIMENSIONS)
|
|
.put(Pose.FALL_FLYING, EntityDimensions.scalable(0.6F, 0.6F).withEyeHeight(0.4F))
|
|
.put(Pose.SWIMMING, EntityDimensions.scalable(0.6F, 0.6F).withEyeHeight(0.4F))
|
|
.put(Pose.SPIN_ATTACK, EntityDimensions.scalable(0.6F, 0.6F).withEyeHeight(0.4F))
|
|
.put(
|
|
Pose.CROUCHING,
|
|
EntityDimensions.scalable(0.6F, 1.5F)
|
|
.withEyeHeight(1.27F)
|
|
.withAttachments(EntityAttachments.builder().attach(EntityAttachment.VEHICLE, DEFAULT_VEHICLE_ATTACHMENT))
|
|
)
|
|
.put(Pose.DYING, EntityDimensions.fixed(0.2F, 0.2F).withEyeHeight(1.62F))
|
|
.build();
|
|
private static final EntityDataAccessor<Float> DATA_PLAYER_ABSORPTION_ID = SynchedEntityData.defineId(Player.class, EntityDataSerializers.FLOAT);
|
|
private static final EntityDataAccessor<Integer> DATA_SCORE_ID = SynchedEntityData.defineId(Player.class, EntityDataSerializers.INT);
|
|
protected static final EntityDataAccessor<Byte> DATA_PLAYER_MODE_CUSTOMISATION = SynchedEntityData.defineId(Player.class, EntityDataSerializers.BYTE);
|
|
protected static final EntityDataAccessor<Byte> DATA_PLAYER_MAIN_HAND = SynchedEntityData.defineId(Player.class, EntityDataSerializers.BYTE);
|
|
protected static final EntityDataAccessor<CompoundTag> DATA_SHOULDER_LEFT = SynchedEntityData.defineId(Player.class, EntityDataSerializers.COMPOUND_TAG);
|
|
protected static final EntityDataAccessor<CompoundTag> DATA_SHOULDER_RIGHT = SynchedEntityData.defineId(Player.class, EntityDataSerializers.COMPOUND_TAG);
|
|
private long timeEntitySatOnShoulder;
|
|
final Inventory inventory = new Inventory(this);
|
|
protected PlayerEnderChestContainer enderChestInventory = new PlayerEnderChestContainer();
|
|
public final InventoryMenu inventoryMenu;
|
|
public AbstractContainerMenu containerMenu;
|
|
protected FoodData foodData = new FoodData();
|
|
protected int jumpTriggerTime;
|
|
public float oBob;
|
|
public float bob;
|
|
public int takeXpDelay;
|
|
public double xCloakO;
|
|
public double yCloakO;
|
|
public double zCloakO;
|
|
public double xCloak;
|
|
public double yCloak;
|
|
public double zCloak;
|
|
private int sleepCounter;
|
|
protected boolean wasUnderwater;
|
|
private final Abilities abilities = new Abilities();
|
|
public int experienceLevel;
|
|
public int totalExperience;
|
|
public float experienceProgress;
|
|
protected int enchantmentSeed;
|
|
protected final float defaultFlySpeed = 0.02F;
|
|
private int lastLevelUpTime;
|
|
/**
|
|
* The player's unique game profile
|
|
*/
|
|
private final GameProfile gameProfile;
|
|
private boolean reducedDebugInfo;
|
|
private ItemStack lastItemInMainHand = ItemStack.EMPTY;
|
|
private final ItemCooldowns cooldowns = this.createItemCooldowns();
|
|
private Optional<GlobalPos> lastDeathLocation = Optional.empty();
|
|
@Nullable
|
|
public FishingHook fishing;
|
|
protected float hurtDir;
|
|
@Nullable
|
|
public Vec3 currentImpulseImpactPos;
|
|
@Nullable
|
|
public Entity currentExplosionCause;
|
|
private boolean ignoreFallDamageFromCurrentImpulse;
|
|
private int currentImpulseContextResetGraceTime;
|
|
|
|
public Player(Level level, BlockPos pos, float yRot, GameProfile gameProfile) {
|
|
super(EntityType.PLAYER, level);
|
|
this.setUUID(gameProfile.getId());
|
|
this.gameProfile = gameProfile;
|
|
this.inventoryMenu = new InventoryMenu(this.inventory, !level.isClientSide, this);
|
|
this.containerMenu = this.inventoryMenu;
|
|
this.moveTo(pos.getX() + 0.5, pos.getY() + 1, pos.getZ() + 0.5, yRot, 0.0F);
|
|
this.rotOffs = 180.0F;
|
|
}
|
|
|
|
public boolean blockActionRestricted(Level level, BlockPos pos, GameType gameMode) {
|
|
if (!gameMode.isBlockPlacingRestricted()) {
|
|
return false;
|
|
} else if (gameMode == GameType.SPECTATOR) {
|
|
return true;
|
|
} else if (this.mayBuild()) {
|
|
return false;
|
|
} else {
|
|
ItemStack itemStack = this.getMainHandItem();
|
|
return itemStack.isEmpty() || !itemStack.canBreakBlockInAdventureMode(new BlockInWorld(level, pos, false));
|
|
}
|
|
}
|
|
|
|
public static AttributeSupplier.Builder createAttributes() {
|
|
return LivingEntity.createLivingAttributes()
|
|
.add(Attributes.ATTACK_DAMAGE, 1.0)
|
|
.add(Attributes.MOVEMENT_SPEED, 0.1F)
|
|
.add(Attributes.ATTACK_SPEED)
|
|
.add(Attributes.LUCK)
|
|
.add(Attributes.BLOCK_INTERACTION_RANGE, 4.5)
|
|
.add(Attributes.ENTITY_INTERACTION_RANGE, 3.0)
|
|
.add(Attributes.BLOCK_BREAK_SPEED)
|
|
.add(Attributes.SUBMERGED_MINING_SPEED)
|
|
.add(Attributes.SNEAKING_SPEED)
|
|
.add(Attributes.MINING_EFFICIENCY)
|
|
.add(Attributes.SWEEPING_DAMAGE_RATIO);
|
|
}
|
|
|
|
@Override
|
|
protected void defineSynchedData(SynchedEntityData.Builder builder) {
|
|
super.defineSynchedData(builder);
|
|
builder.define(DATA_PLAYER_ABSORPTION_ID, 0.0F);
|
|
builder.define(DATA_SCORE_ID, 0);
|
|
builder.define(DATA_PLAYER_MODE_CUSTOMISATION, (byte)0);
|
|
builder.define(DATA_PLAYER_MAIN_HAND, (byte)DEFAULT_MAIN_HAND.getId());
|
|
builder.define(DATA_SHOULDER_LEFT, new CompoundTag());
|
|
builder.define(DATA_SHOULDER_RIGHT, new CompoundTag());
|
|
}
|
|
|
|
@Override
|
|
public void tick() {
|
|
this.noPhysics = this.isSpectator();
|
|
if (this.isSpectator()) {
|
|
this.setOnGround(false);
|
|
}
|
|
|
|
if (this.takeXpDelay > 0) {
|
|
this.takeXpDelay--;
|
|
}
|
|
|
|
if (this.isSleeping()) {
|
|
this.sleepCounter++;
|
|
if (this.sleepCounter > 100) {
|
|
this.sleepCounter = 100;
|
|
}
|
|
|
|
if (!this.level().isClientSide && this.level().isDay()) {
|
|
this.stopSleepInBed(false, true);
|
|
}
|
|
} else if (this.sleepCounter > 0) {
|
|
this.sleepCounter++;
|
|
if (this.sleepCounter >= 110) {
|
|
this.sleepCounter = 0;
|
|
}
|
|
}
|
|
|
|
this.updateIsUnderwater();
|
|
super.tick();
|
|
if (!this.level().isClientSide && this.containerMenu != null && !this.containerMenu.stillValid(this)) {
|
|
this.closeContainer();
|
|
this.containerMenu = this.inventoryMenu;
|
|
}
|
|
|
|
this.moveCloak();
|
|
if (!this.level().isClientSide) {
|
|
this.foodData.tick(this);
|
|
this.awardStat(Stats.PLAY_TIME);
|
|
this.awardStat(Stats.TOTAL_WORLD_TIME);
|
|
if (this.isAlive()) {
|
|
this.awardStat(Stats.TIME_SINCE_DEATH);
|
|
}
|
|
|
|
if (this.isDiscrete()) {
|
|
this.awardStat(Stats.CROUCH_TIME);
|
|
}
|
|
|
|
if (!this.isSleeping()) {
|
|
this.awardStat(Stats.TIME_SINCE_REST);
|
|
}
|
|
}
|
|
|
|
int i = 29999999;
|
|
double d = Mth.clamp(this.getX(), -2.9999999E7, 2.9999999E7);
|
|
double e = Mth.clamp(this.getZ(), -2.9999999E7, 2.9999999E7);
|
|
if (d != this.getX() || e != this.getZ()) {
|
|
this.setPos(d, this.getY(), e);
|
|
}
|
|
|
|
this.attackStrengthTicker++;
|
|
ItemStack itemStack = this.getMainHandItem();
|
|
if (!ItemStack.matches(this.lastItemInMainHand, itemStack)) {
|
|
if (!ItemStack.isSameItem(this.lastItemInMainHand, itemStack)) {
|
|
this.resetAttackStrengthTicker();
|
|
}
|
|
|
|
this.lastItemInMainHand = itemStack.copy();
|
|
}
|
|
|
|
this.turtleHelmetTick();
|
|
this.cooldowns.tick();
|
|
this.updatePlayerPose();
|
|
if (this.currentImpulseContextResetGraceTime > 0) {
|
|
this.currentImpulseContextResetGraceTime--;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected float getMaxHeadRotationRelativeToBody() {
|
|
return this.isBlocking() ? 15.0F : super.getMaxHeadRotationRelativeToBody();
|
|
}
|
|
|
|
public boolean isSecondaryUseActive() {
|
|
return this.isShiftKeyDown();
|
|
}
|
|
|
|
protected boolean wantsToStopRiding() {
|
|
return this.isShiftKeyDown();
|
|
}
|
|
|
|
protected boolean isStayingOnGroundSurface() {
|
|
return this.isShiftKeyDown();
|
|
}
|
|
|
|
protected boolean updateIsUnderwater() {
|
|
this.wasUnderwater = this.isEyeInFluid(FluidTags.WATER);
|
|
return this.wasUnderwater;
|
|
}
|
|
|
|
private void turtleHelmetTick() {
|
|
ItemStack itemStack = this.getItemBySlot(EquipmentSlot.HEAD);
|
|
if (itemStack.is(Items.TURTLE_HELMET) && !this.isEyeInFluid(FluidTags.WATER)) {
|
|
this.addEffect(new MobEffectInstance(MobEffects.WATER_BREATHING, 200, 0, false, false, true));
|
|
}
|
|
}
|
|
|
|
protected ItemCooldowns createItemCooldowns() {
|
|
return new ItemCooldowns();
|
|
}
|
|
|
|
private void moveCloak() {
|
|
this.xCloakO = this.xCloak;
|
|
this.yCloakO = this.yCloak;
|
|
this.zCloakO = this.zCloak;
|
|
double d = this.getX() - this.xCloak;
|
|
double e = this.getY() - this.yCloak;
|
|
double f = this.getZ() - this.zCloak;
|
|
double g = 10.0;
|
|
if (d > 10.0) {
|
|
this.xCloak = this.getX();
|
|
this.xCloakO = this.xCloak;
|
|
}
|
|
|
|
if (f > 10.0) {
|
|
this.zCloak = this.getZ();
|
|
this.zCloakO = this.zCloak;
|
|
}
|
|
|
|
if (e > 10.0) {
|
|
this.yCloak = this.getY();
|
|
this.yCloakO = this.yCloak;
|
|
}
|
|
|
|
if (d < -10.0) {
|
|
this.xCloak = this.getX();
|
|
this.xCloakO = this.xCloak;
|
|
}
|
|
|
|
if (f < -10.0) {
|
|
this.zCloak = this.getZ();
|
|
this.zCloakO = this.zCloak;
|
|
}
|
|
|
|
if (e < -10.0) {
|
|
this.yCloak = this.getY();
|
|
this.yCloakO = this.yCloak;
|
|
}
|
|
|
|
this.xCloak += d * 0.25;
|
|
this.zCloak += f * 0.25;
|
|
this.yCloak += e * 0.25;
|
|
}
|
|
|
|
protected void updatePlayerPose() {
|
|
if (this.canPlayerFitWithinBlocksAndEntitiesWhen(Pose.SWIMMING)) {
|
|
Pose pose;
|
|
if (this.isFallFlying()) {
|
|
pose = Pose.FALL_FLYING;
|
|
} else if (this.isSleeping()) {
|
|
pose = Pose.SLEEPING;
|
|
} else if (this.isSwimming()) {
|
|
pose = Pose.SWIMMING;
|
|
} else if (this.isAutoSpinAttack()) {
|
|
pose = Pose.SPIN_ATTACK;
|
|
} else if (this.isShiftKeyDown() && !this.abilities.flying) {
|
|
pose = Pose.CROUCHING;
|
|
} else {
|
|
pose = Pose.STANDING;
|
|
}
|
|
|
|
Pose pose2;
|
|
if (this.isSpectator() || this.isPassenger() || this.canPlayerFitWithinBlocksAndEntitiesWhen(pose)) {
|
|
pose2 = pose;
|
|
} else if (this.canPlayerFitWithinBlocksAndEntitiesWhen(Pose.CROUCHING)) {
|
|
pose2 = Pose.CROUCHING;
|
|
} else {
|
|
pose2 = Pose.SWIMMING;
|
|
}
|
|
|
|
this.setPose(pose2);
|
|
}
|
|
}
|
|
|
|
protected boolean canPlayerFitWithinBlocksAndEntitiesWhen(Pose pose) {
|
|
return this.level().noCollision(this, this.getDimensions(pose).makeBoundingBox(this.position()).deflate(1.0E-7));
|
|
}
|
|
|
|
@Override
|
|
protected SoundEvent getSwimSound() {
|
|
return SoundEvents.PLAYER_SWIM;
|
|
}
|
|
|
|
@Override
|
|
protected SoundEvent getSwimSplashSound() {
|
|
return SoundEvents.PLAYER_SPLASH;
|
|
}
|
|
|
|
@Override
|
|
protected SoundEvent getSwimHighSpeedSplashSound() {
|
|
return SoundEvents.PLAYER_SPLASH_HIGH_SPEED;
|
|
}
|
|
|
|
@Override
|
|
public int getDimensionChangingDelay() {
|
|
return 10;
|
|
}
|
|
|
|
@Override
|
|
public void playSound(SoundEvent sound, float volume, float pitch) {
|
|
this.level().playSound(this, this.getX(), this.getY(), this.getZ(), sound, this.getSoundSource(), volume, pitch);
|
|
}
|
|
|
|
public void playNotifySound(SoundEvent sound, SoundSource source, float volume, float pitch) {
|
|
}
|
|
|
|
@Override
|
|
public SoundSource getSoundSource() {
|
|
return SoundSource.PLAYERS;
|
|
}
|
|
|
|
@Override
|
|
protected int getFireImmuneTicks() {
|
|
return 20;
|
|
}
|
|
|
|
@Override
|
|
public void handleEntityEvent(byte id) {
|
|
if (id == 9) {
|
|
this.completeUsingItem();
|
|
} else if (id == 23) {
|
|
this.reducedDebugInfo = false;
|
|
} else if (id == 22) {
|
|
this.reducedDebugInfo = true;
|
|
} else {
|
|
super.handleEntityEvent(id);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the current crafting inventory back to the 2x2 square.
|
|
*/
|
|
protected void closeContainer() {
|
|
this.containerMenu = this.inventoryMenu;
|
|
}
|
|
|
|
protected void doCloseContainer() {
|
|
}
|
|
|
|
@Override
|
|
public void rideTick() {
|
|
if (!this.level().isClientSide && this.wantsToStopRiding() && this.isPassenger()) {
|
|
this.stopRiding();
|
|
this.setShiftKeyDown(false);
|
|
} else {
|
|
super.rideTick();
|
|
this.oBob = this.bob;
|
|
this.bob = 0.0F;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void serverAiStep() {
|
|
super.serverAiStep();
|
|
this.updateSwingTime();
|
|
this.yHeadRot = this.getYRot();
|
|
}
|
|
|
|
@Override
|
|
public void aiStep() {
|
|
if (this.jumpTriggerTime > 0) {
|
|
this.jumpTriggerTime--;
|
|
}
|
|
|
|
if (this.level().getDifficulty() == Difficulty.PEACEFUL && this.level().getGameRules().getBoolean(GameRules.RULE_NATURAL_REGENERATION)) {
|
|
if (this.getHealth() < this.getMaxHealth() && this.tickCount % 20 == 0) {
|
|
this.heal(1.0F);
|
|
}
|
|
|
|
if (this.foodData.getSaturationLevel() < 20.0F && this.tickCount % 20 == 0) {
|
|
this.foodData.setSaturation(this.foodData.getSaturationLevel() + 1.0F);
|
|
}
|
|
|
|
if (this.foodData.needsFood() && this.tickCount % 10 == 0) {
|
|
this.foodData.setFoodLevel(this.foodData.getFoodLevel() + 1);
|
|
}
|
|
}
|
|
|
|
this.inventory.tick();
|
|
this.oBob = this.bob;
|
|
super.aiStep();
|
|
this.setSpeed((float)this.getAttributeValue(Attributes.MOVEMENT_SPEED));
|
|
float f;
|
|
if (this.onGround() && !this.isDeadOrDying() && !this.isSwimming()) {
|
|
f = Math.min(0.1F, (float)this.getDeltaMovement().horizontalDistance());
|
|
} else {
|
|
f = 0.0F;
|
|
}
|
|
|
|
this.bob = this.bob + (f - this.bob) * 0.4F;
|
|
if (this.getHealth() > 0.0F && !this.isSpectator()) {
|
|
AABB aABB;
|
|
if (this.isPassenger() && !this.getVehicle().isRemoved()) {
|
|
aABB = this.getBoundingBox().minmax(this.getVehicle().getBoundingBox()).inflate(1.0, 0.0, 1.0);
|
|
} else {
|
|
aABB = this.getBoundingBox().inflate(1.0, 0.5, 1.0);
|
|
}
|
|
|
|
List<Entity> list = this.level().getEntities(this, aABB);
|
|
List<Entity> list2 = Lists.<Entity>newArrayList();
|
|
|
|
for (Entity entity : list) {
|
|
if (entity.getType() == EntityType.EXPERIENCE_ORB) {
|
|
list2.add(entity);
|
|
} else if (!entity.isRemoved()) {
|
|
this.touch(entity);
|
|
}
|
|
}
|
|
|
|
if (!list2.isEmpty()) {
|
|
this.touch(Util.getRandom(list2, this.random));
|
|
}
|
|
}
|
|
|
|
this.playShoulderEntityAmbientSound(this.getShoulderEntityLeft());
|
|
this.playShoulderEntityAmbientSound(this.getShoulderEntityRight());
|
|
if (!this.level().isClientSide && (this.fallDistance > 0.5F || this.isInWater()) || this.abilities.flying || this.isSleeping() || this.isInPowderSnow) {
|
|
this.removeEntitiesOnShoulder();
|
|
}
|
|
}
|
|
|
|
private void playShoulderEntityAmbientSound(@Nullable CompoundTag entityCompound) {
|
|
if (entityCompound != null && (!entityCompound.contains("Silent") || !entityCompound.getBoolean("Silent")) && this.level().random.nextInt(200) == 0) {
|
|
String string = entityCompound.getString("id");
|
|
EntityType.byString(string)
|
|
.filter(entityType -> entityType == EntityType.PARROT)
|
|
.ifPresent(
|
|
entityType -> {
|
|
if (!Parrot.imitateNearbyMobs(this.level(), this)) {
|
|
this.level()
|
|
.playSound(
|
|
null,
|
|
this.getX(),
|
|
this.getY(),
|
|
this.getZ(),
|
|
Parrot.getAmbient(this.level(), this.level().random),
|
|
this.getSoundSource(),
|
|
1.0F,
|
|
Parrot.getPitch(this.level().random)
|
|
);
|
|
}
|
|
}
|
|
);
|
|
}
|
|
}
|
|
|
|
private void touch(Entity entity) {
|
|
entity.playerTouch(this);
|
|
}
|
|
|
|
public int getScore() {
|
|
return this.entityData.get(DATA_SCORE_ID);
|
|
}
|
|
|
|
/**
|
|
* Set player's score
|
|
*/
|
|
public void setScore(int score) {
|
|
this.entityData.set(DATA_SCORE_ID, score);
|
|
}
|
|
|
|
/**
|
|
* Add to player's score
|
|
*/
|
|
public void increaseScore(int score) {
|
|
int i = this.getScore();
|
|
this.entityData.set(DATA_SCORE_ID, i + score);
|
|
}
|
|
|
|
/**
|
|
* Starts the attack used by the Riptide enchantment.
|
|
*/
|
|
public void startAutoSpinAttack(int ticks, float damage, ItemStack itemStack) {
|
|
this.autoSpinAttackTicks = ticks;
|
|
this.autoSpinAttackDmg = damage;
|
|
this.autoSpinAttackItemStack = itemStack;
|
|
if (!this.level().isClientSide) {
|
|
this.removeEntitiesOnShoulder();
|
|
this.setLivingEntityFlag(4, true);
|
|
}
|
|
}
|
|
|
|
@NotNull
|
|
@Override
|
|
public ItemStack getWeaponItem() {
|
|
return this.isAutoSpinAttack() && this.autoSpinAttackItemStack != null ? this.autoSpinAttackItemStack : super.getWeaponItem();
|
|
}
|
|
|
|
@Override
|
|
public void die(DamageSource damageSource) {
|
|
super.die(damageSource);
|
|
this.reapplyPosition();
|
|
if (!this.isSpectator() && this.level() instanceof ServerLevel serverLevel) {
|
|
this.dropAllDeathLoot(serverLevel, damageSource);
|
|
}
|
|
|
|
if (damageSource != null) {
|
|
this.setDeltaMovement(
|
|
-Mth.cos((this.getHurtDir() + this.getYRot()) * (float) (Math.PI / 180.0)) * 0.1F,
|
|
0.1F,
|
|
-Mth.sin((this.getHurtDir() + this.getYRot()) * (float) (Math.PI / 180.0)) * 0.1F
|
|
);
|
|
} else {
|
|
this.setDeltaMovement(0.0, 0.1, 0.0);
|
|
}
|
|
|
|
this.awardStat(Stats.DEATHS);
|
|
this.resetStat(Stats.CUSTOM.get(Stats.TIME_SINCE_DEATH));
|
|
this.resetStat(Stats.CUSTOM.get(Stats.TIME_SINCE_REST));
|
|
this.clearFire();
|
|
this.setSharedFlagOnFire(false);
|
|
this.setLastDeathLocation(Optional.of(GlobalPos.of(this.level().dimension(), this.blockPosition())));
|
|
}
|
|
|
|
@Override
|
|
protected void dropEquipment() {
|
|
super.dropEquipment();
|
|
if (!this.level().getGameRules().getBoolean(GameRules.RULE_KEEPINVENTORY)) {
|
|
this.destroyVanishingCursedItems();
|
|
this.inventory.dropAll();
|
|
}
|
|
}
|
|
|
|
protected void destroyVanishingCursedItems() {
|
|
for (int i = 0; i < this.inventory.getContainerSize(); i++) {
|
|
ItemStack itemStack = this.inventory.getItem(i);
|
|
if (!itemStack.isEmpty() && EnchantmentHelper.has(itemStack, EnchantmentEffectComponents.PREVENT_EQUIPMENT_DROP)) {
|
|
this.inventory.removeItemNoUpdate(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected SoundEvent getHurtSound(DamageSource damageSource) {
|
|
return damageSource.type().effects().sound();
|
|
}
|
|
|
|
@Override
|
|
protected SoundEvent getDeathSound() {
|
|
return SoundEvents.PLAYER_DEATH;
|
|
}
|
|
|
|
/**
|
|
* Drops an item into the world.
|
|
*/
|
|
@Nullable
|
|
public ItemEntity drop(ItemStack itemStack, boolean includeThrowerName) {
|
|
return this.drop(itemStack, false, includeThrowerName);
|
|
}
|
|
|
|
/**
|
|
* Creates and drops the provided item. Depending on the dropAround, it will drop the item around the player, instead of dropping the item from where the player is pointing at. Likewise, if includeThrowerName is true, the dropped item entity will have the thrower set as the player.
|
|
*/
|
|
@Nullable
|
|
public ItemEntity drop(ItemStack droppedItem, boolean dropAround, boolean includeThrowerName) {
|
|
if (droppedItem.isEmpty()) {
|
|
return null;
|
|
} else {
|
|
if (this.level().isClientSide) {
|
|
this.swing(InteractionHand.MAIN_HAND);
|
|
}
|
|
|
|
double d = this.getEyeY() - 0.3F;
|
|
ItemEntity itemEntity = new ItemEntity(this.level(), this.getX(), d, this.getZ(), droppedItem);
|
|
itemEntity.setPickUpDelay(40);
|
|
if (includeThrowerName) {
|
|
itemEntity.setThrower(this);
|
|
}
|
|
|
|
if (dropAround) {
|
|
float f = this.random.nextFloat() * 0.5F;
|
|
float g = this.random.nextFloat() * (float) (Math.PI * 2);
|
|
itemEntity.setDeltaMovement(-Mth.sin(g) * f, 0.2F, Mth.cos(g) * f);
|
|
} else {
|
|
float f = 0.3F;
|
|
float g = Mth.sin(this.getXRot() * (float) (Math.PI / 180.0));
|
|
float h = Mth.cos(this.getXRot() * (float) (Math.PI / 180.0));
|
|
float i = Mth.sin(this.getYRot() * (float) (Math.PI / 180.0));
|
|
float j = Mth.cos(this.getYRot() * (float) (Math.PI / 180.0));
|
|
float k = this.random.nextFloat() * (float) (Math.PI * 2);
|
|
float l = 0.02F * this.random.nextFloat();
|
|
itemEntity.setDeltaMovement(
|
|
-i * h * 0.3F + Math.cos(k) * l, -g * 0.3F + 0.1F + (this.random.nextFloat() - this.random.nextFloat()) * 0.1F, j * h * 0.3F + Math.sin(k) * l
|
|
);
|
|
}
|
|
|
|
return itemEntity;
|
|
}
|
|
}
|
|
|
|
public float getDestroySpeed(BlockState state) {
|
|
float f = this.inventory.getDestroySpeed(state);
|
|
if (f > 1.0F) {
|
|
f += (float)this.getAttributeValue(Attributes.MINING_EFFICIENCY);
|
|
}
|
|
|
|
if (MobEffectUtil.hasDigSpeed(this)) {
|
|
f *= 1.0F + (MobEffectUtil.getDigSpeedAmplification(this) + 1) * 0.2F;
|
|
}
|
|
|
|
if (this.hasEffect(MobEffects.DIG_SLOWDOWN)) {
|
|
f *= switch (this.getEffect(MobEffects.DIG_SLOWDOWN).getAmplifier()) {
|
|
case 0 -> 0.3F;
|
|
case 1 -> 0.09F;
|
|
case 2 -> 0.0027F;
|
|
default -> 8.1E-4F;
|
|
};
|
|
}
|
|
|
|
f *= (float)this.getAttributeValue(Attributes.BLOCK_BREAK_SPEED);
|
|
if (this.isEyeInFluid(FluidTags.WATER)) {
|
|
f *= (float)this.getAttribute(Attributes.SUBMERGED_MINING_SPEED).getValue();
|
|
}
|
|
|
|
if (!this.onGround()) {
|
|
f /= 5.0F;
|
|
}
|
|
|
|
return f;
|
|
}
|
|
|
|
public boolean hasCorrectToolForDrops(BlockState state) {
|
|
return !state.requiresCorrectToolForDrops() || this.inventory.getSelected().isCorrectToolForDrops(state);
|
|
}
|
|
|
|
@Override
|
|
public void readAdditionalSaveData(CompoundTag compound) {
|
|
super.readAdditionalSaveData(compound);
|
|
this.setUUID(this.gameProfile.getId());
|
|
ListTag listTag = compound.getList("Inventory", 10);
|
|
this.inventory.load(listTag);
|
|
this.inventory.selected = compound.getInt("SelectedItemSlot");
|
|
this.sleepCounter = compound.getShort("SleepTimer");
|
|
this.experienceProgress = compound.getFloat("XpP");
|
|
this.experienceLevel = compound.getInt("XpLevel");
|
|
this.totalExperience = compound.getInt("XpTotal");
|
|
this.enchantmentSeed = compound.getInt("XpSeed");
|
|
if (this.enchantmentSeed == 0) {
|
|
this.enchantmentSeed = this.random.nextInt();
|
|
}
|
|
|
|
this.setScore(compound.getInt("Score"));
|
|
this.foodData.readAdditionalSaveData(compound);
|
|
this.abilities.loadSaveData(compound);
|
|
this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(this.abilities.getWalkingSpeed());
|
|
if (compound.contains("EnderItems", 9)) {
|
|
this.enderChestInventory.fromTag(compound.getList("EnderItems", 10), this.registryAccess());
|
|
}
|
|
|
|
if (compound.contains("ShoulderEntityLeft", 10)) {
|
|
this.setShoulderEntityLeft(compound.getCompound("ShoulderEntityLeft"));
|
|
}
|
|
|
|
if (compound.contains("ShoulderEntityRight", 10)) {
|
|
this.setShoulderEntityRight(compound.getCompound("ShoulderEntityRight"));
|
|
}
|
|
|
|
if (compound.contains("LastDeathLocation", 10)) {
|
|
this.setLastDeathLocation(GlobalPos.CODEC.parse(NbtOps.INSTANCE, compound.get("LastDeathLocation")).resultOrPartial(LOGGER::error));
|
|
}
|
|
|
|
if (compound.contains("current_explosion_impact_pos", 9)) {
|
|
Vec3.CODEC
|
|
.parse(NbtOps.INSTANCE, compound.get("current_explosion_impact_pos"))
|
|
.resultOrPartial(LOGGER::error)
|
|
.ifPresent(vec3 -> this.currentImpulseImpactPos = vec3);
|
|
}
|
|
|
|
this.ignoreFallDamageFromCurrentImpulse = compound.getBoolean("ignore_fall_damage_from_current_explosion");
|
|
this.currentImpulseContextResetGraceTime = compound.getInt("current_impulse_context_reset_grace_time");
|
|
}
|
|
|
|
@Override
|
|
public void addAdditionalSaveData(CompoundTag compound) {
|
|
super.addAdditionalSaveData(compound);
|
|
NbtUtils.addCurrentDataVersion(compound);
|
|
compound.put("Inventory", this.inventory.save(new ListTag()));
|
|
compound.putInt("SelectedItemSlot", this.inventory.selected);
|
|
compound.putShort("SleepTimer", (short)this.sleepCounter);
|
|
compound.putFloat("XpP", this.experienceProgress);
|
|
compound.putInt("XpLevel", this.experienceLevel);
|
|
compound.putInt("XpTotal", this.totalExperience);
|
|
compound.putInt("XpSeed", this.enchantmentSeed);
|
|
compound.putInt("Score", this.getScore());
|
|
this.foodData.addAdditionalSaveData(compound);
|
|
this.abilities.addSaveData(compound);
|
|
compound.put("EnderItems", this.enderChestInventory.createTag(this.registryAccess()));
|
|
if (!this.getShoulderEntityLeft().isEmpty()) {
|
|
compound.put("ShoulderEntityLeft", this.getShoulderEntityLeft());
|
|
}
|
|
|
|
if (!this.getShoulderEntityRight().isEmpty()) {
|
|
compound.put("ShoulderEntityRight", this.getShoulderEntityRight());
|
|
}
|
|
|
|
this.getLastDeathLocation()
|
|
.flatMap(globalPos -> GlobalPos.CODEC.encodeStart(NbtOps.INSTANCE, globalPos).resultOrPartial(LOGGER::error))
|
|
.ifPresent(tag -> compound.put("LastDeathLocation", tag));
|
|
if (this.currentImpulseImpactPos != null) {
|
|
compound.put("current_explosion_impact_pos", Vec3.CODEC.encodeStart(NbtOps.INSTANCE, this.currentImpulseImpactPos).getOrThrow());
|
|
}
|
|
|
|
compound.putBoolean("ignore_fall_damage_from_current_explosion", this.ignoreFallDamageFromCurrentImpulse);
|
|
compound.putInt("current_impulse_context_reset_grace_time", this.currentImpulseContextResetGraceTime);
|
|
}
|
|
|
|
@Override
|
|
public boolean isInvulnerableTo(DamageSource source) {
|
|
if (super.isInvulnerableTo(source)) {
|
|
return true;
|
|
} else if (source.is(DamageTypeTags.IS_DROWNING)) {
|
|
return !this.level().getGameRules().getBoolean(GameRules.RULE_DROWNING_DAMAGE);
|
|
} else if (source.is(DamageTypeTags.IS_FALL)) {
|
|
return !this.level().getGameRules().getBoolean(GameRules.RULE_FALL_DAMAGE);
|
|
} else if (source.is(DamageTypeTags.IS_FIRE)) {
|
|
return !this.level().getGameRules().getBoolean(GameRules.RULE_FIRE_DAMAGE);
|
|
} else {
|
|
return source.is(DamageTypeTags.IS_FREEZING) ? !this.level().getGameRules().getBoolean(GameRules.RULE_FREEZE_DAMAGE) : false;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean hurt(DamageSource source, float amount) {
|
|
if (this.isInvulnerableTo(source)) {
|
|
return false;
|
|
} else if (this.abilities.invulnerable && !source.is(DamageTypeTags.BYPASSES_INVULNERABILITY)) {
|
|
return false;
|
|
} else {
|
|
this.noActionTime = 0;
|
|
if (this.isDeadOrDying()) {
|
|
return false;
|
|
} else {
|
|
if (!this.level().isClientSide) {
|
|
this.removeEntitiesOnShoulder();
|
|
}
|
|
|
|
if (source.scalesWithDifficulty()) {
|
|
if (this.level().getDifficulty() == Difficulty.PEACEFUL) {
|
|
amount = 0.0F;
|
|
}
|
|
|
|
if (this.level().getDifficulty() == Difficulty.EASY) {
|
|
amount = Math.min(amount / 2.0F + 1.0F, amount);
|
|
}
|
|
|
|
if (this.level().getDifficulty() == Difficulty.HARD) {
|
|
amount = amount * 3.0F / 2.0F;
|
|
}
|
|
}
|
|
|
|
return amount == 0.0F ? false : super.hurt(source, amount);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void blockUsingShield(LivingEntity attacker) {
|
|
super.blockUsingShield(attacker);
|
|
if (attacker.canDisableShield()) {
|
|
this.disableShield();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean canBeSeenAsEnemy() {
|
|
return !this.getAbilities().invulnerable && super.canBeSeenAsEnemy();
|
|
}
|
|
|
|
public boolean canHarmPlayer(Player other) {
|
|
Team team = this.getTeam();
|
|
Team team2 = other.getTeam();
|
|
if (team == null) {
|
|
return true;
|
|
} else {
|
|
return !team.isAlliedTo(team2) ? true : team.isAllowFriendlyFire();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void hurtArmor(DamageSource damageSource, float damageAmount) {
|
|
this.doHurtEquipment(damageSource, damageAmount, new EquipmentSlot[]{EquipmentSlot.FEET, EquipmentSlot.LEGS, EquipmentSlot.CHEST, EquipmentSlot.HEAD});
|
|
}
|
|
|
|
@Override
|
|
protected void hurtHelmet(DamageSource damageSource, float damageAmount) {
|
|
this.doHurtEquipment(damageSource, damageAmount, new EquipmentSlot[]{EquipmentSlot.HEAD});
|
|
}
|
|
|
|
@Override
|
|
protected void hurtCurrentlyUsedShield(float damageAmount) {
|
|
if (this.useItem.is(Items.SHIELD)) {
|
|
if (!this.level().isClientSide) {
|
|
this.awardStat(Stats.ITEM_USED.get(this.useItem.getItem()));
|
|
}
|
|
|
|
if (damageAmount >= 3.0F) {
|
|
int i = 1 + Mth.floor(damageAmount);
|
|
InteractionHand interactionHand = this.getUsedItemHand();
|
|
this.useItem.hurtAndBreak(i, this, getSlotForHand(interactionHand));
|
|
if (this.useItem.isEmpty()) {
|
|
if (interactionHand == InteractionHand.MAIN_HAND) {
|
|
this.setItemSlot(EquipmentSlot.MAINHAND, ItemStack.EMPTY);
|
|
} else {
|
|
this.setItemSlot(EquipmentSlot.OFFHAND, ItemStack.EMPTY);
|
|
}
|
|
|
|
this.useItem = ItemStack.EMPTY;
|
|
this.playSound(SoundEvents.SHIELD_BREAK, 0.8F, 0.8F + this.level().random.nextFloat() * 0.4F);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void actuallyHurt(DamageSource damageSource, float damageAmount) {
|
|
if (!this.isInvulnerableTo(damageSource)) {
|
|
damageAmount = this.getDamageAfterArmorAbsorb(damageSource, damageAmount);
|
|
damageAmount = this.getDamageAfterMagicAbsorb(damageSource, damageAmount);
|
|
float var7 = Math.max(damageAmount - this.getAbsorptionAmount(), 0.0F);
|
|
this.setAbsorptionAmount(this.getAbsorptionAmount() - (damageAmount - var7));
|
|
float g = damageAmount - var7;
|
|
if (g > 0.0F && g < 3.4028235E37F) {
|
|
this.awardStat(Stats.DAMAGE_ABSORBED, Math.round(g * 10.0F));
|
|
}
|
|
|
|
if (var7 != 0.0F) {
|
|
this.causeFoodExhaustion(damageSource.getFoodExhaustion());
|
|
this.getCombatTracker().recordDamage(damageSource, var7);
|
|
this.setHealth(this.getHealth() - var7);
|
|
if (var7 < 3.4028235E37F) {
|
|
this.awardStat(Stats.DAMAGE_TAKEN, Math.round(var7 * 10.0F));
|
|
}
|
|
|
|
this.gameEvent(GameEvent.ENTITY_DAMAGE);
|
|
}
|
|
}
|
|
}
|
|
|
|
public boolean isTextFilteringEnabled() {
|
|
return false;
|
|
}
|
|
|
|
public void openTextEdit(SignBlockEntity signEntity, boolean isFrontText) {
|
|
}
|
|
|
|
public void openMinecartCommandBlock(BaseCommandBlock commandEntity) {
|
|
}
|
|
|
|
public void openCommandBlock(CommandBlockEntity commandBlockEntity) {
|
|
}
|
|
|
|
public void openStructureBlock(StructureBlockEntity structureEntity) {
|
|
}
|
|
|
|
public void openJigsawBlock(JigsawBlockEntity jigsawBlockEntity) {
|
|
}
|
|
|
|
public void openHorseInventory(AbstractHorse horse, Container inventory) {
|
|
}
|
|
|
|
public OptionalInt openMenu(@Nullable MenuProvider menu) {
|
|
return OptionalInt.empty();
|
|
}
|
|
|
|
public void sendMerchantOffers(int containerId, MerchantOffers offers, int villagerLevel, int villagerXp, boolean showProgress, boolean canRestock) {
|
|
}
|
|
|
|
public void openItemGui(ItemStack stack, InteractionHand hand) {
|
|
}
|
|
|
|
public InteractionResult interactOn(Entity entityToInteractOn, InteractionHand hand) {
|
|
if (this.isSpectator()) {
|
|
if (entityToInteractOn instanceof MenuProvider) {
|
|
this.openMenu((MenuProvider)entityToInteractOn);
|
|
}
|
|
|
|
return InteractionResult.PASS;
|
|
} else {
|
|
ItemStack itemStack = this.getItemInHand(hand);
|
|
ItemStack itemStack2 = itemStack.copy();
|
|
InteractionResult interactionResult = entityToInteractOn.interact(this, hand);
|
|
if (interactionResult.consumesAction()) {
|
|
if (this.abilities.instabuild && itemStack == this.getItemInHand(hand) && itemStack.getCount() < itemStack2.getCount()) {
|
|
itemStack.setCount(itemStack2.getCount());
|
|
}
|
|
|
|
return interactionResult;
|
|
} else {
|
|
if (!itemStack.isEmpty() && entityToInteractOn instanceof LivingEntity) {
|
|
if (this.abilities.instabuild) {
|
|
itemStack = itemStack2;
|
|
}
|
|
|
|
InteractionResult interactionResult2 = itemStack.interactLivingEntity(this, (LivingEntity)entityToInteractOn, hand);
|
|
if (interactionResult2.consumesAction()) {
|
|
this.level().gameEvent(GameEvent.ENTITY_INTERACT, entityToInteractOn.position(), GameEvent.Context.of(this));
|
|
if (itemStack.isEmpty() && !this.abilities.instabuild) {
|
|
this.setItemInHand(hand, ItemStack.EMPTY);
|
|
}
|
|
|
|
return interactionResult2;
|
|
}
|
|
}
|
|
|
|
return InteractionResult.PASS;
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void removeVehicle() {
|
|
super.removeVehicle();
|
|
this.boardingCooldown = 0;
|
|
}
|
|
|
|
@Override
|
|
protected boolean isImmobile() {
|
|
return super.isImmobile() || this.isSleeping();
|
|
}
|
|
|
|
@Override
|
|
public boolean isAffectedByFluids() {
|
|
return !this.abilities.flying;
|
|
}
|
|
|
|
@Override
|
|
protected Vec3 maybeBackOffFromEdge(Vec3 vec, MoverType mover) {
|
|
float f = this.maxUpStep();
|
|
if (!this.abilities.flying
|
|
&& !(vec.y > 0.0)
|
|
&& (mover == MoverType.SELF || mover == MoverType.PLAYER)
|
|
&& this.isStayingOnGroundSurface()
|
|
&& this.isAboveGround(f)) {
|
|
double d = vec.x;
|
|
double e = vec.z;
|
|
double g = 0.05;
|
|
double h = Math.signum(d) * 0.05;
|
|
|
|
double i;
|
|
for (i = Math.signum(e) * 0.05; d != 0.0 && this.canFallAtLeast(d, 0.0, f); d -= h) {
|
|
if (Math.abs(d) <= 0.05) {
|
|
d = 0.0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
while (e != 0.0 && this.canFallAtLeast(0.0, e, f)) {
|
|
if (Math.abs(e) <= 0.05) {
|
|
e = 0.0;
|
|
break;
|
|
}
|
|
|
|
e -= i;
|
|
}
|
|
|
|
while (d != 0.0 && e != 0.0 && this.canFallAtLeast(d, e, f)) {
|
|
if (Math.abs(d) <= 0.05) {
|
|
d = 0.0;
|
|
} else {
|
|
d -= h;
|
|
}
|
|
|
|
if (Math.abs(e) <= 0.05) {
|
|
e = 0.0;
|
|
} else {
|
|
e -= i;
|
|
}
|
|
}
|
|
|
|
return new Vec3(d, vec.y, e);
|
|
} else {
|
|
return vec;
|
|
}
|
|
}
|
|
|
|
private boolean isAboveGround(float maxUpStep) {
|
|
return this.onGround() || this.fallDistance < maxUpStep && !this.canFallAtLeast(0.0, 0.0, maxUpStep - this.fallDistance);
|
|
}
|
|
|
|
private boolean canFallAtLeast(double x, double z, float distance) {
|
|
AABB aABB = this.getBoundingBox();
|
|
return this.level().noCollision(this, new AABB(aABB.minX + x, aABB.minY - distance - 1.0E-5F, aABB.minZ + z, aABB.maxX + x, aABB.minY, aABB.maxZ + z));
|
|
}
|
|
|
|
/**
|
|
* Attacks for the player the targeted entity with the currently equipped item. The equipped item has hitEntity called on it. Args: targetEntity
|
|
*/
|
|
public void attack(Entity target) {
|
|
if (target.isAttackable()) {
|
|
if (!target.skipAttackInteraction(this)) {
|
|
float f = this.isAutoSpinAttack() ? this.autoSpinAttackDmg : (float)this.getAttributeValue(Attributes.ATTACK_DAMAGE);
|
|
ItemStack itemStack = this.getWeaponItem();
|
|
DamageSource damageSource = this.damageSources().playerAttack(this);
|
|
float g = this.getEnchantedDamage(target, f, damageSource) - f;
|
|
float h = this.getAttackStrengthScale(0.5F);
|
|
f *= 0.2F + h * h * 0.8F;
|
|
g *= h;
|
|
this.resetAttackStrengthTicker();
|
|
if (target.getType().is(EntityTypeTags.REDIRECTABLE_PROJECTILE)
|
|
&& target instanceof Projectile projectile
|
|
&& projectile.deflect(ProjectileDeflection.AIM_DEFLECT, this, this, true)) {
|
|
this.level().playSound(null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_NODAMAGE, this.getSoundSource());
|
|
} else {
|
|
if (f > 0.0F || g > 0.0F) {
|
|
boolean bl = h > 0.9F;
|
|
boolean bl2;
|
|
if (this.isSprinting() && bl) {
|
|
this.level().playSound(null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_KNOCKBACK, this.getSoundSource(), 1.0F, 1.0F);
|
|
bl2 = true;
|
|
} else {
|
|
bl2 = false;
|
|
}
|
|
|
|
f += itemStack.getItem().getAttackDamageBonus(target, f, damageSource);
|
|
boolean bl3 = bl
|
|
&& this.fallDistance > 0.0F
|
|
&& !this.onGround()
|
|
&& !this.onClimbable()
|
|
&& !this.isInWater()
|
|
&& !this.hasEffect(MobEffects.BLINDNESS)
|
|
&& !this.isPassenger()
|
|
&& target instanceof LivingEntity
|
|
&& !this.isSprinting();
|
|
if (bl3) {
|
|
f *= 1.5F;
|
|
}
|
|
|
|
float i = f + g;
|
|
boolean bl4 = false;
|
|
double d = this.walkDist - this.walkDistO;
|
|
if (bl && !bl3 && !bl2 && this.onGround() && d < this.getSpeed()) {
|
|
ItemStack itemStack2 = this.getItemInHand(InteractionHand.MAIN_HAND);
|
|
if (itemStack2.getItem() instanceof SwordItem) {
|
|
bl4 = true;
|
|
}
|
|
}
|
|
|
|
float j = 0.0F;
|
|
if (target instanceof LivingEntity livingEntity) {
|
|
j = livingEntity.getHealth();
|
|
}
|
|
|
|
Vec3 vec3 = target.getDeltaMovement();
|
|
boolean bl5 = target.hurt(damageSource, i);
|
|
if (bl5) {
|
|
float k = this.getKnockback(target, damageSource) + (bl2 ? 1.0F : 0.0F);
|
|
if (k > 0.0F) {
|
|
if (target instanceof LivingEntity livingEntity2) {
|
|
livingEntity2.knockback(k * 0.5F, Mth.sin(this.getYRot() * (float) (Math.PI / 180.0)), -Mth.cos(this.getYRot() * (float) (Math.PI / 180.0)));
|
|
} else {
|
|
target.push(-Mth.sin(this.getYRot() * (float) (Math.PI / 180.0)) * k * 0.5F, 0.1, Mth.cos(this.getYRot() * (float) (Math.PI / 180.0)) * k * 0.5F);
|
|
}
|
|
|
|
this.setDeltaMovement(this.getDeltaMovement().multiply(0.6, 1.0, 0.6));
|
|
this.setSprinting(false);
|
|
}
|
|
|
|
if (bl4) {
|
|
float l = 1.0F + (float)this.getAttributeValue(Attributes.SWEEPING_DAMAGE_RATIO) * f;
|
|
|
|
for (LivingEntity livingEntity3 : this.level().getEntitiesOfClass(LivingEntity.class, target.getBoundingBox().inflate(1.0, 0.25, 1.0))) {
|
|
if (livingEntity3 != this
|
|
&& livingEntity3 != target
|
|
&& !this.isAlliedTo(livingEntity3)
|
|
&& (!(livingEntity3 instanceof ArmorStand) || !((ArmorStand)livingEntity3).isMarker())
|
|
&& this.distanceToSqr(livingEntity3) < 9.0) {
|
|
float m = this.getEnchantedDamage(livingEntity3, l, damageSource) * h;
|
|
livingEntity3.knockback(0.4F, Mth.sin(this.getYRot() * (float) (Math.PI / 180.0)), -Mth.cos(this.getYRot() * (float) (Math.PI / 180.0)));
|
|
livingEntity3.hurt(damageSource, m);
|
|
if (this.level() instanceof ServerLevel serverLevel) {
|
|
EnchantmentHelper.doPostAttackEffects(serverLevel, livingEntity3, damageSource);
|
|
}
|
|
}
|
|
}
|
|
|
|
this.level().playSound(null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_SWEEP, this.getSoundSource(), 1.0F, 1.0F);
|
|
this.sweepAttack();
|
|
}
|
|
|
|
if (target instanceof ServerPlayer && target.hurtMarked) {
|
|
((ServerPlayer)target).connection.send(new ClientboundSetEntityMotionPacket(target));
|
|
target.hurtMarked = false;
|
|
target.setDeltaMovement(vec3);
|
|
}
|
|
|
|
if (bl3) {
|
|
this.level().playSound(null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_CRIT, this.getSoundSource(), 1.0F, 1.0F);
|
|
this.crit(target);
|
|
}
|
|
|
|
if (!bl3 && !bl4) {
|
|
if (bl) {
|
|
this.level().playSound(null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_STRONG, this.getSoundSource(), 1.0F, 1.0F);
|
|
} else {
|
|
this.level().playSound(null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_WEAK, this.getSoundSource(), 1.0F, 1.0F);
|
|
}
|
|
}
|
|
|
|
if (g > 0.0F) {
|
|
this.magicCrit(target);
|
|
}
|
|
|
|
this.setLastHurtMob(target);
|
|
Entity entity = target;
|
|
if (target instanceof EnderDragonPart) {
|
|
entity = ((EnderDragonPart)target).parentMob;
|
|
}
|
|
|
|
boolean bl6 = false;
|
|
if (this.level() instanceof ServerLevel serverLevel2) {
|
|
if (entity instanceof LivingEntity livingEntity3x) {
|
|
bl6 = itemStack.hurtEnemy(livingEntity3x, this);
|
|
}
|
|
|
|
EnchantmentHelper.doPostAttackEffects(serverLevel2, target, damageSource);
|
|
}
|
|
|
|
if (!this.level().isClientSide && !itemStack.isEmpty() && entity instanceof LivingEntity) {
|
|
if (bl6) {
|
|
itemStack.postHurtEnemy((LivingEntity)entity, this);
|
|
}
|
|
|
|
if (itemStack.isEmpty()) {
|
|
if (itemStack == this.getMainHandItem()) {
|
|
this.setItemInHand(InteractionHand.MAIN_HAND, ItemStack.EMPTY);
|
|
} else {
|
|
this.setItemInHand(InteractionHand.OFF_HAND, ItemStack.EMPTY);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (target instanceof LivingEntity) {
|
|
float n = j - ((LivingEntity)target).getHealth();
|
|
this.awardStat(Stats.DAMAGE_DEALT, Math.round(n * 10.0F));
|
|
if (this.level() instanceof ServerLevel && n > 2.0F) {
|
|
int o = (int)(n * 0.5);
|
|
((ServerLevel)this.level()).sendParticles(ParticleTypes.DAMAGE_INDICATOR, target.getX(), target.getY(0.5), target.getZ(), o, 0.1, 0.0, 0.1, 0.2);
|
|
}
|
|
}
|
|
|
|
this.causeFoodExhaustion(0.1F);
|
|
} else {
|
|
this.level().playSound(null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_NODAMAGE, this.getSoundSource(), 1.0F, 1.0F);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
protected float getEnchantedDamage(Entity entity, float damage, DamageSource damageSource) {
|
|
return damage;
|
|
}
|
|
|
|
@Override
|
|
protected void doAutoAttackOnTouch(LivingEntity target) {
|
|
this.attack(target);
|
|
}
|
|
|
|
public void disableShield() {
|
|
this.getCooldowns().addCooldown(Items.SHIELD, 100);
|
|
this.stopUsingItem();
|
|
this.level().broadcastEntityEvent(this, (byte)30);
|
|
}
|
|
|
|
/**
|
|
* Called when the entity is dealt a critical hit.
|
|
*/
|
|
public void crit(Entity entityHit) {
|
|
}
|
|
|
|
/**
|
|
* Called when the entity hit is dealt extra melee damage due to an enchantment.
|
|
*/
|
|
public void magicCrit(Entity entityHit) {
|
|
}
|
|
|
|
public void sweepAttack() {
|
|
double d = -Mth.sin(this.getYRot() * (float) (Math.PI / 180.0));
|
|
double e = Mth.cos(this.getYRot() * (float) (Math.PI / 180.0));
|
|
if (this.level() instanceof ServerLevel) {
|
|
((ServerLevel)this.level()).sendParticles(ParticleTypes.SWEEP_ATTACK, this.getX() + d, this.getY(0.5), this.getZ() + e, 0, d, 0.0, e, 0.0);
|
|
}
|
|
}
|
|
|
|
public void respawn() {
|
|
}
|
|
|
|
@Override
|
|
public void remove(Entity.RemovalReason reason) {
|
|
super.remove(reason);
|
|
this.inventoryMenu.removed(this);
|
|
if (this.containerMenu != null && this.hasContainerOpen()) {
|
|
this.doCloseContainer();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns whether this is a {@link net.minecraft.client.player.LocalPlayer}.
|
|
*/
|
|
public boolean isLocalPlayer() {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Returns the GameProfile for this player
|
|
*/
|
|
public GameProfile getGameProfile() {
|
|
return this.gameProfile;
|
|
}
|
|
|
|
public Inventory getInventory() {
|
|
return this.inventory;
|
|
}
|
|
|
|
public Abilities getAbilities() {
|
|
return this.abilities;
|
|
}
|
|
|
|
@Override
|
|
public boolean hasInfiniteMaterials() {
|
|
return this.abilities.instabuild;
|
|
}
|
|
|
|
public void updateTutorialInventoryAction(ItemStack carried, ItemStack clicked, ClickAction action) {
|
|
}
|
|
|
|
public boolean hasContainerOpen() {
|
|
return this.containerMenu != this.inventoryMenu;
|
|
}
|
|
|
|
public Either<Player.BedSleepingProblem, Unit> startSleepInBed(BlockPos bedPos) {
|
|
this.startSleeping(bedPos);
|
|
this.sleepCounter = 0;
|
|
return Either.right(Unit.INSTANCE);
|
|
}
|
|
|
|
public void stopSleepInBed(boolean wakeImmediately, boolean updateLevelForSleepingPlayers) {
|
|
super.stopSleeping();
|
|
if (this.level() instanceof ServerLevel && updateLevelForSleepingPlayers) {
|
|
((ServerLevel)this.level()).updateSleepingPlayerList();
|
|
}
|
|
|
|
this.sleepCounter = wakeImmediately ? 0 : 100;
|
|
}
|
|
|
|
@Override
|
|
public void stopSleeping() {
|
|
this.stopSleepInBed(true, true);
|
|
}
|
|
|
|
/**
|
|
* Returns whether the player is asleep and the screen has fully faded.
|
|
*/
|
|
public boolean isSleepingLongEnough() {
|
|
return this.isSleeping() && this.sleepCounter >= 100;
|
|
}
|
|
|
|
public int getSleepTimer() {
|
|
return this.sleepCounter;
|
|
}
|
|
|
|
public void displayClientMessage(Component chatComponent, boolean actionBar) {
|
|
}
|
|
|
|
public void awardStat(ResourceLocation statKey) {
|
|
this.awardStat(Stats.CUSTOM.get(statKey));
|
|
}
|
|
|
|
public void awardStat(ResourceLocation stat, int increment) {
|
|
this.awardStat(Stats.CUSTOM.get(stat), increment);
|
|
}
|
|
|
|
/**
|
|
* Add a stat once
|
|
*/
|
|
public void awardStat(Stat<?> stat) {
|
|
this.awardStat(stat, 1);
|
|
}
|
|
|
|
/**
|
|
* Adds a value to a statistic field.
|
|
*/
|
|
public void awardStat(Stat<?> stat, int increment) {
|
|
}
|
|
|
|
public void resetStat(Stat<?> stat) {
|
|
}
|
|
|
|
public int awardRecipes(Collection<RecipeHolder<?>> recipes) {
|
|
return 0;
|
|
}
|
|
|
|
public void triggerRecipeCrafted(RecipeHolder<?> recipe, List<ItemStack> items) {
|
|
}
|
|
|
|
public void awardRecipesByKey(List<ResourceLocation> recipes) {
|
|
}
|
|
|
|
public int resetRecipes(Collection<RecipeHolder<?>> recipes) {
|
|
return 0;
|
|
}
|
|
|
|
@Override
|
|
public void jumpFromGround() {
|
|
super.jumpFromGround();
|
|
this.awardStat(Stats.JUMP);
|
|
if (this.isSprinting()) {
|
|
this.causeFoodExhaustion(0.2F);
|
|
} else {
|
|
this.causeFoodExhaustion(0.05F);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void travel(Vec3 travelVector) {
|
|
if (this.isSwimming() && !this.isPassenger()) {
|
|
double d = this.getLookAngle().y;
|
|
double e = d < -0.2 ? 0.085 : 0.06;
|
|
if (d <= 0.0
|
|
|| this.jumping
|
|
|| !this.level().getBlockState(BlockPos.containing(this.getX(), this.getY() + 1.0 - 0.1, this.getZ())).getFluidState().isEmpty()) {
|
|
Vec3 vec3 = this.getDeltaMovement();
|
|
this.setDeltaMovement(vec3.add(0.0, (d - vec3.y) * e, 0.0));
|
|
}
|
|
}
|
|
|
|
if (this.abilities.flying && !this.isPassenger()) {
|
|
double d = this.getDeltaMovement().y;
|
|
super.travel(travelVector);
|
|
Vec3 vec32 = this.getDeltaMovement();
|
|
this.setDeltaMovement(vec32.x, d * 0.6, vec32.z);
|
|
this.resetFallDistance();
|
|
this.setSharedFlag(7, false);
|
|
} else {
|
|
super.travel(travelVector);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void updateSwimming() {
|
|
if (this.abilities.flying) {
|
|
this.setSwimming(false);
|
|
} else {
|
|
super.updateSwimming();
|
|
}
|
|
}
|
|
|
|
protected boolean freeAt(BlockPos pos) {
|
|
return !this.level().getBlockState(pos).isSuffocating(this.level(), pos);
|
|
}
|
|
|
|
@Override
|
|
public float getSpeed() {
|
|
return (float)this.getAttributeValue(Attributes.MOVEMENT_SPEED);
|
|
}
|
|
|
|
@Override
|
|
public boolean causeFallDamage(float fallDistance, float multiplier, DamageSource source) {
|
|
if (this.abilities.mayfly) {
|
|
return false;
|
|
} else {
|
|
if (fallDistance >= 2.0F) {
|
|
this.awardStat(Stats.FALL_ONE_CM, (int)Math.round(fallDistance * 100.0));
|
|
}
|
|
|
|
boolean bl;
|
|
if (this.ignoreFallDamageFromCurrentImpulse && this.currentImpulseImpactPos != null) {
|
|
double d = this.currentImpulseImpactPos.y;
|
|
this.tryResetCurrentImpulseContext();
|
|
if (d < this.getY()) {
|
|
return false;
|
|
}
|
|
|
|
float f = Math.min(fallDistance, (float)(d - this.getY()));
|
|
bl = super.causeFallDamage(f, multiplier, source);
|
|
} else {
|
|
bl = super.causeFallDamage(fallDistance, multiplier, source);
|
|
}
|
|
|
|
if (bl) {
|
|
this.resetCurrentImpulseContext();
|
|
}
|
|
|
|
return bl;
|
|
}
|
|
}
|
|
|
|
public boolean tryToStartFallFlying() {
|
|
if (!this.onGround() && !this.isFallFlying() && !this.isInWater() && !this.hasEffect(MobEffects.LEVITATION)) {
|
|
ItemStack itemStack = this.getItemBySlot(EquipmentSlot.CHEST);
|
|
if (itemStack.is(Items.ELYTRA) && ElytraItem.isFlyEnabled(itemStack)) {
|
|
this.startFallFlying();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public void startFallFlying() {
|
|
this.setSharedFlag(7, true);
|
|
}
|
|
|
|
public void stopFallFlying() {
|
|
this.setSharedFlag(7, true);
|
|
this.setSharedFlag(7, false);
|
|
}
|
|
|
|
@Override
|
|
protected void doWaterSplashEffect() {
|
|
if (!this.isSpectator()) {
|
|
super.doWaterSplashEffect();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void playStepSound(BlockPos pos, BlockState state) {
|
|
if (this.isInWater()) {
|
|
this.waterSwimSound();
|
|
this.playMuffledStepSound(state);
|
|
} else {
|
|
BlockPos blockPos = this.getPrimaryStepSoundBlockPos(pos);
|
|
if (!pos.equals(blockPos)) {
|
|
BlockState blockState = this.level().getBlockState(blockPos);
|
|
if (blockState.is(BlockTags.COMBINATION_STEP_SOUND_BLOCKS)) {
|
|
this.playCombinationStepSounds(blockState, state);
|
|
} else {
|
|
super.playStepSound(blockPos, blockState);
|
|
}
|
|
} else {
|
|
super.playStepSound(pos, state);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public LivingEntity.Fallsounds getFallSounds() {
|
|
return new LivingEntity.Fallsounds(SoundEvents.PLAYER_SMALL_FALL, SoundEvents.PLAYER_BIG_FALL);
|
|
}
|
|
|
|
@Override
|
|
public boolean killedEntity(ServerLevel level, LivingEntity entity) {
|
|
this.awardStat(Stats.ENTITY_KILLED.get(entity.getType()));
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public void makeStuckInBlock(BlockState state, Vec3 motionMultiplier) {
|
|
if (!this.abilities.flying) {
|
|
super.makeStuckInBlock(state, motionMultiplier);
|
|
}
|
|
|
|
this.tryResetCurrentImpulseContext();
|
|
}
|
|
|
|
public void giveExperiencePoints(int xpPoints) {
|
|
this.increaseScore(xpPoints);
|
|
this.experienceProgress = this.experienceProgress + (float)xpPoints / this.getXpNeededForNextLevel();
|
|
this.totalExperience = Mth.clamp(this.totalExperience + xpPoints, 0, Integer.MAX_VALUE);
|
|
|
|
while (this.experienceProgress < 0.0F) {
|
|
float f = this.experienceProgress * this.getXpNeededForNextLevel();
|
|
if (this.experienceLevel > 0) {
|
|
this.giveExperienceLevels(-1);
|
|
this.experienceProgress = 1.0F + f / this.getXpNeededForNextLevel();
|
|
} else {
|
|
this.giveExperienceLevels(-1);
|
|
this.experienceProgress = 0.0F;
|
|
}
|
|
}
|
|
|
|
while (this.experienceProgress >= 1.0F) {
|
|
this.experienceProgress = (this.experienceProgress - 1.0F) * this.getXpNeededForNextLevel();
|
|
this.giveExperienceLevels(1);
|
|
this.experienceProgress = this.experienceProgress / this.getXpNeededForNextLevel();
|
|
}
|
|
}
|
|
|
|
public int getEnchantmentSeed() {
|
|
return this.enchantmentSeed;
|
|
}
|
|
|
|
public void onEnchantmentPerformed(ItemStack enchantedItem, int levelCost) {
|
|
this.experienceLevel -= levelCost;
|
|
if (this.experienceLevel < 0) {
|
|
this.experienceLevel = 0;
|
|
this.experienceProgress = 0.0F;
|
|
this.totalExperience = 0;
|
|
}
|
|
|
|
this.enchantmentSeed = this.random.nextInt();
|
|
}
|
|
|
|
/**
|
|
* Add experience levels to this player.
|
|
*/
|
|
public void giveExperienceLevels(int levels) {
|
|
this.experienceLevel += levels;
|
|
if (this.experienceLevel < 0) {
|
|
this.experienceLevel = 0;
|
|
this.experienceProgress = 0.0F;
|
|
this.totalExperience = 0;
|
|
}
|
|
|
|
if (levels > 0 && this.experienceLevel % 5 == 0 && this.lastLevelUpTime < this.tickCount - 100.0F) {
|
|
float f = this.experienceLevel > 30 ? 1.0F : this.experienceLevel / 30.0F;
|
|
this.level().playSound(null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_LEVELUP, this.getSoundSource(), f * 0.75F, 1.0F);
|
|
this.lastLevelUpTime = this.tickCount;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This method returns the cap amount of experience that the experience bar can hold. With each level, the experience cap on the player's experience bar is raised by 10.
|
|
*/
|
|
public int getXpNeededForNextLevel() {
|
|
if (this.experienceLevel >= 30) {
|
|
return 112 + (this.experienceLevel - 30) * 9;
|
|
} else {
|
|
return this.experienceLevel >= 15 ? 37 + (this.experienceLevel - 15) * 5 : 7 + this.experienceLevel * 2;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Increases exhaustion level by the supplied amount.
|
|
*/
|
|
public void causeFoodExhaustion(float exhaustion) {
|
|
if (!this.abilities.invulnerable) {
|
|
if (!this.level().isClientSide) {
|
|
this.foodData.addExhaustion(exhaustion);
|
|
}
|
|
}
|
|
}
|
|
|
|
public Optional<WardenSpawnTracker> getWardenSpawnTracker() {
|
|
return Optional.empty();
|
|
}
|
|
|
|
/**
|
|
* Returns the player's FoodStats object.
|
|
*/
|
|
public FoodData getFoodData() {
|
|
return this.foodData;
|
|
}
|
|
|
|
public boolean canEat(boolean canAlwaysEat) {
|
|
return this.abilities.invulnerable || canAlwaysEat || this.foodData.needsFood();
|
|
}
|
|
|
|
/**
|
|
* Checks if the player's health is not full and not zero.
|
|
*/
|
|
public boolean isHurt() {
|
|
return this.getHealth() > 0.0F && this.getHealth() < this.getMaxHealth();
|
|
}
|
|
|
|
public boolean mayBuild() {
|
|
return this.abilities.mayBuild;
|
|
}
|
|
|
|
/**
|
|
* Returns whether this player can modify the block at a certain location with the given stack.
|
|
* <p>
|
|
* The position being queried is {@code pos.offset(facing.getOpposite())}.
|
|
*
|
|
* @return Whether this player may modify the queried location in the current world
|
|
* @see ItemStack#canPlaceOn(Block)
|
|
* @see ItemStack#canEditBlocks()
|
|
* @see PlayerCapabilities#allowEdit
|
|
*/
|
|
public boolean mayUseItemAt(BlockPos pos, Direction facing, ItemStack stack) {
|
|
if (this.abilities.mayBuild) {
|
|
return true;
|
|
} else {
|
|
BlockPos blockPos = pos.relative(facing.getOpposite());
|
|
BlockInWorld blockInWorld = new BlockInWorld(this.level(), blockPos, false);
|
|
return stack.canPlaceOnBlockInAdventureMode(blockInWorld);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected int getBaseExperienceReward() {
|
|
if (!this.level().getGameRules().getBoolean(GameRules.RULE_KEEPINVENTORY) && !this.isSpectator()) {
|
|
int i = this.experienceLevel * 7;
|
|
return i > 100 ? 100 : i;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected boolean isAlwaysExperienceDropper() {
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public boolean shouldShowName() {
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
protected Entity.MovementEmission getMovementEmission() {
|
|
return this.abilities.flying || this.onGround() && this.isDiscrete() ? Entity.MovementEmission.NONE : Entity.MovementEmission.ALL;
|
|
}
|
|
|
|
/**
|
|
* Sends the player's abilities to the server (if there is one).
|
|
*/
|
|
public void onUpdateAbilities() {
|
|
}
|
|
|
|
@Override
|
|
public Component getName() {
|
|
return Component.literal(this.gameProfile.getName());
|
|
}
|
|
|
|
/**
|
|
* Returns the InventoryEnderChest of this player.
|
|
*/
|
|
public PlayerEnderChestContainer getEnderChestInventory() {
|
|
return this.enderChestInventory;
|
|
}
|
|
|
|
@Override
|
|
public ItemStack getItemBySlot(EquipmentSlot slot) {
|
|
if (slot == EquipmentSlot.MAINHAND) {
|
|
return this.inventory.getSelected();
|
|
} else if (slot == EquipmentSlot.OFFHAND) {
|
|
return this.inventory.offhand.get(0);
|
|
} else {
|
|
return slot.getType() == EquipmentSlot.Type.HUMANOID_ARMOR ? this.inventory.armor.get(slot.getIndex()) : ItemStack.EMPTY;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected boolean doesEmitEquipEvent(EquipmentSlot slot) {
|
|
return slot.getType() == EquipmentSlot.Type.HUMANOID_ARMOR;
|
|
}
|
|
|
|
@Override
|
|
public void setItemSlot(EquipmentSlot slot, ItemStack stack) {
|
|
this.verifyEquippedItem(stack);
|
|
if (slot == EquipmentSlot.MAINHAND) {
|
|
this.onEquipItem(slot, this.inventory.items.set(this.inventory.selected, stack), stack);
|
|
} else if (slot == EquipmentSlot.OFFHAND) {
|
|
this.onEquipItem(slot, this.inventory.offhand.set(0, stack), stack);
|
|
} else if (slot.getType() == EquipmentSlot.Type.HUMANOID_ARMOR) {
|
|
this.onEquipItem(slot, this.inventory.armor.set(slot.getIndex(), stack), stack);
|
|
}
|
|
}
|
|
|
|
public boolean addItem(ItemStack stack) {
|
|
return this.inventory.add(stack);
|
|
}
|
|
|
|
@Override
|
|
public Iterable<ItemStack> getHandSlots() {
|
|
return Lists.<ItemStack>newArrayList(this.getMainHandItem(), this.getOffhandItem());
|
|
}
|
|
|
|
@Override
|
|
public Iterable<ItemStack> getArmorSlots() {
|
|
return this.inventory.armor;
|
|
}
|
|
|
|
@Override
|
|
public boolean canUseSlot(EquipmentSlot slot) {
|
|
return slot != EquipmentSlot.BODY;
|
|
}
|
|
|
|
public boolean setEntityOnShoulder(CompoundTag entityCompound) {
|
|
if (this.isPassenger() || !this.onGround() || this.isInWater() || this.isInPowderSnow) {
|
|
return false;
|
|
} else if (this.getShoulderEntityLeft().isEmpty()) {
|
|
this.setShoulderEntityLeft(entityCompound);
|
|
this.timeEntitySatOnShoulder = this.level().getGameTime();
|
|
return true;
|
|
} else if (this.getShoulderEntityRight().isEmpty()) {
|
|
this.setShoulderEntityRight(entityCompound);
|
|
this.timeEntitySatOnShoulder = this.level().getGameTime();
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
protected void removeEntitiesOnShoulder() {
|
|
if (this.timeEntitySatOnShoulder + 20L < this.level().getGameTime()) {
|
|
this.respawnEntityOnShoulder(this.getShoulderEntityLeft());
|
|
this.setShoulderEntityLeft(new CompoundTag());
|
|
this.respawnEntityOnShoulder(this.getShoulderEntityRight());
|
|
this.setShoulderEntityRight(new CompoundTag());
|
|
}
|
|
}
|
|
|
|
private void respawnEntityOnShoulder(CompoundTag entityCompound) {
|
|
if (!this.level().isClientSide && !entityCompound.isEmpty()) {
|
|
EntityType.create(entityCompound, this.level()).ifPresent(entity -> {
|
|
if (entity instanceof TamableAnimal) {
|
|
((TamableAnimal)entity).setOwnerUUID(this.uuid);
|
|
}
|
|
|
|
entity.setPos(this.getX(), this.getY() + 0.7F, this.getZ());
|
|
((ServerLevel)this.level()).addWithUUID(entity);
|
|
});
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public abstract boolean isSpectator();
|
|
|
|
@Override
|
|
public boolean canBeHitByProjectile() {
|
|
return !this.isSpectator() && super.canBeHitByProjectile();
|
|
}
|
|
|
|
@Override
|
|
public boolean isSwimming() {
|
|
return !this.abilities.flying && !this.isSpectator() && super.isSwimming();
|
|
}
|
|
|
|
public abstract boolean isCreative();
|
|
|
|
@Override
|
|
public boolean isPushedByFluid() {
|
|
return !this.abilities.flying;
|
|
}
|
|
|
|
public Scoreboard getScoreboard() {
|
|
return this.level().getScoreboard();
|
|
}
|
|
|
|
@Override
|
|
public Component getDisplayName() {
|
|
MutableComponent mutableComponent = PlayerTeam.formatNameForTeam(this.getTeam(), this.getName());
|
|
return this.decorateDisplayNameComponent(mutableComponent);
|
|
}
|
|
|
|
private MutableComponent decorateDisplayNameComponent(MutableComponent displayName) {
|
|
String string = this.getGameProfile().getName();
|
|
return displayName.withStyle(
|
|
style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/tell " + string + " "))
|
|
.withHoverEvent(this.createHoverEvent())
|
|
.withInsertion(string)
|
|
);
|
|
}
|
|
|
|
@Override
|
|
public String getScoreboardName() {
|
|
return this.getGameProfile().getName();
|
|
}
|
|
|
|
@Override
|
|
protected void internalSetAbsorptionAmount(float absorptionAmount) {
|
|
this.getEntityData().set(DATA_PLAYER_ABSORPTION_ID, absorptionAmount);
|
|
}
|
|
|
|
@Override
|
|
public float getAbsorptionAmount() {
|
|
return this.getEntityData().get(DATA_PLAYER_ABSORPTION_ID);
|
|
}
|
|
|
|
public boolean isModelPartShown(PlayerModelPart part) {
|
|
return (this.getEntityData().get(DATA_PLAYER_MODE_CUSTOMISATION) & part.getMask()) == part.getMask();
|
|
}
|
|
|
|
@Override
|
|
public SlotAccess getSlot(int slot) {
|
|
if (slot == 499) {
|
|
return new SlotAccess() {
|
|
@Override
|
|
public ItemStack get() {
|
|
return Player.this.containerMenu.getCarried();
|
|
}
|
|
|
|
@Override
|
|
public boolean set(ItemStack carried) {
|
|
Player.this.containerMenu.setCarried(carried);
|
|
return true;
|
|
}
|
|
};
|
|
} else {
|
|
final int i = slot - 500;
|
|
if (i >= 0 && i < 4) {
|
|
return new SlotAccess() {
|
|
@Override
|
|
public ItemStack get() {
|
|
return Player.this.inventoryMenu.getCraftSlots().getItem(i);
|
|
}
|
|
|
|
@Override
|
|
public boolean set(ItemStack carried) {
|
|
Player.this.inventoryMenu.getCraftSlots().setItem(i, carried);
|
|
Player.this.inventoryMenu.slotsChanged(Player.this.inventory);
|
|
return true;
|
|
}
|
|
};
|
|
} else if (slot >= 0 && slot < this.inventory.items.size()) {
|
|
return SlotAccess.forContainer(this.inventory, slot);
|
|
} else {
|
|
int j = slot - 200;
|
|
return j >= 0 && j < this.enderChestInventory.getContainerSize() ? SlotAccess.forContainer(this.enderChestInventory, j) : super.getSlot(slot);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Whether the "reducedDebugInfo" option is active for this player.
|
|
*/
|
|
public boolean isReducedDebugInfo() {
|
|
return this.reducedDebugInfo;
|
|
}
|
|
|
|
public void setReducedDebugInfo(boolean reducedDebugInfo) {
|
|
this.reducedDebugInfo = reducedDebugInfo;
|
|
}
|
|
|
|
@Override
|
|
public void setRemainingFireTicks(int remainingFireTicks) {
|
|
super.setRemainingFireTicks(this.abilities.invulnerable ? Math.min(remainingFireTicks, 1) : remainingFireTicks);
|
|
}
|
|
|
|
@Override
|
|
public HumanoidArm getMainArm() {
|
|
return this.entityData.get(DATA_PLAYER_MAIN_HAND) == 0 ? HumanoidArm.LEFT : HumanoidArm.RIGHT;
|
|
}
|
|
|
|
public void setMainArm(HumanoidArm hand) {
|
|
this.entityData.set(DATA_PLAYER_MAIN_HAND, (byte)(hand == HumanoidArm.LEFT ? 0 : 1));
|
|
}
|
|
|
|
public CompoundTag getShoulderEntityLeft() {
|
|
return this.entityData.get(DATA_SHOULDER_LEFT);
|
|
}
|
|
|
|
protected void setShoulderEntityLeft(CompoundTag entityCompound) {
|
|
this.entityData.set(DATA_SHOULDER_LEFT, entityCompound);
|
|
}
|
|
|
|
public CompoundTag getShoulderEntityRight() {
|
|
return this.entityData.get(DATA_SHOULDER_RIGHT);
|
|
}
|
|
|
|
protected void setShoulderEntityRight(CompoundTag entityCompound) {
|
|
this.entityData.set(DATA_SHOULDER_RIGHT, entityCompound);
|
|
}
|
|
|
|
public float getCurrentItemAttackStrengthDelay() {
|
|
return (float)(1.0 / this.getAttributeValue(Attributes.ATTACK_SPEED) * 20.0);
|
|
}
|
|
|
|
/**
|
|
* Returns the percentage of attack power available based on the cooldown (zero to one).
|
|
*/
|
|
public float getAttackStrengthScale(float adjustTicks) {
|
|
return Mth.clamp((this.attackStrengthTicker + adjustTicks) / this.getCurrentItemAttackStrengthDelay(), 0.0F, 1.0F);
|
|
}
|
|
|
|
public void resetAttackStrengthTicker() {
|
|
this.attackStrengthTicker = 0;
|
|
}
|
|
|
|
public ItemCooldowns getCooldowns() {
|
|
return this.cooldowns;
|
|
}
|
|
|
|
@Override
|
|
protected float getBlockSpeedFactor() {
|
|
return !this.abilities.flying && !this.isFallFlying() ? super.getBlockSpeedFactor() : 1.0F;
|
|
}
|
|
|
|
public float getLuck() {
|
|
return (float)this.getAttributeValue(Attributes.LUCK);
|
|
}
|
|
|
|
public boolean canUseGameMasterBlocks() {
|
|
return this.abilities.instabuild && this.getPermissionLevel() >= 2;
|
|
}
|
|
|
|
@Override
|
|
public boolean canTakeItem(ItemStack stack) {
|
|
EquipmentSlot equipmentSlot = this.getEquipmentSlotForItem(stack);
|
|
return this.getItemBySlot(equipmentSlot).isEmpty();
|
|
}
|
|
|
|
@Override
|
|
public EntityDimensions getDefaultDimensions(Pose pose) {
|
|
return (EntityDimensions)POSES.getOrDefault(pose, STANDING_DIMENSIONS);
|
|
}
|
|
|
|
@Override
|
|
public ImmutableList<Pose> getDismountPoses() {
|
|
return ImmutableList.of(Pose.STANDING, Pose.CROUCHING, Pose.SWIMMING);
|
|
}
|
|
|
|
@Override
|
|
public ItemStack getProjectile(ItemStack weaponStack) {
|
|
if (!(weaponStack.getItem() instanceof ProjectileWeaponItem)) {
|
|
return ItemStack.EMPTY;
|
|
} else {
|
|
Predicate<ItemStack> predicate = ((ProjectileWeaponItem)weaponStack.getItem()).getSupportedHeldProjectiles();
|
|
ItemStack itemStack = ProjectileWeaponItem.getHeldProjectile(this, predicate);
|
|
if (!itemStack.isEmpty()) {
|
|
return itemStack;
|
|
} else {
|
|
predicate = ((ProjectileWeaponItem)weaponStack.getItem()).getAllSupportedProjectiles();
|
|
|
|
for (int i = 0; i < this.inventory.getContainerSize(); i++) {
|
|
ItemStack itemStack2 = this.inventory.getItem(i);
|
|
if (predicate.test(itemStack2)) {
|
|
return itemStack2;
|
|
}
|
|
}
|
|
|
|
return this.abilities.instabuild ? new ItemStack(Items.ARROW) : ItemStack.EMPTY;
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public ItemStack eat(Level level, ItemStack food, FoodProperties foodProperties) {
|
|
this.getFoodData().eat(foodProperties);
|
|
this.awardStat(Stats.ITEM_USED.get(food.getItem()));
|
|
level.playSound(null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_BURP, SoundSource.PLAYERS, 0.5F, level.random.nextFloat() * 0.1F + 0.9F);
|
|
if (this instanceof ServerPlayer) {
|
|
CriteriaTriggers.CONSUME_ITEM.trigger((ServerPlayer)this, food);
|
|
}
|
|
|
|
ItemStack itemStack = super.eat(level, food, foodProperties);
|
|
Optional<ItemStack> optional = foodProperties.usingConvertsTo();
|
|
if (optional.isPresent() && !this.hasInfiniteMaterials()) {
|
|
if (itemStack.isEmpty()) {
|
|
return ((ItemStack)optional.get()).copy();
|
|
}
|
|
|
|
if (!this.level().isClientSide()) {
|
|
this.getInventory().add(((ItemStack)optional.get()).copy());
|
|
}
|
|
}
|
|
|
|
return itemStack;
|
|
}
|
|
|
|
@Override
|
|
public Vec3 getRopeHoldPosition(float partialTicks) {
|
|
double d = 0.22 * (this.getMainArm() == HumanoidArm.RIGHT ? -1.0 : 1.0);
|
|
float f = Mth.lerp(partialTicks * 0.5F, this.getXRot(), this.xRotO) * (float) (Math.PI / 180.0);
|
|
float g = Mth.lerp(partialTicks, this.yBodyRotO, this.yBodyRot) * (float) (Math.PI / 180.0);
|
|
if (this.isFallFlying() || this.isAutoSpinAttack()) {
|
|
Vec3 vec3 = this.getViewVector(partialTicks);
|
|
Vec3 vec32 = this.getDeltaMovement();
|
|
double e = vec32.horizontalDistanceSqr();
|
|
double h = vec3.horizontalDistanceSqr();
|
|
float k;
|
|
if (e > 0.0 && h > 0.0) {
|
|
double i = (vec32.x * vec3.x + vec32.z * vec3.z) / Math.sqrt(e * h);
|
|
double j = vec32.x * vec3.z - vec32.z * vec3.x;
|
|
k = (float)(Math.signum(j) * Math.acos(i));
|
|
} else {
|
|
k = 0.0F;
|
|
}
|
|
|
|
return this.getPosition(partialTicks).add(new Vec3(d, -0.11, 0.85).zRot(-k).xRot(-f).yRot(-g));
|
|
} else if (this.isVisuallySwimming()) {
|
|
return this.getPosition(partialTicks).add(new Vec3(d, 0.2, -0.15).xRot(-f).yRot(-g));
|
|
} else {
|
|
double l = this.getBoundingBox().getYsize() - 1.0;
|
|
double e = this.isCrouching() ? -0.2 : 0.07;
|
|
return this.getPosition(partialTicks).add(new Vec3(d, l, e).yRot(-g));
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean isAlwaysTicking() {
|
|
return true;
|
|
}
|
|
|
|
public boolean isScoping() {
|
|
return this.isUsingItem() && this.getUseItem().is(Items.SPYGLASS);
|
|
}
|
|
|
|
@Override
|
|
public boolean shouldBeSaved() {
|
|
return false;
|
|
}
|
|
|
|
public Optional<GlobalPos> getLastDeathLocation() {
|
|
return this.lastDeathLocation;
|
|
}
|
|
|
|
public void setLastDeathLocation(Optional<GlobalPos> lastDeathLocation) {
|
|
this.lastDeathLocation = lastDeathLocation;
|
|
}
|
|
|
|
@Override
|
|
public float getHurtDir() {
|
|
return this.hurtDir;
|
|
}
|
|
|
|
@Override
|
|
public void animateHurt(float yaw) {
|
|
super.animateHurt(yaw);
|
|
this.hurtDir = yaw;
|
|
}
|
|
|
|
@Override
|
|
public boolean canSprint() {
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
protected float getFlyingSpeed() {
|
|
if (this.abilities.flying && !this.isPassenger()) {
|
|
return this.isSprinting() ? this.abilities.getFlyingSpeed() * 2.0F : this.abilities.getFlyingSpeed();
|
|
} else {
|
|
return this.isSprinting() ? 0.025999999F : 0.02F;
|
|
}
|
|
}
|
|
|
|
public double blockInteractionRange() {
|
|
return this.getAttributeValue(Attributes.BLOCK_INTERACTION_RANGE);
|
|
}
|
|
|
|
public double entityInteractionRange() {
|
|
return this.getAttributeValue(Attributes.ENTITY_INTERACTION_RANGE);
|
|
}
|
|
|
|
public boolean canInteractWithEntity(Entity entity, double distance) {
|
|
return entity.isRemoved() ? false : this.canInteractWithEntity(entity.getBoundingBox(), distance);
|
|
}
|
|
|
|
public boolean canInteractWithEntity(AABB boundingBox, double distance) {
|
|
double d = this.entityInteractionRange() + distance;
|
|
return boundingBox.distanceToSqr(this.getEyePosition()) < d * d;
|
|
}
|
|
|
|
public boolean canInteractWithBlock(BlockPos pos, double distance) {
|
|
double d = this.blockInteractionRange() + distance;
|
|
return new AABB(pos).distanceToSqr(this.getEyePosition()) < d * d;
|
|
}
|
|
|
|
public void setIgnoreFallDamageFromCurrentImpulse(boolean ignoreFallDamageFromCurrentImpulse) {
|
|
this.ignoreFallDamageFromCurrentImpulse = ignoreFallDamageFromCurrentImpulse;
|
|
if (ignoreFallDamageFromCurrentImpulse) {
|
|
this.currentImpulseContextResetGraceTime = 40;
|
|
} else {
|
|
this.currentImpulseContextResetGraceTime = 0;
|
|
}
|
|
}
|
|
|
|
public boolean isIgnoringFallDamageFromCurrentImpulse() {
|
|
return this.ignoreFallDamageFromCurrentImpulse;
|
|
}
|
|
|
|
public void tryResetCurrentImpulseContext() {
|
|
if (this.currentImpulseContextResetGraceTime == 0) {
|
|
this.resetCurrentImpulseContext();
|
|
}
|
|
}
|
|
|
|
public void resetCurrentImpulseContext() {
|
|
this.currentImpulseContextResetGraceTime = 0;
|
|
this.currentExplosionCause = null;
|
|
this.currentImpulseImpactPos = null;
|
|
this.ignoreFallDamageFromCurrentImpulse = false;
|
|
}
|
|
|
|
public static enum BedSleepingProblem {
|
|
NOT_POSSIBLE_HERE,
|
|
NOT_POSSIBLE_NOW(Component.translatable("block.minecraft.bed.no_sleep")),
|
|
TOO_FAR_AWAY(Component.translatable("block.minecraft.bed.too_far_away")),
|
|
OBSTRUCTED(Component.translatable("block.minecraft.bed.obstructed")),
|
|
OTHER_PROBLEM,
|
|
NOT_SAFE(Component.translatable("block.minecraft.bed.not_safe"));
|
|
|
|
@Nullable
|
|
private final Component message;
|
|
|
|
private BedSleepingProblem() {
|
|
this.message = null;
|
|
}
|
|
|
|
private BedSleepingProblem(final Component message) {
|
|
this.message = message;
|
|
}
|
|
|
|
@Nullable
|
|
public Component getMessage() {
|
|
return this.message;
|
|
}
|
|
}
|
|
}
|