RyanHub – file viewer
filename: common/src/main/java/rearth/oritech/block/entity/arcane/SpawnerControllerBlockEntity.java
branch: 1.21
back to repo
package rearth.oritech.block.entity.arcane;

import net.minecraft.block.BlockState;
import net.minecraft.block.entity.BlockEntityTicker;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.SpawnReason;
import net.minecraft.entity.mob.MobEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.registry.Registries;
import net.minecraft.registry.RegistryWrapper;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.math.Vec3i;
import net.minecraft.world.World;
import org.jetbrains.annotations.Nullable;
import rearth.oritech.Oritech;
import rearth.oritech.client.init.ParticleContent;
import rearth.oritech.init.BlockContent;
import rearth.oritech.init.BlockEntitiesContent;
import rearth.oritech.init.TagContent;
import rearth.oritech.network.NetworkContent;
import rearth.oritech.util.ComparatorOutputProvider;

public class SpawnerControllerBlockEntity extends BaseSoulCollectionEntity implements BlockEntityTicker<SpawnerControllerBlockEntity>, ComparatorOutputProvider {
    
    public int maxSouls = 100_000;
    public int collectedSouls = 0;
    
    public EntityType<?> spawnedMob;
    public Entity renderedEntity;
    private boolean networkDirty;
    public boolean hasCage;
    private int lastComparatorOutput;
    private boolean redstonePowered;
    
    // loading cache only
    private Identifier loadedMob;
    
    // client only
    public float lastProgress = 0f;
    
    public SpawnerControllerBlockEntity(BlockPos pos, BlockState state) {
        super(BlockEntitiesContent.SPAWNER_CONTROLLER_BLOCK_ENTITY, pos, state);
    }
    
    @Override
    public void tick(World world, BlockPos pos, BlockState state, SpawnerControllerBlockEntity blockEntity) {
        
        if (world.isClient) return;
        
        if (loadedMob != null && spawnedMob == null) {
            loadEntityFromIdentifier(loadedMob);
            loadedMob = null;
            this.markDirty();
        }
        
        if (spawnedMob == null || !hasCage || redstonePowered) return;
        
        if (collectedSouls >= maxSouls && world.getTime() % 4 == 0) {
            spawnMob();
            updateComparator();
        }
        
        if (networkDirty) {
            updateNetwork();
            DeathListener.resetEvents();
        }
        
    }
    
