Версия Minecraft
1.12.2
API
Forge
198
1
24
Как можно сделать моба, способного грузить чанк, в котором он находится, и бродить по карте независимо от того, есть ли рядом игрок?
 

tox1cozZ

aka Agravaine
8,456
598
2,893
Всё довольно просто. Вот только я не уверен будет ли моб адекватно бродить по миру если грузит всего один чанк. Если нет - сделай чтобы грузил 3х3 чанка, такое вот тебе домашнее задание.

Пишем такой волшебный классик:
Java:
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.util.SoundCategory;
import net.minecraft.util.SoundEvent;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.world.IWorldEventListener;
import net.minecraft.world.World;
import net.minecraftforge.common.ForgeChunkManager;
import net.minecraftforge.common.ForgeChunkManager.Ticket;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.entity.EntityEvent;
import net.minecraftforge.event.world.WorldEvent;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;

import javax.annotation.Nullable;
import java.util.List;
import java.util.WeakHashMap;

@SideOnly(Side.SERVER)
public class EntityChunkLoadingManager implements ForgeChunkManager.LoadingCallback{

    private final Object mod;
    private final WeakHashMap<ChunkLoadingEntity, Ticket> tickets = new WeakHashMap<>();
    private final WorldAccessor worldAccessor = new WorldAccessor();

    public EntityChunkLoadingManager(Object modInstance){
        mod = modInstance;
        MinecraftForge.EVENT_BUS.register(this);
        ForgeChunkManager.setForcedChunkLoadingCallback(mod, this);
    }

    @SubscribeEvent
    public void onEnteringChunk(EntityEvent.EnteringChunk e){
        if(e.getEntity() instanceof ChunkLoadingEntity && !e.getEntity().isDead){
            ChunkLoadingEntity loadingEntity = (ChunkLoadingEntity)e.getEntity();
            ForgeChunkManager.Ticket ticket = getTicket(loadingEntity);
            if(ticket == null){
                ticket = ForgeChunkManager.requestTicket(mod, e.getEntity().world, ForgeChunkManager.Type.ENTITY);
                if(ticket != null){
                    ticket.bindEntity(e.getEntity());
                    addTicket(loadingEntity, ticket);
                    loadingEntity.onAddedTicket(ticket);
                }
            }
            if(ticket != null){
                if(e.getOldChunkX() != 0 && e.getOldChunkZ() != 0){
                    ForgeChunkManager.unforceChunk(ticket, new ChunkPos(e.getOldChunkX(), e.getOldChunkZ()));
                }
                ForgeChunkManager.forceChunk(ticket, new ChunkPos(e.getNewChunkX(), e.getNewChunkZ()));
            }
        }
    }

    @SubscribeEvent
    public void onWorldLoad(WorldEvent.Load e){
        e.getWorld().removeEventListener(worldAccessor);
        e.getWorld().addEventListener(worldAccessor);
    }

    @Override
    public void ticketsLoaded(List<Ticket> tickets, World world){
        for(ForgeChunkManager.Ticket ticket : tickets){
            boolean saved = false;
            Entity entity = ticket.getEntity();
            if(entity instanceof ChunkLoadingEntity && !entity.isDead){
                saved = true;
                addTicket((ChunkLoadingEntity)entity, ticket);
            }
            if(!saved){
                ForgeChunkManager.releaseTicket(ticket);
            }
        }
    }

    public ForgeChunkManager.Ticket removeTicket(ChunkLoadingEntity entity){
        ForgeChunkManager.Ticket ticket = tickets.get(entity);
        if(ticket != null){
            ForgeChunkManager.releaseTicket(ticket);
        }
        tickets.remove(entity);
        return ticket;
    }

    public void addTicket(ChunkLoadingEntity entity, ForgeChunkManager.Ticket ticket){
        if(tickets.get(entity) != null){
            removeTicket(entity);
        }
        tickets.put(entity, ticket);
    }

    public ForgeChunkManager.Ticket getTicket(ChunkLoadingEntity entity){
        return tickets.get(entity);
    }

    private class WorldAccessor implements IWorldEventListener{

        @Override
        public void notifyBlockUpdate(World worldIn, BlockPos pos, IBlockState oldState, IBlockState newState, int flags){

        }

        @Override
        public void notifyLightSet(BlockPos pos){

        }

        @Override
        public void markBlockRangeForRenderUpdate(int x1, int y1, int z1, int x2, int y2, int z2){

        }

        @Override
        public void playSoundToAllNearExcept(@Nullable EntityPlayer player, SoundEvent soundIn, SoundCategory category, double x, double y, double z, float volume, float pitch){

        }

        @Override
        public void playRecord(SoundEvent soundIn, BlockPos pos){

        }

        @Override
        public void spawnParticle(int particleID, boolean ignoreRange, double xCoord, double yCoord, double zCoord, double xSpeed, double ySpeed, double zSpeed, int... parameters){

        }

