Синхронизация TileEntity

Версия Minecraft
1.7.10
API
Forge
236
4
22
Здравствуйте всем, у меня есть не большой вопрос про синхронизацию. Я уже вроде как спрашивал на этом форуме про такое - мне посоветовали смотреть код контейнера печи из Minecraft и "копипастить". Что ж, всё работало, пока не.... Пока не понадобилось отправить int числа, которые > Short.MAX_VALUE. Когда я такие числа посылаю - в одиночной игре всё хорошо отображается, "придраться" не к чему. Но вот если речь идёт о мултиплеере - там уже моя переменная режется и становиться -2045, 1000, 1023 и т.д., а должна быть 1 - 10.000.000. Как мне избежать эту проблему и передать информацию игрокам в GUI? (Хотелось бы посылать данные только для тех, кто смотрит в GUI меню, как в печке контейнера через #detectAndSendChanges(), ибо не хочу баганный мод, который будет молниеносно поедать трафик Интернета + просаживать TPS.

Я конечно видел что-то про дескрипшен пакеты, но в других форумах мне сказали, что они будут посылаться каждый тик всем и вся - а это не очень хорошо, как уже сказал я выше - мне нужно только тем игрокам отправить, которые смотрят в GUI.

Заранее спасибо, если кто-то ответит на эту тему. Попутно задам вопрос, можно ли отправить String данные?
 
236
4
22
В чем проблема отправлять свой пакет только когда открыт контейнер и данные изменились?
Как мне понять, что данные изменились? Создать своё поле и проверять if(this.energy != tile.getEnergy()) { //Как-то шлю пакет }?
Если да - где мне найти экземпл отсылки пакетов, желательно чтобы он не был громоздким и не ясным?
 

tox1cozZ

aka Agravaine
8,454
598
2,890
Как мне понять, что данные изменились?
Самый простой вариант - да, вот так. Либо у себя в тайле в сеттерах переменных устанавливать булев changed на тру, в апдейте чекать если она тру то отправлять пакет, а после отправки ставить на фолс.
Если да - где мне найти экземпл отсылки пакетов
Миллион тем как на форуме, так и на зарубежных сайтах.
 

Icosider

Kotliner
Администратор
3,600
99
663
булев changed на тру
Зачем? Можно же сразу отправить пакет. Можно конечно собирать данные в пачку и по времени отсылать(твой вариант), но есть такие случаи когда данные на клиенте нужны прямо сейчас
 
236
4
22
Всё понятно. Правда скорее всего забить прийдётся, ибо решить этот вопрос нельзя. Чтобы работать с IMessage пакетами - мне нужно наследовать SimplePacket + обязательно должно быть наследование контейнера, ибо без него никак. А java ЯП, к сожанию, двойное наследование не доступно - не получится реализовать. Делать без SimplePacket класса - возможно и выйдет, но кода будет море, потом фиг поймёшь что это и зачем.

Система же получается работает как.. на клиенте в контейнере есть поле + метод каждый, который тик для тех, кто открыл контейнер посылает пакет с новым значением, если на сервере в тайле оно изменилось. Если отправить пакет ещё можно (через статик методы к примеру), то принять - вряд ли.
 

Icosider

Kotliner
Администратор
3,600
99
663
Зачем тебе наследоваться и от SimplePacket и от контейнера? Для пакет один класс, для контейнера другой или как вариант вложенный класс. Ну и если прям так хочется всё в одном классе, то делай имплементацию IMessage, IMessageHandler и реализуй toBuf, fromBuf и onMessageHandle. Всё, ничего сложного нет, можно даже без IMessageHandler обойтись и всё сразу в контейнер писать
 
236
4
22
Хотел сделать себе удобную систему для синка пакетов. Чтобы я мог делать примерно так:

Java:
public int energy; //Это в контейнере. На клиенте в GUI я смогу брать её значение и выводить

    @Override
    public void detectAndSendChanges() {
        super.detectAndSendChanges();
        for (int i = 0; i < this.crafters.size(); i++) {
            ICrafting icrafting = (ICrafting) this.crafters.get(i);
            if (this.energy != (int) tile.getEnergy()) {
                PacketManager.sendTileInfo(1/*ID переменной*/, (int) tile.getEnergy()/*новое значение*/);
            }
        }
    }

    @SideOnly(Side.CLIENT)
    @Override
    public void reciveTileInfo(int id, int value) {
        switch(id)
        {
            case 1:
            this.energy = value; // В соотвествии с ID я клиент переменную буду обновлять
            return;
        }
    }

Но походу это не возможно, либо же я самый тупой в мире. Если сейчас будет вопрос чем мне дефолтная система детекта не нравится - она не передаёт int в мультиплеер(режет его) и не умеет слать String.
 

tox1cozZ

aka Agravaine
8,454
598
2,890
Зачем? Можно же сразу отправить пакет. Можно конечно собирать данные в пачку и по времени отсылать(твой вариант), но есть такие случаи когда данные на клиенте нужны прямо сейчас
Зачем отсылать каждый тик пакет даже если данные вообще не менялись? Понимаю.
Ты меня не понял походу. Мы изменили любые данные - установили changed на тру в сеттере - у себя в контейнере в detectAndSendChanges(он вызывается каждый тик) проверяем changed у тайла и если тру - отсылаем все данные и ставим на фолс. Иначе же ты будешь слать пакет постоянно, даже если ничего не меняется, это глупо.
 
236
4
22
Зачем отсылать каждый тик пакет даже если данные вообще не менялись? Понимаю.
Вот и я о том же. Хочу только при обновлении данных и только игрокам которые открыли меню слать это. Но как мне слать большие числа я пока до сих пор не решил. Выше я писал варик, который хочу добиться - но походу это не возможно.
 
236
4
22
Эта тема потихоньку превращается в спамерскую. Может кто-то хотя бы скинет экземпл кода на отправку с сервера значение переменных тайла игрокам, которые смотрят в GUI (detectAndSendChanges()) (контейнер классом) и принятие этих данных? Для String, int (и желательно long)
 

will0376

Токсичная личность
2,059
55
574
омфг. выучи жабу уже, и заюзай интерфейс(по касту интерфейса кладёшь нужные переменные на клиенте в нужные места)(Если у тебя много блоков, требующие такого обновления... Хотя не думаю, что ты возьмешься за такое) - потом достаёшь что надо и откуда надо.
 
236
4
22
Немного подумав, разбив себе бошку об стену, я собрал код обработки и отправки пакета, опираясь на этот гайд. Однако он конечно же не сработал. При открытии GUI я получил целый миллион ошибок и кик с одиночкой игры с фатальной ошибкой. Помогите, в чём тут я уже напортачил? 2ой-3тий день пытаюсь решить это уже...

Контейнер механизма:
public class ContainerQuantumTank extends Container implements IMessageHandler<ContainerQuantumTank.QuantumTankPacket, IMessage>{
    public TileQuantumTank tileQuantumTank;
    public EntityPlayer player;

    public ContainerQuantumTank() {}
    
    public ContainerQuantumTank(TileQuantumTank tileQuantumTank,
                                EntityPlayer player) {
        super();
        this.tileQuantumTank = tileQuantumTank;
        this.player = player;

        this.addSlotToContainer(new SlotFluidIn(tileQuantumTank, tileQuantumTank.inventory, 0, 98, 17, true));
        this.addSlotToContainer(new SlotOutput(tileQuantumTank.inventory, 1,
                98, 53));
        this.addSlotToContainer(new SlotFake(tileQuantumTank.inventory, 2, 78, 52, false, false, 1));

        int i;

        for (i = 0; i < 3; ++i) {
            for (int j = 0; j < 9; ++j) {
                this.addSlotToContainer(new Slot(player.inventory, j + i * 9
                        + 9, 8 + j * 18, 84 + i * 18));
            }
        }

        for (i = 0; i < 9; ++i) {
            this.addSlotToContainer(new Slot(player.inventory, i, 8 + i * 18,
                    142));
        }
    }

    public boolean canInteractWith(EntityPlayer player) {

        if (!player.isEntityAlive()) return false;
        
        if (this.tileQuantumTank.getWorldObj().getTileEntity(tileQuantumTank.xCoord, tileQuantumTank.yCoord, tileQuantumTank.zCoord) != tileQuantumTank) {
            return false;
        } else {
            return player.getDistanceSq((double) tileQuantumTank.xCoord + 0.5D, (double) tileQuantumTank.yCoord + 0.5D, (double) tileQuantumTank.zCoord + 0.5D) <= 64.0D;
        }
    }
    
    private int fluid;
    
    @Override
    public void detectAndSendChanges() {
        super.detectAndSendChanges();
        for (int i = 0; i < this.crafters.size(); i++) {
            EntityPlayerMP player = (EntityPlayerMP) this.crafters.get(i);
            if(this.fluid != tileQuantumTank.tank.getFluidAmount())
            Core.NETWORK.sendTo(new QuantumTankPacket(tileQuantumTank.tank.getFluidAmount()), player);
        }
    }
    
    @Override
    public IMessage onMessage(QuantumTankPacket message, MessageContext ctx) {
        this.fluid = message.fluid;
        return null;
    }
    
    public int getFluid() {return this.fluid;}

    public class QuantumTankPacket implements IMessage{
        public int fluid;

        public QuantumTankPacket() {}

        public QuantumTankPacket(int a) {
            this.fluid = a;
        }

        @Override
        public void toBytes(ByteBuf buf) {
            buf.writeInt(this.fluid);
        }

        @Override
        public void fromBytes(ByteBuf buf) {
            this.fluid = buf.readInt();
        }

    }   
}


Преинициализация мода:
NETWORK = NetworkRegistry.INSTANCE.newSimpleChannel(ModInfo.MOD_ID);
        NETWORK.registerMessage(ContainerQuantumTank.class, QuantumTankPacket.class, 0, Side.CLIENT);

В главном классе есть
Java:
public static SimpleNetworkWrapper NETWORK;


Ошибка в консоле при открытии GUI:
io.netty.handler.codec.DecoderException: java.lang.InstantiationException: mymod.client.container.ContainerQuantumTank$QuantumTankPacket
at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:99) ~[MessageToMessageDecoder.class:?]
at io.netty.handler.codec.MessageToMessageCodec.channelRead(MessageToMessageCodec.java:111) ~[MessageToMessageCodec.class:?]
at io.netty.channel.DefaultChannelHandlerContext.invokeChannelRead(DefaultChannelHandlerContext.java:337) [DefaultChannelHandlerContext.class:?]
at io.netty.channel.DefaultChannelHandlerContext.fireChannelRead(DefaultChannelHandlerContext.java:323) [DefaultChannelHandlerContext.class:?]
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:785) [DefaultChannelPipeline.class:?]
at io.netty.channel.embedded.EmbeddedChannel.writeInbound(EmbeddedChannel.java:169) [EmbeddedChannel.class:?]
at cpw.mods.fml.common.network.internal.FMLProxyPacket.processPacket(FMLProxyPacket.java:86) [FMLProxyPacket.class:?]
at net.minecraft.network.NetworkManager.processReceivedPackets(NetworkManager.java:241) [NetworkManager.class:?]
at net.minecraft.client.multiplayer.PlayerControllerMP.updateController(PlayerControllerMP.java:317) [PlayerControllerMP.class:?]
at net.minecraft.client.Minecraft.runTick(Minecraft.java:1693) [Minecraft.class:?]
at net.minecraft.client.Minecraft.runGameLoop(Minecraft.java:1039) [Minecraft.class:?]
at net.minecraft.client.Minecraft.run(Minecraft.java:962) [Minecraft.class:?]
at net.minecraft.client.main.Main.main(Main.java:164) [Main.class:?]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_265]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_265]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_265]
at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_265]
at net.minecraft.launchwrapper.Launch.launch(Launch.java:135) [launchwrapper-1.12.jar:?]
at net.minecraft.launchwrapper.Launch.main(Launch.java:28) [launchwrapper-1.12.jar:?]
at net.minecraftforge.gradle.GradleStartCommon.launch(Unknown Source) [start/:?]
at GradleStart.main(Unknown Source) [start/:?]
Caused by: java.lang.InstantiationException: mymod.client.container.ContainerQuantumTank$QuantumTankPacket
at java.lang.Class.newInstance(Class.java:427) ~[?:1.8.0_265]
at cpw.mods.fml.common.network.FMLIndexedMessageToMessageCodec.decode(FMLIndexedMessageToMessageCodec.java:75) ~[FMLIndexedMessageToMessageCodec.class:?]
at cpw.mods.fml.common.network.FMLIndexedMessageToMessageCodec.decode(FMLIndexedMessageToMessageCodec.java:17) ~[FMLIndexedMessageToMessageCodec.class:?]
at io.netty.handler.codec.MessageToMessageCodec[imath]2.decode(MessageToMessageCodec.java:81) ~[MessageToMessageCodec[/imath]2.class:?]
at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:89) ~[MessageToMessageDecoder.class:?]
... 20 more
Caused by: java.lang.NoSuchMethodException: mymod.client.container.ContainerQuantumTank$QuantumTankPacket.<init>()
at java.lang.Class.getConstructor0(Class.java:3082) ~[?:1.8.0_265]
at java.lang.Class.newInstance(Class.java:412) ~[?:1.8.0_265]
at cpw.mods.fml.common.network.FMLIndexedMessageToMessageCodec.decode(FMLIndexedMessageToMessageCodec.java:75) ~[FMLIndexedMessageToMessageCodec.class:?]
at cpw.mods.fml.common.network.FMLIndexedMessageToMessageCodec.decode(FMLIndexedMessageToMessageCodec.java:17) ~[FMLIndexedMessageToMessageCodec.class:?]
at io.netty.handler.codec.MessageToMessageCodec[imath]2.decode(MessageToMessageCodec.java:81) ~[MessageToMessageCodec[/imath]2.class:?]
at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:89) ~[MessageToMessageDecoder.class:?]
... 20 more
[10:12:01] [Client thread/ERROR] [FML]: SimpleChannelHandlerWrapper exception


