package dev.okenote.entities.spawner.timer;

import dev.okenote.JediCraft;
import dev.okenote.entities.spawner.Spawner;
import dev.okenote.managers.ChatManager;
import lombok.Getter;
import net.kyori.adventure.text.Component;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.EntityType;
import net.minestom.server.entity.metadata.other.ArmorStandMeta;
import net.minestom.server.instance.Instance;

import java.util.ArrayList;
import java.util.List;

public class SimpleSpawnerTimer implements SpawnerTimer {

    private final Spawner spawner;
    private final String entityName;
    private final Pos timerLocation;
    private final Instance timerInstance;

    private Entity entityNameStand;
    private Entity timeRemainingStand;

    public final List<Entity> stands = new ArrayList<>();

    @Getter
    private boolean visible;

    public SimpleSpawnerTimer(Spawner spawner, Instance timerInstance, Pos timerLocation, String entityName) {
        this.spawner = spawner;
        this.timerInstance = timerInstance;
        this.timerLocation = timerLocation;
        this.entityName = entityName;

        timerInstance.getEntities().stream()
                .filter(entity -> entity.getEntityType() == EntityType.ARMOR_STAND &&
                        entity.getPosition().distance(timerLocation) < 30)
                .forEach(Entity::remove);
    }

    @Override
    public void update() {
        ChatManager chatManager = JediCraft.getJediCraft().getManagerLayer().getChatManager();
        long secondsToRespawn = (spawner.getSpawnInterval() - System.currentTimeMillis() / 1000L + spawner.getDeathTime());

        if (secondsToRespawn > 0) {
            show();
            int minutesToRespawn = (int) secondsToRespawn / 60;
            int hoursToRespawn = minutesToRespawn / 60;
            String respawnTimer;

            if (hoursToRespawn > 0) {
                minutesToRespawn -= hoursToRespawn * 60;
                respawnTimer = String.format("%d %s %d %s", hoursToRespawn,
                        chatManager.transformByCount(hoursToRespawn, "часа", "часов", "часов"),
                        minutesToRespawn, chatManager.transformByCount(minutesToRespawn, "минуты", "минут", "минут"));
            } else if (minutesToRespawn > 0) {
                respawnTimer = String.format("%s %s", minutesToRespawn, chatManager.transformByCount(minutesToRespawn, "минуты", "минут", "минут"));
            } else {
                respawnTimer = String.format("%d %s", secondsToRespawn, chatManager.transformByCount((int) secondsToRespawn, "секунда", "секунды", "секунд"));
            }

            timeRemainingStand.getEntityMeta().setCustomName(Component.text(respawnTimer));
        } else {
            hide();
        }
    }

    @Override
    public void show() {
        if (!visible) {
            forceHide();
            Pos standSpawnLocation = timerLocation;

            entityNameStand = new Entity(EntityType.ARMOR_STAND);
            entityNameStand.setInstance(timerInstance, new Pos(standSpawnLocation.x(), standSpawnLocation.y() + 0.4, standSpawnLocation.z()));

            ArmorStandMeta nameMeta = (ArmorStandMeta) entityNameStand.getEntityMeta();
            nameMeta.setHasNoGravity(true);
            nameMeta.setCustomNameVisible(true);
            nameMeta.setSmall(true);
            nameMeta.setInvisible(true);
            nameMeta.setCustomName(Component.text(entityName));
            stands.add(entityNameStand);

            timeRemainingStand = new Entity(EntityType.ARMOR_STAND);
            timeRemainingStand.setInstance(timerInstance, new Pos(standSpawnLocation.x(), standSpawnLocation.y() - 0.4, standSpawnLocation.z()));

            ArmorStandMeta timerMeta = (ArmorStandMeta) timeRemainingStand.getEntityMeta();
            timerMeta.setHasNoGravity(true);
            timerMeta.setCustomNameVisible(true);
            timerMeta.setSmall(true);
            timerMeta.setInvisible(true);
            timerMeta.setCustomName(Component.text(""));
            stands.add(timeRemainingStand);
            visible = true;
        }
    }

