Jump to content

Recommended Posts

Posted (edited)

Hey everyone,

all the info I could google on custom entities pertains to living entities - but I need to use a straight extension of Entity for my multiblock structures. I managed to get it all working just fine, except the entity doesn't get sent out to the client. The client is clearly in range (I set the tracking range to 32, with the player being within at most 5 blocks of the entity). The entities position is correct on the server, and the register method gets called just fine too. Here's all the code (nevermind the registration in EntityBoilerMultiblock, a breakpoint there does indeed trigger so the way I call it is irrelevant as far as this is concerned). I also annotated stuff that might be confusing to people unfamiliar with Kotlin, the rest should be pretty much self explanatory.

 

The SyncedEntity class:

// ?. = null safe version of . | ?: fallback if null

abstract class SyncedEntity(world: World) : Entity(world), ISyncMapProvider, IEntityAdditionalSpawnData {

    override val syncMap: SyncMapEntity<SyncedEntity> = SyncMapEntity(this)

    init { //constructor
        createSyncedFields()
        SyncObjectScanner.instance.registerAllFields(syncMap, this)
    }

    override fun writeEntityToNBT(compound: NBTTagCompound) {
        super.writeToNBT(compound)
        syncMap.writeToNBT(compound)
    }

    override fun readEntityFromNBT(compound: NBTTagCompound) {
        super.readFromNBT(compound)
        syncMap.readFromNBT(compound)
    }

    protected abstract fun createSyncedFields()
    fun addSyncedObject(name: String, obj: ISyncableObject) { syncMap.put(name, obj) }
    fun sync() = syncMap.sync()

    override fun readSpawnData(additionalData: ByteBuf) {
        syncMap.readFromStream(DataInputStream(ByteBufInputStream(additionalData)))
    }

    override fun writeSpawnData(buffer: ByteBuf) {
        syncMap.writeToStream(DataOutputStream(ByteBufOutputStream(buffer)), true)
    }

    protected fun createPacket(fullPacket: Boolean): Packet<*> {
        val payload = syncMap.createPayload(fullPacket)
        return SyncChannelHolder.createPacket(payload)
    }

    override fun entityInit() {

    }

    override fun getEntityData(): NBTTagCompound {
        val tag = NBTTagCompound()
        syncMap.writeToNBT(NBTTagCompound())
        return tag
    }
}

 

And the EntityMultiblock class:

abstract class EntityMultiblock(world: World) : SyncedEntity(world) {
    abstract fun destroy()
    override fun canRenderOnFire(): Boolean = false
    override fun canBeCollidedWith(): Boolean = false
    override fun canBePushed(): Boolean = false
    override fun dealFireDamage(amount: Int) = Unit
    override fun attackEntityFrom(source: DamageSource?, amount: Float): Boolean = false
    override fun canTrample(world: World?, block: Block?, pos: BlockPos?, fallDistance: Float): Boolean = false
}

 

And finally the EntityBoilerMultiblock class (with some unrelated parts removed for brevity):

class EntityBoilerMultiblock(world: World) : EntityMultiblock(world), IHasGui, IInventoryProvider, IItemHandler {
    companion object { //static
        init {
            KaidenCraft.preInitEvent += { EntityRegistry.registerModEntity(ResourceLocation(KaidenCraft.MOD_ID, "multiblock_boiler"),
                    EntityBoilerMultiblock::class.java, "multiblock_boiler", 0, KaidenCraft.instance, 32, 20, false) } //lambda added to handler list
        }

        fun rebuild(world: World, pos: BlockPos): EntityBoilerMultiblock? {
			...			

            val entity = EntityBoilerMultiblock(world)
            entity.setPosition(currentPos.x.toDouble(), currentPos.y.toDouble(), currentPos.z.toDouble())
            boilerBlocks.join(tankBlocks).forEach { // exactly the same as for(BlockPos it : tankBlocks)
                val te = world.getTileEntity(it)
                if(te is TileEntityMultiblockPart) {
                    if(te.multiblockId.value != 0) getMultiblockEnt(world, te)?.destroy()
                    te.multiblockId.value = entity.entityId
                }
            }

            entity.boilerBlocks.addAll(boilerBlocks)
            entity.tankBlocks.addAll(tankBlocks)
            Log.debug("Found multiblock, contains ${boilerBlocks.size} boiler/tank units")
            return entity

			//code in TileEntityMultiblockPart: if(entity != null) world.spawnEntity(entity)
        }

        private fun getMultiblockEnt(world: World, te: TileEntityMultiblockPart): EntityMultiblock? {
            return (world.getEntityByID(te.multiblockId.value)) as? EntityMultiblock
        }
    }