    @Override
    protected void writeNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registryLookup) {
        super.writeNbt(nbt, registryLookup);
        nbt.putInt("souls", collectedSouls);
        nbt.putInt("maxSouls", maxSouls);
        nbt.putBoolean("cage", hasCage);
        nbt.putBoolean("redstone", redstonePowered);
        if (spawnedMob != null) {
            nbt.putString("spawnedMob", Registries.ENTITY_TYPE.getId(spawnedMob).toString());
        }
    }
    
    @Override
    protected void readNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registryLookup) {
        super.readNbt(nbt, registryLookup);
        hasCage = nbt.getBoolean("cage");
        maxSouls = nbt.getInt("maxSouls");
        collectedSouls = nbt.getInt("souls");
        redstonePowered = nbt.getBoolean("redstone");
        
        if (nbt.contains("spawnedMob"))
            loadedMob = Identifier.of(nbt.getString("spawnedMob"));
    }
    
    private void spawnMob() {
        // try and find a valid position within 10 attempts
        var spawnRange = 4;
        var requiredHeight = Math.round(spawnedMob.getHeight() + 0.5f);
        
        var targetPosition = findSpawnPosition(spawnRange, requiredHeight);
        
        if (targetPosition == null) return;
        networkDirty = true;
        
        spawnedMob.spawn((ServerWorld) world, targetPosition, SpawnReason.SPAWNER);
        collectedSouls -= maxSouls;
        ParticleContent.SOUL_USED.spawn(world, targetPosition.toCenterPos(), maxSouls);
        
    }
    
    private BlockPos findSpawnPosition(int spawnRange, int requiredHeight) {
        for (int i = 0; i < 10; i++) {
            var candidate = pos.add(world.random.nextBetween(-spawnRange, spawnRange), 3, world.random.nextBetween(-spawnRange, spawnRange));
            var foundFree = 0;
            for (int j = 0; j < 9; j++) {
                var state = world.getBlockState(candidate.down(j));
                if (state.isAir()) {
                    foundFree++;
                } else {
                    if (foundFree > requiredHeight) {
                        // found target
                        return candidate.down(j - 1);
                        
                    } else {
                        foundFree = 0;
                    }
                }
            }
        }
        
        return null;
    }
    
    private void updateNetwork() {
        networkDirty = false;
        
        if (spawnedMob != null)
            NetworkContent.MACHINE_CHANNEL.serverHandle(this).send(new NetworkContent.SpawnerSyncPacket(pos, Registries.ENTITY_TYPE.getId(spawnedMob), hasCage, collectedSouls, maxSouls));
    }
    
    @Override
    public void markDirty() {
        super.markDirty();
        this.networkDirty = true;
    }
    
    public void loadEntityFromIdentifier(Identifier identifier) {
        var newMob = Registries.ENTITY_TYPE.get(identifier);
        
        if (newMob != spawnedMob) {
            spawnedMob = newMob;
            renderedEntity = spawnedMob.create(world);
        }
    }
    
    @Override
    public boolean canAcceptSoul() {
        return collectedSouls < maxSouls;
    }
    
    private void updateComparator() {
        var progress = getComparatorOutput();
        if (lastComparatorOutput != progress) {
            lastComparatorOutput = progress;
            world.updateComparators(pos, getCachedState().getBlock());
        }
        
    }

    @Override
    public int getComparatorOutput() {
        if (spawnedMob == null || maxSouls == 0) return 0;
        
        return  (int) (collectedSouls / (float) maxSouls * 15);
    }
    
    public void setRedstonePowered(boolean active) {
        this.redstonePowered = active;
    }
    
    @Override
    public void onSoulIncoming(Vec3d source) {
        var distance = (float) source.distanceTo(pos.toCenterPos());
        collectedSouls++;
        
        var soulPath = pos.toCenterPos().subtract(source);
        var animData = new ParticleContent.SoulParticleData(soulPath, (int) getSoulTravelDuration(distance));
        
        ParticleContent.WANDERING_SOUL.spawn(world, source.add(0, 0.7f, 0), animData);
        networkDirty = true;
        updateComparator();
    }
    
    private int getSoulCost(int maxHp) {
        return (int) (Math.sqrt(maxHp) + 0.5f) * Oritech.CONFIG.spawnerCostMultiplier();
    }
    
    public void onEntitySteppedOn(Entity entity) {
        if (spawnedMob != null) return;
        
        if (entity instanceof MobEntity mobEntity) {
            
            if (mobEntity.getType().arch$holder().isIn(TagContent.SPAWNER_BLACKLIST)) {
                Oritech.LOGGER.debug("Ignored blacklisted entity for spawner: " + mobEntity.getType().arch$registryName());
                return;
            }
            
            spawnedMob = mobEntity.getType();
            
            
            networkDirty = true;
            maxSouls = getSoulCost((int) mobEntity.getMaxHealth());
            
            mobEntity.remove(Entity.RemovalReason.DISCARDED);
            reloadCage(null);
            
            this.markDirty();
        }
    }
    
    public void onBlockInteracted(PlayerEntity player) {
        
        if (spawnedMob == null) {
            player.sendMessage(Text.translatable("message.oritech.spawner.no_mob"));
            return;
        }
        
        networkDirty = true;
        
        reloadCage(player);
        
        if (hasCage)
            player.sendMessage(Text.translatable("tooltip.oritech.spawner.collected_souls", collectedSouls, maxSouls));
    }
    
    private void reloadCage(@Nullable PlayerEntity player) {
        var cageSize = new Vec3i(Math.round(spawnedMob.getWidth() * 2 + 0.5f), Math.round(spawnedMob.getHeight() + 0.5f), Math.round(spawnedMob.getWidth() * 2 + 0.5f));
        var offset = cageSize.getX() / 2;
        
        hasCage = true;
        
        for (int x = 0; x < cageSize.getX(); x++) {
            for (int y = 0; y < cageSize.getY(); y++) {
                for (int z = 0; z < cageSize.getZ(); z++) {
                    var candidate = pos.add(-offset + x, -y - 1, -offset + z);
                    
                    // block type is a placeholder
                    if (!world.getBlockState(candidate).getBlock().equals(BlockContent.SPAWNER_CAGE_BLOCK)) {
                        hasCage = false;
                        ParticleContent.DEBUG_BLOCK.spawn(world, Vec3d.of(candidate));
                    }
                    
                }
            }
        }
        
        if (!hasCage && player != null) {
            player.sendMessage(Text.translatable("message.oritech.spawner.no_cage"));
        }
        
        this.markDirty();
    }
}