В гугле на эти ошибки пишет, что нужен пустой конструктор. Я его создал - ошибки никуда не убрались, так же вылетает. Помогите, пожалуйста.
 

will0376

Токсичная личность
2,059
55
574
2ой-3тий день пытаюсь решить это уже...
При этом никого не хотя даже слушать. текущий контейнер на стороне клиента хранится в mc. как и гуи. Пакеты можешь слать какие хочешь, на клиенте чекаешь инстанс гуи и кастуешься, заливая инфу.
Или еще круче - сразу в клиентские переменные в контейнере, также через каст.

Твою проблему можно решить как минимум 2-3 способами, но тебе на них насрать.
 

tox1cozZ

aka Agravaine
8,454
598
2,890
Ломбоком, надеюсь, пользуешься. Если нет - крайне рекомендую.
Базовый класс пакета:
Java:
import cpw.mods.fml.common.network.simpleimpl.IMessage;
import cpw.mods.fml.common.network.simpleimpl.IMessageHandler;
import cpw.mods.fml.common.network.simpleimpl.MessageContext;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import io.netty.buffer.ByteBuf;
import lombok.extern.log4j.Log4j2;
import net.minecraft.client.Minecraft;
import net.minecraft.client.entity.EntityClientPlayerMP;
import net.minecraft.client.multiplayer.WorldClient;
import net.minecraft.client.network.NetHandlerPlayClient;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.network.NetHandlerPlayServer;
import net.minecraft.network.PacketBuffer;
import net.minecraft.world.WorldServer;