    override fun destroy() {
        ...
    }

    lateinit var boilerBurnTime: SyncableInt
    lateinit var currentItemBurnTime: SyncableInt
    lateinit var waterTank: SyncableTank
    lateinit var steamTank: SyncableTank
    lateinit var boilerBlocks: SyncableCoordList
    lateinit var tankBlocks: SyncableCoordList

    override fun onEntityUpdate() {
        ...
    }

    override fun insertItem(slot: Int, stack: ItemStack, simulate: Boolean): ItemStack {
        
    }

    override fun getStackInSlot(slot: Int): ItemStack = inventory.getStackInSlot(slot)
    override fun getSlotLimit(slot: Int): Int = inventory.sizeInventory
    override fun getSlots(): Int = 1
    override fun extractItem(slot: Int, amount: Int, simulate: Boolean): ItemStack {
        return emptyItemStack
    }

    override fun createSyncedFields() {
        syncMap.sentSyncEvent += {
            if(it.changes.contains(waterTank)) waterTank.capacity = 4000 * tankBlocks.size
            if(it.changes.contains(steamTank)) steamTank.capacity = 4000 * tankBlocks.size
        }

        boilerBurnTime = SyncableInt()
        currentItemBurnTime = SyncableInt()
        boilerBlocks = SyncableCoordList()
        tankBlocks = SyncableCoordList()
        waterTank = SyncableTank(4000)
        steamTank = SyncableTank(4000)
    }

    override fun writeToNBT(tag: NBTTagCompound): NBTTagCompound {
        super.writeToNBT(tag)
        tag.setTag("inventory", inventory.serializeNBT())
        return tag
    }

    override fun readFromNBT(tag: NBTTagCompound) {
        super.readFromNBT(tag)
        inventory.deserializeNBT(tag.getCompoundTag("inventory"))
    }

    override val inventory: InventorySerializable = object: InventorySerializable("boiler", false, 1) {
        override fun isItemValidForSlot(i: Int, stack: ItemStack): Boolean = GameRegistry.getFuelValue(stack) > 0
    }

    override fun getServerGui(player: EntityPlayer): Any {
        return ContainerBoiler(player.inventory, this)
    }

    override fun getClientGui(player: EntityPlayer): Any {
        return GuiBoiler(ContainerBoiler(player.inventory, this))
    }

    override fun canOpenGui(player: EntityPlayer): Boolean = true
    fun isFurnaceBurning(): Boolean = boilerBurnTime.value > 0
}

 

Also the SyncMap class since this is about syncing:

class SyncMapEntity<out H>(handler: H) : SyncMap<H>(handler) where H : Entity, H : ISyncMapProvider {

    override val handlerType: SyncMap.HandlerType = HandlerType.ENTITY

    override val playersWatching: Set<EntityPlayerMP> by lazy { NetUtils.getPlayersWatchingEntity(handler.world as WorldServer, handler.entityId) } // just lazy loading the value

    override val world: World = handler.world

    override val invalid = handler.isDead
}

abstract class SyncMap<out H : ISyncMapProvider>(protected val handler: H) {
    companion object { // static
        class SyncFieldException : RuntimeException {
            constructor(cause: Throwable, name: String) : super("Failed to sync field '$name'", cause)
            constructor(cause: Throwable, index: Int) : super("Failed to sync field $index", cause)
        }

        private val MAX_OBJECT_NUM = 16

        @Throws(IOException::class)
        fun findSyncMap(world: World, input: DataInput): ISyncMapProvider? {
            val handlerTypeId = ByteUtils.readVLI(input)

            require(handlerTypeId < HandlerType.TYPES.size) { "SERIOUS BUG!!! handler type" }

            val handlerType = HandlerType.TYPES[handlerTypeId]
            val handler = handlerType.findHandler(world, input)
            return handler
        }
    }