        @Override
        public void spawnParticle(int id, boolean ignoreRange, boolean minimiseParticleLevel, double x, double y, double z, double xSpeed, double ySpeed, double zSpeed, int... parameters){

        }

        @Override
        public void onEntityAdded(Entity entityIn){

        }

        @Override
        public void onEntityRemoved(Entity entity){
            if(entity instanceof ChunkLoadingEntity){
                ChunkLoadingEntity loadingEntity = (ChunkLoadingEntity)entity;
                Ticket ticket = removeTicket(loadingEntity);
                loadingEntity.onRemovedTicket(ticket);
            }
        }

        @Override
        public void broadcastSound(int soundID, BlockPos pos, int data){

        }

        @Override
        public void playEvent(EntityPlayer player, int type, BlockPos blockPosIn, int data){

        }

        @Override
        public void sendBlockBreakProgress(int breakerId, BlockPos pos, int progress){

        }
    }
}
И простой интерфейсик для удобства:
Java:
import net.minecraftforge.common.ForgeChunkManager;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;

public interface ChunkLoadingEntity{

    @SideOnly(Side.SERVER)
    void onAddedTicket(ForgeChunkManager.Ticket ticket);

    @SideOnly(Side.SERVER)
    void onRemovedTicket(ForgeChunkManager.Ticket ticket);
}
Расширяем этот интерфейс в своем энтити и реализуем два метода. Первый вызывается когда к сущности привязывается тикет, второй же когда тикет удаляется (например, сущность умерла или еще как-то исчезла из мира):
implements ChunkLoadingEntity
И наконец регаем наш магический классик где-то в preInit на серверной стороне:
Java:
@SideOnly(Side.SERVER)
private EntityChunkLoadingManager entityChunkLoadingManager;

@EventHandler
@SideOnly(Side.SERVER)
public void preInitServer(FMLPreInitializationEvent e){
    entityChunkLoadingManager = new EntityChunkLoadingManager(INSTANCE); // INSTANCE - наш инстанс мода (на котором весит @Instance)
}
 
1,159
38
544
Очень интересный вопрос на самом деле. Попробуй посмотреть как в RailCraft'е вагонетка с якорем мира удерживает чанк загруженным. Или же карьер из BuildCraft'а. Как решишь проблему - пожалуйста поделись со всеми, уж очень интересно.
 
198
1
24
С загрузкой чанков всё не так гладко. То есть, чанки грузит, например, за 3К блоков от игрока, но только если после рестарта это место посетить. Думаю, надо при остановке сервера сохранять последние известные координаты моба и при запуске загружать этот чанк, а дальше моб уже сам.
 
Последнее редактирование модератором:

sk9zist :l

Исправился
981
18
157
Если этот моб не один босс на мир, а обычный моб, который будет ходить по миру - то это не лучшая идея. Я бы так не делал.
 
7,099
324
1,510
А что делать в реализации onAddedTicket/onRemovedTicket? Это просто уведомление энтити о отгрузке? Можно там ничего не делать?
 
198
1
24
Всё довольно просто. Вот только я не уверен будет ли моб адекватно бродить по миру если грузит всего один чанк. Если нет - сделай чтобы грузил 3х3 чанка, такое вот тебе домашнее задание.
Всё сложнее - моб должен не только бродить, но и телепортироваться дальше, чем радиус загрузки чанков. Это чтоб нельзя было его поймать и использовать как чанклоадер.
 
7,099
324
1,510
моб должен не только бродить, но и телепортироваться дальше, чем радиус загрузки чанков
Из домашки по 3*3 чанка сделай прогрузку чанка в который моб намеревается перейти, тогда он будет свободен от радиуса
~~~
Мод публичный будет?
 
198
1
24
Так, похоже, что моб тупо деспавнится. В т.ч. просто когда бродит вокруг, когда его видно...
А ещё он дохнет...
Разьве вот этот код не должен сделать моба бессмертным?

Java:
protected void applyEntityAttributes() {
        super.applyEntityAttributes();
        this.getEntityAttribute(SharedMonsterAttributes.FOLLOW_RANGE).setBaseValue(35.0D);
        this.getEntityAttribute(SharedMonsterAttributes.MOVEMENT_SPEED).setBaseValue(0.50D);
        this.enablePersistence();
        this.setEntityInvulnerable(true);
}
(строки 5 и 6)
 
Последнее редактирование:
198
1
24
Похоже, этот код не работает (поместил в главный класс):
@SideOnly(Side.SERVER)
private EntityChunkLoadingManager entityChunkLoadingManager;

@EventHandler
@SideOnly(Side.SERVER)
public void preInitServer(FMLPreInitializationEvent e){
    entityChunkLoadingManager = new EntityChunkLoadingManager(INSTANCE); // INSTANCE - наш инстанс мода (на котором весит @Instance)
}
То есть, preInitServer() вообще не выполняется.
 
Сверху