import java.io.IOException;

@Log4j2
public abstract class PacketBase implements IMessage, IMessageHandler<PacketBase, PacketBase>{

    @Override
    public final void fromBytes(ByteBuf buf){
        try{
            readPacketData(new PacketBuffer(buf));
        }catch(IOException e){
            log.error("Problem with reading packet data", e);
        }
    }

    @Override
    public final void toBytes(ByteBuf buf){
        try{
            writePacketData(new PacketBuffer(buf));
        }catch(IOException e){
            log.error("Problem with writing packet data", e);
        }
    }

    public abstract void writePacketData(PacketBuffer buf) throws IOException;

    public abstract void readPacketData(PacketBuffer buf) throws IOException;

    @SideOnly(Side.CLIENT)
    public PacketBase handleClientSide(NetHandlerPlayClient netHandler, Minecraft mc, WorldClient world, EntityClientPlayerMP player){
        return null;
    }

    @SideOnly(Side.SERVER)
    public PacketBase handleServerSide(NetHandlerPlayServer netHandler, WorldServer world, EntityPlayerMP player){
        return null;
    }

    @Override
    public final PacketBase onMessage(PacketBase message, MessageContext ctx){
        if(ctx.side.isServer()){
            return onServerMessage(message, ctx);
        }else{
            return onClientMessage(message, ctx);
        }
    }

