Long story short: I have an item that, when right clicked, does two things.
1. Spawns in an entity
2. Sets the player who spawned it in as riding that entity.
This "works", but the problem is that it takes multiple ticks for the entity's existence to be recognized by the client. This creates a really jarring effect where the player will hover frozen in the air for a 1/4-1/2 of a second before it resumes as expected. Since the entity moves rather quickly this can also be extremely disorienting to the player, making using the item much less fun and rather confusing.
A hypothetical solution would be, in my mind, to add a stage to the entity spawning process that first spawns it on the client side, then a packet is sent to the server to tell it that the entity exists and to tell the server to tell everyone else that the entity exists.
However, this goes against how minecraft fundamentally does its entity creation, which is the server is told to make an entity, then tell everyone on the server of the entities existence and synchronizes everything. I don't think either minecraft or forge has the ability to accommodate the method above, so that leaves behind the $64,000 question.
Q: Is ensuring that a client recognizes an entity the first tick it is created even possible (that is, is this a solution I should be persuing?), and if so, how?
private static EntityType.Builder<HoverBoardEntity> hoverBoard() {
return EntityType.Builder.<HoverBoardEntity>of(HoverBoardEntity::new, MobCategory.MISC)
.sized(1.0f, 0.1f)
.fireImmune()
.setUpdateInterval(1)
.clientTrackingRange(10)
.setCustomClientFactory((spawnEntity, level) -> new HoverBoardEntity(MyEntities.HOVER_BOARD_ENTITY.get(), level));
}
public class HoverBoardEntity extends Entity {
protected static final EntityDataAccessor<Optional<UUID>> OWNER_UNIQUE_ID = SynchedEntityData.defineId(HoverBoardEntity.class, EntityDataSerializers.OPTIONAL_UUID);
public HoverBoardEntity(EntityType<?> entityTypeIn, Level worldIn) {
super(entityTypeIn, worldIn);
}
public HoverBoardEntity(Level worldIn) {
this(MyEntities.HOVER_BOARD_ENTITY.get(), worldIn);
this.setNoGravity(true);
this.noPhysics = true;
}
@Override
protected void defineSynchedData() {
this.entityData.define(OWNER_UNIQUE_ID, Optional.empty());
}
@Override
protected void readAdditionalSaveData(CompoundTag compound) {
UUID uuid;
if (compound.hasUUID("Owner")) {
uuid = compound.getUUID("Owner");
} else {
String s = compound.getString("Owner");
uuid = OldUsersConverter.convertMobOwnerIfNecessary(this.getServer(), s);
}
if (uuid != null) {
try {
this.setOwnerId(uuid);
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
protected void addAdditionalSaveData(@NotNull CompoundTag compound) {
if (this.getOwnerId() != null) {
compound.putUUID("Owner", this.getOwnerId());
}
}
@Override
public void tick() {
super.tick();
System.out.println(this.getPlayer());
if (this.getPassengers().isEmpty()) {
this.setCoolDown(this.tickCount * 3 + 60);
this.givePlayerMomentum();
this.remove(RemovalReason.DISCARDED);
} else
if (this.tickCount >= 100) {
boolean creativeFlag = false;
for (Entity e : this.getPassengers()) {
if (e instanceof Player && ((Player) e).isCreative()) creativeFlag = true;
e.stopRiding();
}
this.setCoolDown(!creativeFlag ? 300 : 10);
this.givePlayerMomentum();
this.remove(RemovalReason.DISCARDED);
} else {
this.setDeltaMovement(this.getDeltaMovement().x, this.getDeltaMovement().y * 0.9D, this.getDeltaMovement().z);
this.move(MoverType.PLAYER, this.getDeltaMovement());
if (this.getPlayer() != null) {
Player player = this.getPlayer();
Vec3 collide = GravitationEffect.collide(player, this.getDeltaMovement());
if(collide.x != this.getDeltaMovement().x || collide.z != this.getDeltaMovement().z) {
for (Entity e : this.getPassengers()) {
e.stopRiding();
}
this.setCoolDown(300);
this.givePlayerMomentum();
this.remove(RemovalReason.DISCARDED);
}
}
}
}
@Override
public double getPassengersRidingOffset() {
return 0.2D;
}
@Nullable
public UUID getOwnerId() {
return this.entityData.get(OWNER_UNIQUE_ID).orElse(null);
}
@Override
public boolean shouldRiderSit() {
return false;
}
@Override
protected void removePassenger(@NotNull Entity passenger) {
super.removePassenger(passenger);
passenger.setDeltaMovement(this.getDeltaMovement());
}
public void setOwnerId(@Nullable UUID p_184754_1_) {
this.entityData.set(OWNER_UNIQUE_ID, Optional.ofNullable(p_184754_1_));
}
private void setCoolDown(int cooldown) {
UUID uuid = this.getOwnerId();
if (uuid != null) {
Player player = this.level.getPlayerByUUID(uuid);
if (player != null) {
player.getCooldowns().addCooldown(RareoresItems.HOVER_BOARD.get(), cooldown);
}
}
}
private boolean givePlayerMomentum() {
Player player = this.getPlayer();
if (player != null) {
player.setDeltaMovement(this.getDeltaMovement());
return true;
}
return false;
}
private Player getPlayer() {
UUID uuid = this.getOwnerId();
if (uuid != null) {
return this.level.getPlayerByUUID(uuid);
}
return null;
}
@Override
public @NotNull Vec3 getDismountLocationForPassenger(@NotNull LivingEntity livingEntity) {
return new Vec3(this.position().x, this.position().y, this.position().z);
}
@Override
public void onPassengerTurned(Entity pEntityToUpdate) {
super.onPassengerTurned(pEntityToUpdate);
}
@Override
public boolean canCollideWith(Entity p) {
return false;
}
@Override
public @NotNull Packet<?> getAddEntityPacket() {
return NetworkHooks.getEntitySpawningPacket(this);
}
}
public class ItemHoverBoard extends Item {
public ItemHoverBoard(Properties properties) {
super(properties);
}
@Override
public InteractionResultHolder<ItemStack> use(Level level, Player playerIn, InteractionHand hand) {
ItemStack itemstack = playerIn.getItemInHand(hand);
if(!playerIn.isPassenger()) {
playerIn.startUsingItem(hand);
double x = playerIn.getViewVector(0).x;
double z = playerIn.getViewVector(0).z;
double ratio = 1.0D / Math.sqrt((x * x + z * z));
AttributeInstance gravity = playerIn.getAttribute(net.minecraftforge.common.ForgeMod.ENTITY_GRAVITY.get());
Vec3 motion = new Vec3(x * ratio * 0.667D, playerIn.getDeltaMovement().y + gravity.getValue(), z * ratio * 0.667D);
HoverBoardEntity entity = new HoverBoardEntity(level);
if (!level.isClientSide()) {
level.addFreshEntity(entity);
}
entity.setPosRaw(playerIn.position().x, playerIn.position().y + 0.5D, playerIn.position().z);
playerIn.startRiding(entity);
entity.setOwnerId(playerIn.getUUID());
entity.setDeltaMovement(motion);
return InteractionResultHolder.sidedSuccess(itemstack, level.isClientSide());
}
return InteractionResultHolder.pass(itemstack);
}
}