    @Override
    public void hide() {
        if (visible) {
            forceHide();
        }
    }

    private void forceHide() {
        if (entityNameStand != null) {
            stands.remove(entityNameStand);
            entityNameStand.remove();
            entityNameStand = null;
        }

        if (timeRemainingStand != null) {
            stands.remove(timeRemainingStand);
            timeRemainingStand.remove();
            timeRemainingStand = null;
        }

        visible = false;
    }
}

package dev.okenote.entities.spawner.timer;

public interface SpawnerTimer {

    void update();

    void show();

    void hide();
}

package dev.okenote.entities.spawner;

import dev.okenote.JediCraft;
import dev.okenote.database.Entities;
import dev.okenote.entities.JediEntity;
import dev.okenote.entities.spawner.timer.SimpleSpawnerTimer;
import dev.okenote.entities.spawner.timer.SpawnerTimer;
import lombok.Data;
import lombok.Getter;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.Entity;
import net.minestom.server.instance.Instance;

import java.util.ArrayList;
import java.util.List;

@Data
public class DefaultSpawner implements Spawner {

    @Getter
    protected final Pos spawnLocation;

    @Getter
    protected final Instance spawnInstance;

    protected final JediEntity entityType;

    protected final long spawnInterval;

    protected final int maximumFollowRange;

    protected final String respawnDelayName;

    protected Entity currentEntity;

    protected long deathTime;

    protected List<SpawnerTimer> timers = new ArrayList<>();

    protected Entities spawnerModel;

    public DefaultSpawner(JediEntity entityType, Pos spawnLocation, Instance spawnInstance, long spawnInterval, int maximumFollowRange, String respawnDelayName, Entities spawnerModel) {
        this.entityType = entityType;
        this.spawnLocation = spawnLocation;
        this.spawnInstance = spawnInstance;
        this.spawnInterval = spawnInterval;
        this.maximumFollowRange = maximumFollowRange;
        this.respawnDelayName = respawnDelayName;
        this.spawnerModel = spawnerModel;

        int timeToRespawn = (int) spawnerModel.getTimeToRespawn();
        deathTime = timeToRespawn > 0 ? timeToRespawn + System.currentTimeMillis() / 1000L - spawnInterval : -1;

        if (respawnDelayName != null) {
            timers.add(new SimpleSpawnerTimer(this, spawnInstance, spawnLocation, respawnDelayName));
        }
    }

    @Override
    public Entity spawn() {
        Pos location = getSpawnLocation();
        Instance instance = getSpawnInstance();
        reset();
        deathTime = 0L;
        return JediEntity.entitySpawn(entityType, instance, location.add(0.0D, 1.0D, 0.0D), this);
    }

    @Override
    public void tick() {
        if (currentEntity == null || currentEntity.isRemoved()) {
            if (deathTime == 0L) {
                deathTime = System.currentTimeMillis() / 1000L;
            }

            long timeToRespawn = spawnInterval - System.currentTimeMillis() / 1000L + deathTime;
            if (timeToRespawn <= 0) {
                currentEntity = spawn();
            }
        } else {
            Pos entityLocation = currentEntity.getPosition();
            if (getSpawnLocation().distanceSquared(entityLocation) > maximumFollowRange * maximumFollowRange) {
                currentEntity.teleport(getSpawnLocation());
            }
        }
        timers.forEach(SpawnerTimer::update);
    }

    @Override
    public void reset() {
        if (currentEntity != null && !currentEntity.isRemoved()) {
            currentEntity.remove();
        }
        deathTime = -1L;
    }

    public void saveTimeToRespawn() {
        spawnerModel.setTimeToRespawn(spawnInterval - System.currentTimeMillis() / 1000L + deathTime);
        JediCraft.getJediCraft().getDataBaseLayer().getEntitiesDao().save(spawnerModel);
    }
}