    @SideOnly(Side.SERVER)
    public final PacketBase onServerMessage(PacketBase message, MessageContext ctx){
        NetHandlerPlayServer netHandler = ctx.getServerHandler();
        return message.handleServerSide(netHandler, netHandler.playerEntity.getServerForPlayer(), netHandler.playerEntity);
    }

    @SideOnly(Side.CLIENT)
    public final PacketBase onClientMessage(PacketBase message, MessageContext ctx){
        Minecraft mc = Minecraft.getMinecraft();
        return message.handleClientSide(ctx.getClientHandler(), mc, mc.theWorld, mc.thePlayer);
    }
}
Регистрация пакетов (YourModNetwork.initialize() вызвать в preInit):
Java:
import cpw.mods.fml.common.network.simpleimpl.IMessage;
import cpw.mods.fml.common.network.simpleimpl.SimpleNetworkWrapper;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import lombok.extern.log4j.Log4j2;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.world.WorldServer;

import java.util.Set;

@Log4j2
public class YourModNetwork{

    public static SimpleNetworkWrapper packetSender;
    private static int nextMessageId = 0;

    public static void initialize(){
        packetSender = new SimpleNetworkWrapper("НАЗВАНИЕ КАНАЛА (НЕ ДЛИННЕЕ 24 СИМВОЛОВ, ВРОДЕ)");
        registerPackets();
    }

