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

sk9zist :l

Исправился
981
18
157
Если этот моб не один босс на мир, а обычный моб, который будет ходить по миру - то это не лучшая идея. Я бы так не делал.
 
1,159
38
544
Очень интересный вопрос на самом деле. Попробуй посмотреть как в RailCraft'е вагонетка с якорем мира удерживает чанк загруженным. Или же карьер из BuildCraft'а. Как решишь проблему - пожалуйста поделись со всеми, уж очень интересно.
 

tox1cozZ

aka Agravaine
8,455
598
2,892
Всё довольно просто. Вот только я не уверен будет ли моб адекватно бродить по миру если грузит всего один чанк. Если нет - сделай чтобы грузил 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)
}
 
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() вообще не выполняется.
 
Сверху