package dev.okenote.entities.spawner;

import dev.okenote.JediCraft;
import dev.okenote.database.Entities;
import dev.okenote.entities.JediEntity;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.instance.Instance;

import java.util.function.Function;

@Getter
@RequiredArgsConstructor
public enum JediSpawnerType {

    DEFAULT_SPAWNER(spawnerDataBase -> {
        JediEntity entityType;

        try {
            entityType = spawnerDataBase.getEntity();
        } catch (IllegalArgumentException e) {
            System.out.println("Unable to load spawner!");
            System.out.println("Error: Unknown entity type");
            return null;
        }

        Instance instance = JediCraft.getJediCraft().getWorlds().get(spawnerDataBase.getPlanetName());
        Pos spawnLocation = new Pos(spawnerDataBase.getX(), spawnerDataBase.getY(),
                spawnerDataBase.getZ(), spawnerDataBase.getYaw(), spawnerDataBase.getPitch());

        long spawnInterval = spawnerDataBase.getInterval();

        if (spawnInterval == -1) {
            System.out.println("Unable to load spawner!");
            System.out.println("Error: Spawn interval is not set");
            return null;
        }

        String respawnDelayName = spawnerDataBase.getRespawnDelayName();
        return new DefaultSpawner(entityType, spawnLocation, instance, spawnInterval, 64, respawnDelayName, spawnerDataBase);
    });

    private final Function<Entities, Spawner> spawnerLoadFunction;
}

package dev.okenote.entities.spawner;

import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.Entity;
import net.minestom.server.instance.Instance;

public interface Spawner {

    Pos getSpawnLocation();

    Instance getSpawnInstance();

    long getSpawnInterval();

    long getDeathTime();

    Entity spawn();

    void tick();

    void reset();

    default void saveTimeToRespawn() {
    }
}

package dev.okenote.entities.spawner;

import dev.okenote.JediCraft;
import dev.okenote.database.Entities;
import lombok.Getter;
import lombok.SneakyThrows;
import net.minestom.server.MinecraftServer;

import java.time.Duration;
import java.util.HashSet;
import java.util.Set;

@Getter
public class SpawnersManager {

    private final Set<Spawner> spawnerSet = new HashSet<>();

    public SpawnersManager() {
        loadSpawners();
        MinecraftServer.getSchedulerManager().buildTask(() -> spawnerSet.forEach(Spawner::tick))
                .delay(Duration.ofMillis(120L)).repeat(Duration.ofMillis(20L)).schedule();
    }

    @SneakyThrows
    private void loadSpawners() {
        for (Entities spawnerModel : JediCraft.getJediCraft().getDataBaseLayer().getEntitiesDao().getAll()) {
            if (spawnerModel != null) {
                Spawner spawner = JediSpawnerType.DEFAULT_SPAWNER.getSpawnerLoadFunction().apply(spawnerModel);

                if (spawner != null) {
                    spawnerSet.add(spawner);
                }
            }
        }
    }
}

package dev.okenote.entities;

import dev.okenote.entities.bosses.JangoFett;
import dev.okenote.entities.spawner.Spawner;
import lombok.*;
import lombok.experimental.FieldDefaults;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.Entity;
import net.minestom.server.instance.Instance;

@Getter
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
public enum JediEntity {

    CLONE(JangoFett.class);

    Class<? extends JediEntityCreature> custom;
    
    public static Entity entitySpawn(JediEntity entityType, Instance instance, @NonNull Pos location, Spawner spawner) {
        try {
            Entity mob;
            mob = entityType.getCustom().getConstructor(new Class[]{Spawner.class}).newInstance(spawner);
            mob.setInstance(instance, location);
            return mob;
        } catch (Throwable t) {
            throw new RuntimeException("Unable to spawn NMS-entity", t);
        }
    }
}

Это всё полностью работоспособные классы, но мне не нравится их реализация, переделай эти классы, обнови методы, сократи код и оптимизируй.