    private static void registerPackets(){
        registerMessage(PacketSyncTileEntity.class, Side.CLIENT);
    }

    @SideOnly(Side.SERVER)
    public static void sendToTrackingPlayers(IMessage message, Entity entity){
        Set<EntityPlayer> players = ((WorldServer)entity.worldObj).getEntityTracker().getTrackingPlayers(entity);
        players.forEach(player -> packetSender.sendTo(message, (EntityPlayerMP)player));
    }

    private static void registerMessage(Class<? extends PacketBase> packet, Side toSide){
        try{
            packetSender.registerMessage(packet.newInstance(), packet, nextMessageId++, toSide);
        }catch(InstantiationException | IllegalAccessException e){
            log.error("Failed to register packet", e);
        }
    }
}
Пример пакета:
Java:
@AllArgsConstructor
@NoArgsConstructor
public class PacketSyncTileEntity extends PacketBase{

    private String someString;
    private int someInt;

    @Override
    public void writePacketData(PacketBuffer buf) throws IOException{
        ByteBufUtils.writeUTF8String(buf, someString);
        buf.writeVarIntToBuffer(someInt);
    }

    @Override
    public void readPacketData(PacketBuffer buf) throws IOException{
        someString = ByteBufUtils.readUTF8String(buf);
        someInt = buf.readVarIntFromBuffer();
    }

    @Override
    @SideOnly(Side.CLIENT)
    public PacketBase handleClientSide(NetHandlerPlayClient netHandler, Minecraft mc, WorldClient world, EntityClientPlayerMP player){
        if(mc.thePlayer.openContainer instanceof YourContainer){
            YourTileEntity tile = ((YourContainer)mc.thePlayer.openContainer).getTileEntity();
            tile.setSomeString(someString);
            tile.setSomeInt(someInt);
        }
        return null;
    }
}
В контейнере:
Java:
@Override
    public void detectAndSendChanges() {
        super.detectAndSendChanges();
        if(this.fluid != tileQuantumTank.tank.getFluidAmount()){
            for (int i = 0; i < this.crafters.size(); i++) {
                EntityPlayerMP player = (EntityPlayerMP) this.crafters.get(i);
                YourModNetwork.packetSender.sendTo(new PacketSyncTileEntity("Example String", tileQuantumTank.tank.getFluidAmount()), player);
            }
            this.fluid = tileQuantumTank.tank.getFluidAmount();
        }
    }
 
Сверху