diff --git a/NMS/v1_20_4/build.gradle b/NMS/v1_20_4/build.gradle new file mode 100644 index 0000000..f33a965 --- /dev/null +++ b/NMS/v1_20_4/build.gradle @@ -0,0 +1,36 @@ +plugins { + id("io.papermc.paperweight.userdev") version "1.6.0" +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(21)) + } +} + +group 'NMS:v1_20_4' + +dependencies { + paperweightDevelopmentBundle("io.papermc.paper:dev-bundle:1.20.5-R0.1-SNAPSHOT") + compileOnly project(":API") + compileOnly rootProject +} + +shadowJar { + archiveFileName = "${project.name}-exclude.jar" +} + +assemble { + dependsOn(reobfJar) +} + +tasks { + reobfJar { + File outputFile = new File(rootProject.archiveFolder, "reobf/${project.name}.jar") + outputJar.set(layout.buildDirectory.file(outputFile.getPath())) + } +} + +if (project.hasProperty('nms.compile_v1_20') && !Boolean.valueOf(project.findProperty("nms.compile_v1_20").toString())) { + project.tasks.all { task -> task.enabled = false } +} \ No newline at end of file diff --git a/NMS/v1_20_4/src/main/java/com/bgsoftware/wildloaders/nms/v1_20_4/ChunkLoaderNPCImpl.java b/NMS/v1_20_4/src/main/java/com/bgsoftware/wildloaders/nms/v1_20_4/ChunkLoaderNPCImpl.java new file mode 100644 index 0000000..eac163a --- /dev/null +++ b/NMS/v1_20_4/src/main/java/com/bgsoftware/wildloaders/nms/v1_20_4/ChunkLoaderNPCImpl.java @@ -0,0 +1,252 @@ +package com.bgsoftware.wildloaders.nms.v1_20_4; + +import com.bgsoftware.common.reflection.ReflectMethod; +import com.bgsoftware.wildloaders.api.npc.ChunkLoaderNPC; +import com.bgsoftware.wildloaders.handlers.NPCHandler; +import com.bgsoftware.wildloaders.npc.DummyChannel; +import com.mojang.authlib.GameProfile; +import net.minecraft.advancements.AdvancementHolder; +import net.minecraft.advancements.AdvancementProgress; +import net.minecraft.core.BlockPos; +import net.minecraft.network.Connection; +import net.minecraft.network.PacketListener; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.PacketFlow; +import net.minecraft.network.protocol.game.ServerboundChatPacket; +import net.minecraft.network.protocol.game.ServerboundContainerClickPacket; +import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket; +import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket; +import net.minecraft.network.protocol.game.ServerboundSetCarriedItemPacket; +import net.minecraft.network.protocol.game.ServerboundSignUpdatePacket; +import net.minecraft.network.protocol.game.ServerboundUseItemPacket; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.PlayerAdvancements; +import net.minecraft.server.ServerAdvancementManager; +import net.minecraft.server.level.ClientInformation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.level.ServerPlayerGameMode; +import net.minecraft.server.network.CommonListenerCookie; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import net.minecraft.world.level.GameType; +import net.minecraft.world.level.storage.LevelResource; +import net.minecraft.world.phys.AABB; +import org.bukkit.Location; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.nio.file.Path; +import java.util.UUID; + +public final class ChunkLoaderNPCImpl extends ServerPlayer implements ChunkLoaderNPC { + + private static final ReflectMethod SET_GAMEMODE = new ReflectMethod<>(ServerPlayerGameMode.class, + 1, GameType.class, GameType.class); + + private final ServerLevel serverLevel; + private final AABB boundingBox; + private final PlayerAdvancements advancements; + + private boolean dieCall = false; + + public ChunkLoaderNPCImpl(MinecraftServer minecraftServer, Location location, UUID uuid) { + super(minecraftServer, ((CraftWorld) location.getWorld()).getHandle(), + new GameProfile(uuid, NPCHandler.getName(location.getWorld().getName())), + ClientInformation.createDefault()); + + this.serverLevel = serverLevel(); + this.boundingBox = new AABB(new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ())); + + this.connection = new DummyServerGamePacketListenerImpl(minecraftServer, this); + + this.advancements = new DummyPlayerAdvancements(minecraftServer, this); + + SET_GAMEMODE.invoke(this.gameMode, GameType.CREATIVE, null); + try { + setLoadViewDistance(2); + setTickViewDistance(2); + setSendViewDistance(2); + } catch (Throwable ignored) { + // Doesn't exist on Spigot + } + + fauxSleeping = true; + + spawnIn(this.serverLevel); + moveTo(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); + + this.serverLevel.addNewPlayer(this); + + super.setBoundingBox(this.boundingBox); + } + + @Override + public UUID getUniqueId() { + return super.getUUID(); + } + + @Override + public void die() { + discard(); + } + + @Override + public AABB getBoundingBoxForCulling() { + return this.boundingBox; + } + + @Override + public void remove(RemovalReason removalReason) { + if (!dieCall) { + dieCall = true; + this.serverLevel.removePlayerImmediately(this, RemovalReason.UNLOADED_WITH_PLAYER); + dieCall = false; + } else { + super.remove(removalReason); + } + } + + @Override + public Location getLocation() { + return getBukkitEntity().getLocation(); + } + + @Override + public Player getPlayer() { + return getBukkitEntity(); + } + + @Override + public PlayerAdvancements getAdvancements() { + return this.advancements; + } + + public static class DummyConnection extends Connection { + + DummyConnection() { + super(PacketFlow.SERVERBOUND); + this.channel = new DummyChannel(); + this.address = null; + } + + @Override + public void setListenerForServerboundHandshake(PacketListener packetListener) { + // Do nothing. + } + } + + public class DummyServerGamePacketListenerImpl extends ServerGamePacketListenerImpl { + + DummyServerGamePacketListenerImpl(MinecraftServer minecraftServer, ServerPlayer serverPlayer) { + super(minecraftServer, new DummyConnection(), serverPlayer, + CommonListenerCookie.createInitial(ChunkLoaderNPCImpl.this.getGameProfile(), false)); + } + + @Override + public void handleContainerClick(ServerboundContainerClickPacket containerClickPacket) { + // Do nothing. + } + + @Override + public void handleMovePlayer(ServerboundMovePlayerPacket movePlayerPacket) { + // Do nothing. + } + + @Override + public void handleSignUpdate(ServerboundSignUpdatePacket signUpdatePacket) { + // Do nothing. + } + + @Override + public void handlePlayerAction(ServerboundPlayerActionPacket playerActionPacket) { + // Do nothing. + } + + @Override + public void handleUseItem(ServerboundUseItemPacket useItemPacket) { + // Do nothing. + } + + @Override + public void handleSetCarriedItem(ServerboundSetCarriedItemPacket setCarriedItemPacket) { + // Do nothing. + } + + @Override + public void handleChat(ServerboundChatPacket chatPacket) { + // Do nothing. + } + + @Override + public void disconnect(String s) { + // Do nothing. + } + + public void send(Packet packet) { + // Do nothing. + } + + } + + private static class DummyPlayerAdvancements extends PlayerAdvancements { + + DummyPlayerAdvancements(MinecraftServer server, ServerPlayer serverPlayer) { + super(server.getFixerUpper(), server.getPlayerList(), server.getAdvancements(), + getAdvancementsFile(server, serverPlayer), serverPlayer); + } + + private static Path getAdvancementsFile(MinecraftServer server, ServerPlayer serverPlayer) { + File advancementsDir = server.getWorldPath(LevelResource.PLAYER_ADVANCEMENTS_DIR).toFile(); + return new File(advancementsDir, serverPlayer.getUUID() + ".json").toPath(); + } + + @Override + public void setPlayer(ServerPlayer owner) { + // Do nothing. + } + + @Override + public void stopListening() { + // Do nothing. + } + + @Override + public void reload(ServerAdvancementManager advancementLoader) { + // Do nothing. + } + + @Override + public void save() { + // Do nothing. + } + + @Override + public boolean award(AdvancementHolder advancement, String criterionName) { + return false; + } + + @Override + public boolean revoke(AdvancementHolder advancement, String criterionName) { + return false; + } + + @Override + public void flushDirty(ServerPlayer player) { + // Do nothing. + } + + @Override + public void setSelectedTab(@Nullable AdvancementHolder advancement) { + // Do nothing. + } + + @Override + public AdvancementProgress getOrStartProgress(AdvancementHolder advancement) { + return new AdvancementProgress(); + } + + } + +} diff --git a/NMS/v1_20_4/src/main/java/com/bgsoftware/wildloaders/nms/v1_20_4/EntityHologram.java b/NMS/v1_20_4/src/main/java/com/bgsoftware/wildloaders/nms/v1_20_4/EntityHologram.java new file mode 100644 index 0000000..fb977c0 --- /dev/null +++ b/NMS/v1_20_4/src/main/java/com/bgsoftware/wildloaders/nms/v1_20_4/EntityHologram.java @@ -0,0 +1,165 @@ +package com.bgsoftware.wildloaders.nms.v1_20_4; + +import com.bgsoftware.wildloaders.api.holograms.Hologram; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.sounds.SoundEvent; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.decoration.ArmorStand; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.entity.CraftArmorStand; +import org.bukkit.craftbukkit.entity.CraftEntity; +import org.bukkit.craftbukkit.util.CraftChatMessage; + +public final class EntityHologram extends ArmorStand implements Hologram { + + private static final AABB EMPTY_BOUND = new AABB(0D, 0D, 0D, 0D, 0D, 0D); + + private CraftEntity bukkitEntity; + + public EntityHologram(ServerLevel serverLevel, double x, double y, double z) { + super(serverLevel, x, y, z); + + setInvisible(true); + setSmall(true); + setShowArms(false); + setNoGravity(true); + setNoBasePlate(true); + setMarker(true); + + super.collides = false; + super.setCustomNameVisible(true); // Custom name visible + super.setBoundingBox(EMPTY_BOUND); + } + + @Override + public void setHologramName(String name) { + super.setCustomName(CraftChatMessage.fromStringOrNull(name)); + } + + @Override + public void removeHologram() { + super.remove(RemovalReason.DISCARDED); + } + + @Override + public org.bukkit.entity.Entity getEntity() { + return getBukkitEntity(); + } + + @Override + public void tick() { + // Disable normal ticking for this entity. + + // Workaround to force EntityTrackerEntry to send a teleport packet immediately after spawning this entity. + if (this.onGround) { + this.onGround = false; + } + } + + @Override + public void inactiveTick() { + // Disable normal ticking for this entity. + + // Workaround to force EntityTrackerEntry to send a teleport packet immediately after spawning this entity. + if (this.onGround) { + this.onGround = false; + } + } + + @Override + public void addAdditionalSaveData(CompoundTag compoundTag) { + // Do not save NBT. + } + + @Override + public boolean saveAsPassenger(CompoundTag compoundTag) { + // Do not save NBT. + return false; + } + + @Override + public CompoundTag saveWithoutId(CompoundTag compoundTag) { + // Do not save NBT. + return compoundTag; + } + + @Override + public void readAdditionalSaveData(CompoundTag compoundTag) { + // Do not load NBT. + } + + @Override + public void load(CompoundTag compoundTag) { + // Do not load NBT. + } + + @Override + public boolean isInvulnerableTo(DamageSource source) { + /* + * The field Entity.invulnerable is private. + * It's only used while saving NBTTags, but since the entity would be killed + * on chunk unload, we prefer to override isInvulnerable(). + */ + return true; + } + + @Override + public boolean repositionEntityAfterLoad() { + return false; + } + + @Override + public void setCustomName(Component component) { + // Locks the custom name. + } + + @Override + public void setCustomNameVisible(boolean visible) { + // Locks the custom name. + } + + @Override + public InteractionResult interactAt(Player player, Vec3 hitPos, InteractionHand hand) { + // Prevent stand being equipped + return InteractionResult.PASS; + } + + @Override + public void setItemSlot(EquipmentSlot equipmentSlot, ItemStack itemStack, boolean silent) { + // Prevent stand being equipped + } + + @Override + public AABB getBoundingBoxForCulling() { + return EMPTY_BOUND; + } + + @Override + public void playSound(SoundEvent soundEvent, float volume, float pitch) { + // Remove sounds. + } + + @Override + public void remove(RemovalReason removalReason) { + // Prevent being killed. + } + + @Override + public CraftEntity getBukkitEntity() { + if (bukkitEntity == null) { + bukkitEntity = new CraftArmorStand((CraftServer) Bukkit.getServer(), this); + } + return bukkitEntity; + } + +} diff --git a/NMS/v1_20_4/src/main/java/com/bgsoftware/wildloaders/nms/v1_20_4/NMSAdapter.java b/NMS/v1_20_4/src/main/java/com/bgsoftware/wildloaders/nms/v1_20_4/NMSAdapter.java new file mode 100644 index 0000000..c401f6a --- /dev/null +++ b/NMS/v1_20_4/src/main/java/com/bgsoftware/wildloaders/nms/v1_20_4/NMSAdapter.java @@ -0,0 +1,175 @@ +package com.bgsoftware.wildloaders.nms.v1_20_4; + +import com.bgsoftware.wildloaders.api.loaders.ChunkLoader; +import com.bgsoftware.wildloaders.loaders.ITileEntityChunkLoader; +import com.bgsoftware.wildloaders.nms.v1_20_4.loader.ChunkLoaderBlockEntity; +import com.mojang.authlib.properties.Property; +import com.mojang.authlib.properties.PropertyMap; +import net.minecraft.core.BlockPos; +import net.minecraft.core.component.DataComponents; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.component.CustomData; +import net.minecraft.world.item.component.ResolvableProfile; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.SpawnerBlockEntity; +import net.minecraft.world.level.chunk.LevelChunk; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.inventory.CraftItemStack; + +import java.util.Optional; +import java.util.UUID; + +public final class NMSAdapter implements com.bgsoftware.wildloaders.nms.NMSAdapter { + + @Override + public String getTag(org.bukkit.inventory.ItemStack bukkitItem, String key, String def) { + ItemStack itemStack = CraftItemStack.asNMSCopy(bukkitItem); + CustomData customData = itemStack.get(DataComponents.CUSTOM_DATA); + if (customData != null) { + CompoundTag compoundTag = customData.getUnsafe(); + if (compoundTag.contains(key, 8)) + return compoundTag.getString(key); + } + return def; + } + + @Override + public org.bukkit.inventory.ItemStack setTag(org.bukkit.inventory.ItemStack bukkitItem, String key, String value) { + ItemStack itemStack = CraftItemStack.asNMSCopy(bukkitItem); + + CustomData customData = itemStack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY); + customData = customData.update(compoundTag -> compoundTag.putString(key, value)); + itemStack.set(DataComponents.CUSTOM_DATA, customData); + + return CraftItemStack.asBukkitCopy(itemStack); + } + + @Override + public long getTag(org.bukkit.inventory.ItemStack bukkitItem, String key, long def) { + ItemStack itemStack = CraftItemStack.asNMSCopy(bukkitItem); + CustomData customData = itemStack.get(DataComponents.CUSTOM_DATA); + if (customData != null) { + CompoundTag compoundTag = customData.getUnsafe(); + if (compoundTag.contains(key, 4)) + return compoundTag.getLong(key); + } + return def; + } + + @Override + public org.bukkit.inventory.ItemStack setTag(org.bukkit.inventory.ItemStack bukkitItem, String key, long value) { + ItemStack itemStack = CraftItemStack.asNMSCopy(bukkitItem); + + CustomData customData = itemStack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY); + customData = customData.update(compoundTag -> compoundTag.putLong(key, value)); + itemStack.set(DataComponents.CUSTOM_DATA, customData); + + return CraftItemStack.asBukkitCopy(itemStack); + } + + @Override + public org.bukkit.inventory.ItemStack getPlayerSkull(org.bukkit.inventory.ItemStack bukkitItem, String texture) { + ItemStack itemStack = CraftItemStack.asNMSCopy(bukkitItem); + + PropertyMap propertyMap = new PropertyMap(); + propertyMap.put("textures", new Property("textures", texture)); + + ResolvableProfile resolvableProfile = new ResolvableProfile(Optional.empty(), Optional.empty(), propertyMap); + + itemStack.set(DataComponents.PROFILE, resolvableProfile); + + return CraftItemStack.asBukkitCopy(itemStack); + } + + @Override + public com.bgsoftware.wildloaders.api.npc.ChunkLoaderNPC createNPC(Location location, UUID uuid) { + return new ChunkLoaderNPCImpl(((CraftServer) Bukkit.getServer()).getServer(), location, uuid); + } + + @Override + public ITileEntityChunkLoader createLoader(ChunkLoader chunkLoader) { + Location loaderLoc = chunkLoader.getLocation(); + World bukkitWorld = loaderLoc.getWorld(); + + if (bukkitWorld == null) + throw new IllegalArgumentException("Cannot create loader in null world."); + + ServerLevel serverLevel = ((CraftWorld) bukkitWorld).getHandle(); + BlockPos blockPos = new BlockPos(loaderLoc.getBlockX(), loaderLoc.getBlockY(), loaderLoc.getBlockZ()); + + ChunkLoaderBlockEntity ChunkLoaderBlockEntity = new ChunkLoaderBlockEntity(chunkLoader, serverLevel, blockPos); + serverLevel.addBlockEntityTicker(ChunkLoaderBlockEntity.getTicker()); + + for (org.bukkit.Chunk bukkitChunk : chunkLoader.getLoadedChunks()) { + LevelChunk levelChunk = serverLevel.getChunk(bukkitChunk.getX(), bukkitChunk.getZ()); + levelChunk.getBlockEntities().values().stream() + .filter(blockEntity -> blockEntity instanceof SpawnerBlockEntity) + .forEach(blockEntity -> { + ((SpawnerBlockEntity) blockEntity).getSpawner().requiredPlayerRange = -1; + }); + + ChunkPos chunkPos = levelChunk.getPos(); + serverLevel.setChunkForced(chunkPos.x, chunkPos.z, true); + } + + return ChunkLoaderBlockEntity; + } + + @Override + public void removeLoader(ChunkLoader chunkLoader, boolean spawnParticle) { + Location loaderLoc = chunkLoader.getLocation(); + World bukkitWorld = loaderLoc.getWorld(); + + if (bukkitWorld == null) + throw new IllegalArgumentException("Cannot remove loader in null world."); + + ServerLevel serverLevel = ((CraftWorld) bukkitWorld).getHandle(); + BlockPos blockPos = new BlockPos(loaderLoc.getBlockX(), loaderLoc.getBlockY(), loaderLoc.getBlockZ()); + + long chunkPosLong = ChunkPos.asLong(blockPos.getX() >> 4, blockPos.getZ() >> 4); + ChunkLoaderBlockEntity chunkLoaderBlockEntity = ChunkLoaderBlockEntity.chunkLoaderBlockEntityMap.remove(chunkPosLong); + + if (chunkLoaderBlockEntity != null) { + chunkLoaderBlockEntity.holograms.forEach(EntityHologram::removeHologram); + chunkLoaderBlockEntity.removed = true; + } + + if (spawnParticle) + serverLevel.levelEvent(null, 2001, blockPos, Block.getId(serverLevel.getBlockState(blockPos))); + + for (org.bukkit.Chunk bukkitChunk : chunkLoader.getLoadedChunks()) { + LevelChunk levelChunk = serverLevel.getChunk(bukkitChunk.getX(), bukkitChunk.getZ()); + levelChunk.getBlockEntities().values().stream() + .filter(blockEntity -> blockEntity instanceof SpawnerBlockEntity) + .forEach(blockEntity -> { + ((SpawnerBlockEntity) blockEntity).getSpawner().requiredPlayerRange = 16; + }); + + ChunkPos chunkPos = levelChunk.getPos(); + serverLevel.setChunkForced(chunkPos.x, chunkPos.z, false); + } + } + + @Override + public void updateSpawner(Location location, boolean reset) { + World bukkitWorld = location.getWorld(); + + if (bukkitWorld == null) + throw new IllegalArgumentException("Cannot remove loader in null world."); + + ServerLevel serverLevel = ((CraftWorld) bukkitWorld).getHandle(); + BlockPos blockPos = new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()); + BlockEntity blockEntity = serverLevel.getBlockEntity(blockPos); + if (blockEntity instanceof SpawnerBlockEntity spawnerBlockEntity) + spawnerBlockEntity.getSpawner().requiredPlayerRange = reset ? 16 : -1; + } + +} diff --git a/NMS/v1_20_4/src/main/java/com/bgsoftware/wildloaders/nms/v1_20_4/loader/ChunkLoaderBlockEntity.java b/NMS/v1_20_4/src/main/java/com/bgsoftware/wildloaders/nms/v1_20_4/loader/ChunkLoaderBlockEntity.java new file mode 100644 index 0000000..dbd8252 --- /dev/null +++ b/NMS/v1_20_4/src/main/java/com/bgsoftware/wildloaders/nms/v1_20_4/loader/ChunkLoaderBlockEntity.java @@ -0,0 +1,149 @@ +package com.bgsoftware.wildloaders.nms.v1_20_4.loader; + +import com.bgsoftware.wildloaders.api.holograms.Hologram; +import com.bgsoftware.wildloaders.api.loaders.ChunkLoader; +import com.bgsoftware.wildloaders.loaders.ITileEntityChunkLoader; +import com.bgsoftware.wildloaders.loaders.WChunkLoader; +import com.bgsoftware.wildloaders.nms.v1_20_4.EntityHologram; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityType; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public final class ChunkLoaderBlockEntity extends BlockEntity implements ITileEntityChunkLoader { + + public static final Map chunkLoaderBlockEntityMap = new HashMap<>(); + + public final List holograms = new ArrayList<>(); + private final WChunkLoader chunkLoader; + private final Block loaderBlock; + private final ChunkLoaderBlockEntityTicker ticker; + private final ServerLevel serverLevel; + private final BlockPos blockPos; + private final String cachedPlacerName; + + private short currentTick = 20; + private short daysAmount, hoursAmount, minutesAmount, secondsAmount; + public boolean removed = false; + + public ChunkLoaderBlockEntity(ChunkLoader chunkLoader, ServerLevel serverLevel, BlockPos blockPos) { + super(BlockEntityType.COMMAND_BLOCK, blockPos, serverLevel.getBlockState(blockPos)); + + this.chunkLoader = (WChunkLoader) chunkLoader; + this.ticker = new ChunkLoaderBlockEntityTicker(this); + this.blockPos = blockPos; + this.serverLevel = serverLevel; + + setLevel(serverLevel); + + loaderBlock = serverLevel.getBlockState(blockPos).getBlock(); + + this.cachedPlacerName = Optional.ofNullable(this.chunkLoader.getWhoPlaced().getName()).orElse(""); + + if (!this.chunkLoader.isInfinite()) { + long timeLeft = chunkLoader.getTimeLeft(); + + daysAmount = (short) (timeLeft / 86400); + timeLeft = timeLeft % 86400; + + hoursAmount = (short) (timeLeft / 3600); + timeLeft = timeLeft % 3600; + + minutesAmount = (short) (timeLeft / 60); + timeLeft = timeLeft % 60; + + secondsAmount = (short) timeLeft; + } + + long chunkPosLong = ChunkPos.asLong(blockPos.getX() >> 4, blockPos.getZ() >> 4); + chunkLoaderBlockEntityMap.put(chunkPosLong, this); + + List hologramLines = this.chunkLoader.getHologramLines(); + + double currentY = blockPos.getY() + 1; + for (int i = hologramLines.size(); i > 0; i--) { + EntityHologram hologram = new EntityHologram(serverLevel, blockPos.getX() + 0.5, currentY, blockPos.getZ() + 0.5); + updateName(hologram, hologramLines.get(i - 1)); + serverLevel.addFreshEntity(hologram); + currentY += 0.23; + holograms.add(hologram); + } + } + + public void tick() { + if (removed || ++currentTick <= 20) + return; + + currentTick = 0; + + if (chunkLoader.isNotActive() || this.serverLevel.getBlockState(this.blockPos).getBlock() != loaderBlock) { + chunkLoader.remove(); + return; + } + + if (chunkLoader.isInfinite()) + return; + + List hologramLines = chunkLoader.getHologramLines(); + + int hologramsAmount = holograms.size(); + for (int i = hologramsAmount; i > 0; i--) { + EntityHologram hologram = holograms.get(hologramsAmount - i); + updateName(hologram, hologramLines.get(i - 1)); + } + + chunkLoader.tick(); + + if (!removed) { + secondsAmount--; + if (secondsAmount < 0) { + secondsAmount = 59; + minutesAmount--; + if (minutesAmount < 0) { + minutesAmount = 59; + hoursAmount--; + if (hoursAmount < 0) { + hoursAmount = 23; + daysAmount--; + } + } + } + } + } + + @Override + public Collection getHolograms() { + return Collections.unmodifiableList(holograms); + } + + @Override + public boolean isRemoved() { + return removed || super.isRemoved(); + } + + public ChunkLoaderBlockEntityTicker getTicker() { + return ticker; + } + + private void updateName(EntityHologram hologram, String line) { + hologram.setHologramName(line + .replace("{0}", this.cachedPlacerName) + .replace("{1}", daysAmount + "") + .replace("{2}", hoursAmount + "") + .replace("{3}", minutesAmount + "") + .replace("{4}", secondsAmount + "") + ); + } + +} + diff --git a/NMS/v1_20_4/src/main/java/com/bgsoftware/wildloaders/nms/v1_20_4/loader/ChunkLoaderBlockEntityTicker.java b/NMS/v1_20_4/src/main/java/com/bgsoftware/wildloaders/nms/v1_20_4/loader/ChunkLoaderBlockEntityTicker.java new file mode 100644 index 0000000..3023867 --- /dev/null +++ b/NMS/v1_20_4/src/main/java/com/bgsoftware/wildloaders/nms/v1_20_4/loader/ChunkLoaderBlockEntityTicker.java @@ -0,0 +1,30 @@ +package com.bgsoftware.wildloaders.nms.v1_20_4.loader; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.entity.TickingBlockEntity; + +public record ChunkLoaderBlockEntityTicker( + ChunkLoaderBlockEntity chunkLoaderBlockEntity) implements TickingBlockEntity { + + @Override + public void tick() { + chunkLoaderBlockEntity.tick(); + } + + @Override + public boolean isRemoved() { + return chunkLoaderBlockEntity.isRemoved(); + } + + @Override + public BlockPos getPos() { + return chunkLoaderBlockEntity.getBlockPos(); + } + + @Override + public String getType() { + return BlockEntityType.getKey(chunkLoaderBlockEntity.getType()) + ""; + } + +} diff --git a/settings.gradle b/settings.gradle index 8f16298..ce1e1bc 100644 --- a/settings.gradle +++ b/settings.gradle @@ -27,4 +27,7 @@ include 'NMS:v1_18' include 'NMS:v1_19' include 'NMS:v1_20_1' include 'NMS:v1_20_2' -include 'NMS:v1_20_3' \ No newline at end of file +include 'NMS:v1_20_3' +include 'NMS:v1_20_4' +findProject(':NMS:v1_20_4')?.name = 'v1_20_4' + diff --git a/src/main/java/com/bgsoftware/wildloaders/WildLoadersPlugin.java b/src/main/java/com/bgsoftware/wildloaders/WildLoadersPlugin.java index 3b05764..3685499 100644 --- a/src/main/java/com/bgsoftware/wildloaders/WildLoadersPlugin.java +++ b/src/main/java/com/bgsoftware/wildloaders/WildLoadersPlugin.java @@ -117,7 +117,8 @@ public final class WildLoadersPlugin extends JavaPlugin implements WildLoaders { new Pair<>(3337, "v1_19"), new Pair<>(3465, "v1_20_1"), new Pair<>(3578, "v1_20_2"), - new Pair<>(3700, "v1_20_3") + new Pair<>(3700, "v1_20_3"), + new Pair<>(3837, "v1_20_4") ); for (Pair versionData : versions) {