    class SyncEvent(val changes: Set<ISyncableObject>)

    abstract class HandlerType {
        companion object { //static
            val ENTITY = object: HandlerType() {
                override val ordinal = 1

                override fun findHandler(world: World, input: DataInput): ISyncMapProvider? {
                    val entityId = input.readInt()
                    val entity = world.getEntityByID(entityId)
                    if(entity is ISyncMapProvider) return entity

                    Log.warn("Invalid handler info: can't find ISyncHandler entity id $entityId")
                    return null
                }

                override fun writeHandlerInfo(handler: ISyncMapProvider, output: DataOutput) {
                    try {
                        val e = handler as Entity
                        output.writeInt(e.entityId)
                    } catch (e: ClassCastException) {
                        throw RuntimeException("Invalid usage of handler type", e)
                    }
                }
            }

            val TILE_ENTITY = object: HandlerType() {
                override val ordinal = 0

                override fun findHandler(world: World, input: DataInput): ISyncMapProvider? {
                    val x = input.readInt()
                    val y = input.readInt()
                    val z = input.readInt()
                    val blockPos = BlockPos(x, y ,z)

                    if(!world.isAirBlock(blockPos)) {
                        val tile = world.getTileEntity(blockPos)
                        if(tile is ISyncMapProvider) return tile
                    }

                    Log.warn("Invalid handler info: can't find ISyncHandler TE @ ($x,$y,$z)", x, y, z)
                    return null
                }

                override fun writeHandlerInfo(handler: ISyncMapProvider, output: DataOutput) {
                    try {
                        val tileEntity = handler as TileEntity
                        output.writeInt(tileEntity.pos.x)
                        output.writeInt(tileEntity.pos.y)
                        output.writeInt(tileEntity.pos.z)
                    } catch (e: ClassCastException) {
                        throw RuntimeException("Invalid usage of handler type", e)
                    }
                }
            }

            internal val TYPES = arrayOf(TILE_ENTITY, ENTITY)
        }

        @Throws(IOException::class)
        abstract fun findHandler(world: World, input: DataInput): ISyncMapProvider?

        @Throws(IOException::class)
        abstract fun writeHandlerInfo(handler: ISyncMapProvider, output: DataOutput)
        abstract val ordinal: Int
    }

    private val knownUsers = hashSetOf<Int>()

    private val objects = arrayOfNulls<ISyncableObject>(16)
    private val nameMap = hashMapOf<String, ISyncableObject>()
    private val objectToId = Maps.newIdentityHashMap<ISyncableObject, Int>()

    val sentSyncEvent = Event<SyncEvent>()
    val receivedSyncEvent = Event<SyncEvent>()
    val clientInitEvent = Event<SyncEvent>()

    private var index = 0

    val size get() = index

    fun put(name: String, value: ISyncableObject) {
        require(index < MAX_OBJECT_NUM) { "Can't add more than $MAX_OBJECT_NUM objects" }
        val objId = index++
        objects[objId] = value //equivalent to objects.put(objId, value)
        nameMap[name] = value
        val prev = objectToId.put(value, objId)
        require(prev == null) { "Object $value registered twice, under ids $prev and $objId" }
    }

    operator fun set(name: String, value: ISyncableObject) = put(name, value)

    operator fun get(name: String) = nameMap["name"] ?: throw NoSuchElementException(name)
    operator fun get(objectId: Int): ISyncableObject {
        try {
            return objects[objectId] ?: throw NoSuchElementException(objectId.toString())
        } catch (e: ArrayIndexOutOfBoundsException) {
            throw NoSuchElementException(objectId.toString())
        }
    }

    fun getId(objectt: ISyncableObject): Int {
        return objectToId[objectt] ?: throw NoSuchElementException(objectt.toString())
    }

    @Throws(IOException::class)
    fun readFromStream(dataInputStream: DataInputStream) {
        var mask = dataInputStream.readShort()
        val changes = Sets.newIdentityHashSet<ISyncableObject>()
        var currentBit = 0

        while(mask != 0.toShort()) {
            if((mask and 1.toShort()) != 0.toShort()) {
                val objectt = objects[currentBit]
                if(objectt != null) {
                    try {
                        objectt.readFromStream(dataInputStream)
                    } catch (e: Throwable) {
                        throw SyncFieldException(e, currentBit)
                    }
                    changes.add(objectt)
                }
            }
            currentBit++
            mask = (mask.toInt() shr 1).toShort()
        }

        if(!changes.isEmpty()) receivedSyncEvent.fire(SyncEvent(Collections.unmodifiableSet(changes)))
    }

    @Throws(IOException::class) fun writeToStream(dataOutputStream: DataOutputStream, fullPacket: Boolean) {
        var mask = 0
        for(i in 0..index-1) {
            val objectt = objects[i]
            if(objectt != null && (fullPacket || objectt.isDirty())) {
                mask = ByteUtils.on(mask, i)
            }
        }
        dataOutputStream.writeShort(mask)

        for(i in 0..index-1) {
            val objectt = objects[i]
            if(objectt != null && (fullPacket || objectt.isDirty())) {
                try {
                    objectt.writeToStream(dataOutputStream)
                } catch (t: Throwable) {
                    throw SyncFieldException(t, i)
                }
            }
        }
    }

    protected abstract val handlerType: HandlerType
    protected abstract val playersWatching: Set<EntityPlayerMP>
    protected abstract val world: World
    protected abstract val invalid: Boolean

    fun sync() {
        require(!world.isRemote) { "This method can only be used server side" }
        if(invalid) return

        val changes = listChanges()
        val hasChanges = !changes.isEmpty()

        val fullPacketTargets = arrayListOf<EntityPlayerMP>()
        val deltaPacketTargets = arrayListOf<EntityPlayerMP>()

        val players = playersWatching
        for(player in players) {
            if(knownUsers.contains(player.entityId)) {
                if(hasChanges) deltaPacketTargets.add(player)
            } else {
                knownUsers.add(player.entityId)
                fullPacketTargets.add(player)
            }
        }

        try {
            if(!deltaPacketTargets.isEmpty()) {
                val deltaPayload = createPayload(false)
                SyncChannelHolder.instance.value.sendPayloadToPlayers(deltaPayload, deltaPacketTargets)
            }
        } catch (e: IOException) {
            Log.warn(e, "IOError during delta sync")
        }

        try {
            if(!fullPacketTargets.isEmpty()) {
                val fullPayload = createPayload(true)
                SyncChannelHolder.instance.value.sendPayloadToPlayers(fullPayload, fullPacketTargets)
            }
        } catch (e: IOException) {
            Log.warn(e, "IOError during full sync")
        }

        if(hasChanges) {
            markClean(changes)
            sentSyncEvent.fire(SyncEvent(Collections.unmodifiableSet(changes)))
        }
    }

    @Suppress("UNCHECKED_CAST")
    private fun listChanges() = objects.filter { it != null && it.isDirty() }.toSet() as Set<ISyncableObject>
    private fun markClean(changes: Set<ISyncableObject>) = changes.forEach { it.markClean() }

    @Throws(IOException::class)
    fun createPayload(fullPacket: Boolean): PacketBuffer {
        val output = Unpooled.buffer()
        val type = handlerType
        ByteBufUtils.writeVarInt(output, type.ordinal, 5)

        val dataOutputStream = DataOutputStream(ByteBufOutputStream(output))
        type.writeHandlerInfo(handler, dataOutputStream)
        writeToStream(dataOutputStream, fullPacket)

        return PacketBuffer(output.copy())
    }

    fun writeToNBT(tag: NBTTagCompound) {
        for((name, obj) in nameMap.entries) {
            try {
                obj.writeToNBT(tag, name)
            } catch (e: Throwable) {
                throw SyncFieldException(e, name)
            }
        }
    }

    fun readFromNBT(tag: NBTTagCompound) {
        for((name, obj) in nameMap.entries) {
            try {
                obj.readFromNBT(tag, name)
            } catch (e: Throwable) {
                throw SyncFieldException(e, name)
            }
        }
    }
}

Full GitHub for library and mod here and here

Hope someone can help me out here. Cheers in advance.

Edited by BenignBanana
Fixed a bug that was added after testing.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Announcements



  • Recently Browsing

    • No registered users viewing this page.
  • Posts

    • Yes that’s the full log, I managed to get it working last night, the anvil fix mod is what was causing it to crash
    • Hey guys, i'm currently developping a mod with forge 1.12.2 2860 and i'm using optifine and gradle 4.9. The thing is i'm trying to figure out how to show the player's body in first person. So far everything's going well since i've try to use a shader. The player's body started to blink dark when using a shader. I've try a lot of shader like chocapic, zeus etc etc but still the same issue. So my question is : How should i apply the current shader to the body ? At the same time i'm also drawing a HUD so maybe it could be the problem?   Here is the issue :    And here is the code where i'm trying to display the body :    private static void renderFirstPersonBody(EntityPlayerSP player, float partialTicks) { Minecraft mc = Minecraft.getMinecraft(); GlStateManager.pushMatrix(); GlStateManager.pushAttrib(); try { // Préparation OpenGL GlStateManager.enableDepth(); GlStateManager.depthMask(true); GlStateManager.enableAlpha(); GlStateManager.alphaFunc(GL11.GL_GREATER, 0.1F); GlStateManager.enableBlend(); GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); // Éclairage correct pour shaders GlStateManager.enableLighting(); RenderHelper.enableStandardItemLighting(); GlStateManager.enableRescaleNormal(); // Active la lightmap pour les shaders mc.entityRenderer.enableLightmap(); // Position de rendu interpolée double px = player.lastTickPosX + (player.posX - player.lastTickPosX) * partialTicks; double py = player.lastTickPosY + (player.posY - player.lastTickPosY) * partialTicks; double pz = player.lastTickPosZ + (player.posZ - player.lastTickPosZ) * partialTicks; GlStateManager.translate( px - mc.getRenderManager().viewerPosX, py - mc.getRenderManager().viewerPosY, pz - mc.getRenderManager().viewerPosZ ); // Rendu du joueur sans la tête Render<?> render = mc.getRenderManager().getEntityRenderObject(player); if (render instanceof RenderPlayer) { RenderPlayer renderPlayer = (RenderPlayer) render; boolean oldHeadHidden = renderPlayer.getMainModel().bipedHead.isHidden; boolean oldHeadwearHidden = renderPlayer.getMainModel().bipedHeadwear.isHidden; renderPlayer.getMainModel().bipedHead.isHidden = true; renderPlayer.getMainModel().bipedHeadwear.isHidden = true; setArmorHeadVisibility(renderPlayer, false); renderPlayer.doRender(player, 0, 0, 0, player.rotationYaw, partialTicks); renderPlayer.getMainModel().bipedHead.isHidden = oldHeadHidden; renderPlayer.getMainModel().bipedHeadwear.isHidden = oldHeadwearHidden; setArmorHeadVisibility(renderPlayer, !oldHeadwearHidden); } // Nettoyage post rendu mc.entityRenderer.disableLightmap(); GlStateManager.disableRescaleNormal(); } catch (Exception e) { // silent fail } finally { GlStateManager.popAttrib(); GlStateManager.popMatrix(); } }   Ty for your help. 
    • Item successfully registered, but there was a problem with the texture of the item, it did not insert and has just the wrong texture.     
    • Keep on using the original Launcher Run Vanilla 1.12.2 once and close the game Download Optifine and run optifine as installer (click on the optifine jar) Start the launcher and make sure the Optifine profile is selected - then test it again  
    • Hi everyone, I’m hoping to revisit an old version of Minecraft — specifically around Beta 1.7.3 — for nostalgia’s sake. I’ve heard you can do this through the official Minecraft Launcher, but I’m unsure how to do it safely without affecting my current installation or save files. Are there any compatibility issues I should watch out for when switching between versions? Would really appreciate any tips or advice from anyone who’s done this before! – Adam
  • Topics

×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.