
BenignBanana
Forge Modder-
Posts
49 -
Joined
-
Last visited
Everything posted by BenignBanana
-
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.
-
BlockState rotation around Z axis not working
BenignBanana replied to BenignBanana's topic in Modder Support
Just posting one last update here for anyone who sees this in the future. Turns out there IS a way to rotate around the Z axis, it's just really hard to find. I stumbled upon it after googling for blockstate transforms (as in moving the model). See Forge Blockstate V1 specs for all the details. Do keep in mind that any Y rotation you may do in a facing variant will override these transforms. -
BlockState rotation around Z axis not working
BenignBanana replied to BenignBanana's topic in Modder Support
Yea I looked at the ModelRotation code. That's some of the most inflexible code I've ever seen. Guess I'll just have to bake it in then. Anyways, thanks for the help. -
BlockState rotation around Z axis not working
BenignBanana replied to BenignBanana's topic in Modder Support
Is there no way to do this other than baking the y rotation into a copy of the model? -
BlockState rotation around Z axis not working
BenignBanana replied to BenignBanana's topic in Modder Support
Yea this definitely doesn't work. After recreating the BlockState scenarios in Maya it seems that Y is always applied after X, making this trick impossible. Applying y 270 first, then x 90 gives me the correct result in Maya (as it should), but what I see in Minecraft is equivalent to applying x 90 first, then y 270. This is what I did in my JSON: "corner": { "model": "kaidencraft:boilertank/end.obj", "y": 270, "x": 90 }, -
BlockState rotation around Z axis not working
BenignBanana replied to BenignBanana's topic in Modder Support
Yea I figured that out now with some hand moving and loading a test object into Maya But I'm still struggling to understand how that will interact with the facing property that I use for Y rotation. -
BlockState rotation around Z axis not working
BenignBanana replied to BenignBanana's topic in Modder Support
Can you tell me how to do that specifically? I'm terrible at 3D visualization and vector math. Also, would the y rotation work additively with the facing? -
Hey everyone, I tried my hand at some X and Z rotating to save myself a bunch of models on my connected model, but it seems "z": 90 doesn't to anything. "x":90 works just fine and so does "y":90, but z seems to be completely ignored. Why could this be and how can I fix it? Here's my BlockState (it's a long one): { "forge_marker": 1, "variants": { "normal": [ {"model": "kaidencraft:boilertank/single.obj"} ], "inventory": [ {} ], "mb_part": { "single": { "model": "kaidencraft:boilertank/single.obj" }, "end": { "model": "kaidencraft:boilertank/pillar_bottom.obj", "x": 90 }, "line_edge": { "model": "kaidencraft:boilertank/pillar_center.obj", "z": 90 }, "corner": { "model": "kaidencraft:boilertank/end.obj", "z": 90 }, "edge": { "model": "kaidencraft:boilertank/edge_single.obj" }, "center": { "model": "kaidencraft:boilertank/center_single.obj" }, "bottom_end": { "model": "kaidencraft:boilertank/end.obj" }, "bottom_edge": { "model": "kaidencraft:boilertank/edge.obj" }, "bottom_corner": { "model": "kaidencraft:boilertank/corner.obj" }, "bottom_center": { "model": "kaidencraft:boilertank/center_top.obj", "x": 180 }, "bottom_pillar": { "model": "kaidencraft:boilertank/pillar_bottom.obj" }, "center_end": { "model": "kaidencraft:boilertank/edge_single.obj", "x": 90, "z": 90 }, "center_corner": { "model": "kaidencraft:boilertank/edge.obj", "z": 90, "x": 90 }, "center_edge": { "model": "kaidencraft:boilertank/center_top.obj", "x": 270 }, "center_line_edge": { "model": "kaidencraft:boilertank/center_single.obj", "x": 90 }, "center_pillar": { "model": "kaidencraft:boilertank/pillar_center.obj" }, "top_end": { "model": "kaidencraft:boilertank/end.obj", "z": 180 }, "top_corner": { "model": "kaidencraft:boilertank/corner.obj", "x": 180 }, "top_edge": { "model": "kaidencraft:boilertank/edge.obj", "x": 90 }, "top_line_edge": { "model": "kaidencraft:boilertank/edge_single.obj", "x": 90 }, "top_pillar": { "model": "kaidencraft:boilertank/pillar_bottom.obj", "x": 180 }, "top_center": { "model": "kaidencraft:boilertank/center_top.obj" }, "hidden": { "model": "kaidencraft:blank.obj" } }, "facing": { "north": {}, "south": { "y": 180 }, "west": { "y": 270 }, "east": { "y": 90 } } }, "defaults": { "model": "kaidencraft:boilertank/single.obj", "textures": { "#initialShadingGroup": "kaidencraft:block/boiler_main" } } } The part I'm testing with is "corner", which rotates fine when I put in an x rotation, but does nothing when I put in various z rotations. Cheers in advance Edit for posterity: You can get any transforms you want using forge specific BlockState parts - see Forge Blockstate V1 specs for all details. Only the last specified transform will be applied - this includes simple rotation like you would use in a facing variant.
-
Call method when block is watched by player
BenignBanana replied to BenignBanana's topic in Modder Support
I don't access the world or blocks when making the tag, I wanted to use my "own" (really OpenModsLib's) sync system to send the syncable info over a custom network channel. That code needs to get the TileEntity at the relevant position so it can handle the incoming sync data. I solved this for now by just serializing the sync map into NBT and returning that in getUpdateTag(), but I'd really prefer to use a unified sync method. The readFromNBT()/writeToNBT() on the sync map are only supposed to be called when loading/saving the world, so I dislike calling it for synchronisation. So I need an event that triggers after a player has loaded the world from the server. Edit: Here's my code that requires the world to be loaded: 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 } -
Hey everyone, after loads of debugging work I finally managed to make my multiblock with custom models work, but I noticed this weird graphical issue on two sides of my model. There is what looks like a lighting glitch, manifesting as a dark blob, spread out across the entire side of the multiblock, even between the separate block models. Here's some images: Not sure what could be causing this. The models are identical, just rotated 90/180 degrees. Any ideas? Edit: I'm, obviously talking about the bottom models. The top ones are just placeholders.
-
Call method when block is watched by player
BenignBanana replied to BenignBanana's topic in Modder Support
Alright so I used getUpdateTag() which does actually fire (tried getUpdatePacket() yesterday, which doesn't seem to fire in SP). However it seems at the time getUpdateTag() is called, the world isn't properly loaded yet and all blocks return as air. Where do I need to hook in to get the first viable state where the player is watching and the client has already loaded the world from the server? Edit: I use a custom sync system with all the networking handled in my library, I could use writeToNBT() but it would significantly increase the overhead for networking. -
Hey everyone, I hate to ask for yet more help after all my threads over the last few days, but this is a framework question so at least I'm not just an idiot and have a bug in my own code. I need to do an initial full sync for my TileEntities whenever a player starts watching it (or in the case of the client, as soon as the single player gets loaded in). How can I hook into this to trigger the sync? I tried to put it in onLoad() but that seems to be called before the client is loaded and it wouldn't work for multiplayer anyways. Cheers in advance
-
[SOLVED] Getting actual state on startup
BenignBanana replied to BenignBanana's topic in Modder Support
Nevermind, I guess it was a breakpoint not working properly. I had one in my getMultiblockPart() method which is called from getActualState() and that wasn't getting triggered, but after doing a full recompile and restart it's working fine. The issue that was causing it to not work was the fact that the TE wasn't serialized because I didn't properly exit and instead just stopped it in the debugger. It also seems to be called before the server/client sync so I'll have to recalculate the rendering in my sync event anyways. Derp. -
Hey everyone, I noticed that my additional blockstate data works fine when placing/breaking blocks, but the game doesn't call getActualState() on startup. Is there a "proper" way to get it called? I could always add a bit of one time code into the TileEntity update but that feels a bit hacky and roundabout. So does Forge/MC have an actual way to do this?
-
Alright I finally figured out what was going on with the events not being called server side. Turns out in this new world, unlike before, the multiblock parts had different Y coordinates instead of different X coordinates, which caused an infinite loop in my rebuild function (due to a mistake of mine), but I couldn't catch this in step through because the logic was in a lambda delegate (which is executed in a separate context, hence no stepping). As a result the server got frozen and any further events were no longer sent. I thought the whole debugger hanging up was an issue because of spending too long in paused mode (which happens fairly frequently), but after a few restarts I noticed it always happened in the same spot, allowing me to spot the issue. I learned my lesson: Always replace delegates with regular for loops while debugging. The issue with TEs being replaced is also fixed thanks to jeffryfisher's help. The question about a discord or something still stands though, I'd much rather ask for help in a chat program if it's something that won't be useful to see for anyone else in the future.
-
I totally misread that vanilla code, I thought if it ISN'T vanilla it checks if the block changed, otherwise if the state changed. Seems like its the other way around. Derp. Anyways, still have the problem of onBlockPlacedBy not being called server side. I'm moving my way through all the internal Minecraft/Forge code atm to see why this isn't working. Hopefully I can find something. Edit: Is there some kind of Discord or something for Forge development help? I don't want to spam this forum with updates on the debugging.
-
I posted both in the OP. It's completely up to date after draco reminded me to commit. Well yeah. I know that Kotlin hasn't really caught on yet outside the Android community, but most of the code is similar enough to Java to be understandable. Either way most of the more complicated kotlin code doesn't really deal with the problem, it's just that very simple bit of code I posted above. Also, I mostly asked "No one?" as a way to push the thread back up, since the conversation kind of stopped after yesterday afternoon and the thread was about to leave the first page when I checked back today. Sorry if I'm being a bit pushy, this is just a really big issue that prevents me from further working on my mod and despite my 8 years Java experience I cannot for the life of me figure this out by myself. I hate asking others to help me debug my own code, I only posted here because I'm pretty desperate.
-
I always forget to commit when I'm trying to debug stuff. It's updated now. It seems that after restarting the client the events fire once on the server side (including blockAdded), but after that it only fires client side and blockAdded stops firing altogether. I honestly don't have a clue what's going on. I don't just have a breakpoint in the events themselves, I have breakpoints on lots of spots down the chain and the only ones that are getting hit are onBlockPlacedBy and teh following world.isRemote check in BlockMultiblockPart. I can't figure out what's happening. I made sure nothing down the inheritance chain is overriding those functions either. Here's the listing of the relevant functions with break points marked as a comment override fun onBlockPlacedBy(worldIn: World, pos: BlockPos, state: IBlockState, placer: EntityLivingBase, stack: ItemStack) { worldIn.setBlockState(pos, state.withProperty(FACING, placer.horizontalFacing.opposite), 2) //BP: breaks once with worldIn instanceof WorldClient placeEvent.fire(BlockPlacedEvent(worldIn, pos, state, placer, stack)) super.onBlockPlacedBy(worldIn, pos, state, placer, stack) } override fun onBlockAdded(worldIn: World, pos: BlockPos, state: IBlockState) { addEvent.fire(BlockAddedEvent(worldIn, pos, state)) //BP: never triggers super.onBlockAdded(worldIn, pos, state) } init { placeEvent += { if(!it.world.isRemote) { //BP: Triggers once with it.world instanceof WorldClient val te = it.world.getTileEntity(it.pos) as TileEntityMultiblockPart te.rebuild(it.pos) } } breakEvent += { if(!it.world.isRemote) { val te = it.world.getTileEntity(it.pos) as TileEntityMultiblockPart if(te.multiblockId.value != -1) (it.world.getEntityByID(te.multiblockId.value) as? EntityMultiblock)?.destroy() } } } This is the hardest to track down bug I've ever had. Seriously.
-
Hmm, so the original issue may or may not still be there, but after removing that shouldRefresh override and defaulting it to the vanilla behaviour, the server side no longer gets the onBlockPlacedBy event, and onBlockAdded() doesn't seem to fire at all, period. My guess is that shouldRefresh has something to do with sending a block update to the server, but I can't find the relevant code. Any ideas?
-
I don't see what would set a new BlockState though, especially considering only one of the two TileEntityBoilers has this issue. I also don't override shouldRefresh and in the default TileEntity that just returns true if the Block changes (which it obviously doesn't). After doing a bunch more debugging with a clearer mind I noticed some other things. For one, I made a new world to exclude the possibility of a world corruption during development. This didn't help. I also added a third block to the structure and tracked the TileEntities over quite a few multiblock breaks/rebuilds. What I found is that it's not one specific TileEntity that gets refreshed, but they almost always refresh one at a time. There were two syncs where 2 changed at once, but on the vast majority it was just one, going highest X to lowest X in order. I also let the BlockState refresh from just normal render refreshes and the TileEntities never changed, combined with the fact that this only happens on the client side it means the bug must be somewhere in the sync code. I pretty much straight ported that from OpenModsLib which was written for 1.7 (aka pre BlockStates), so there might be a sneaky bug in there I can't find. I hardly changed anything in the sync system, so it might be easier for you to understand the Java equivalent code here: https://github.com/OpenMods/OpenModsLib/tree/master/src/main/java/openmods/sync I've been looking at this code for way too long and I might just be missing something really obvious. I'll keep looking but maybe you can figure something out.
-
Hey guys, After hours of banging my head against a wall I finally managed to narrow down my code not working to one specific issue. For some unknown reason, one of my multiblock TileEntities is getting replaced by a new instance after a sync from the server. I have no idea what's causing this, the other identical TE is working perfectly fine, I'm at the end of my wisdom and I just hope that maybe someone here has any idea. The code is way too all over the place to post here (though you're welcome to check out the GitHub for the main library and the mod itself, most of the code in question is in the library and all relevant classes have multiblock or sync in them). I did take some fairly detailed debug notes though, so here they are: /* type | side | type mem location | syncable id mem location | action */ boiler server 10066 10053 setting proper id boiler server 10096 10143 setting proper id tank server 10105 10112 setting proper id tank server 10100 10037 setting proper id tank client 10182 10154 reading proper id from stream boiler client 10209 10196 reading proper id from stream boiler client 10174 10222 reading proper id from stream tank client 10259 10246 reading proper id from stream /* type | side | coords | debug uuid msb | debug uuid lsb | actions performed */ boiler server 399 4 2100 7406611159510238969 -6004429813246348525 set value in rebuild | getActualBlockState() boiler server 400 4 2100 8627676692757629106 -9168228343733114012 set value in rebuild tank server 399 5 2100 984907512996252097 -8261117959805136242 set value in rebuild // triggered rebuild tank server 400 5 2100 5216046578638867465 -9161033129145849707 set value in rebuild boiler client 399 4 2100 -777796711269906213 -6909084835805274391 called 2x in getActualBlockState() | read from stream boiler client 400 4 2100 1875314197142261539 -4929778570773350453 called 2x in getActualBlockState() | read from stream tank client 400 5 2100 -2113191912659205197 -4780008886862939808 read from stream tank client 399 5 2100 -6765782617268665069 -6187872588524286999 read from stream /* like above, but the UUID combined with the BlockPos is the important thing to look at. boiler client 399 4 2100 -1982676738952378984 -7018763097901900451 getActualBlockState() new TE!!!! boiler client 400 4 2100 1875314197142261539 -4929778570773350453 getActualBlockState() this one is the same Keep in mind that each of the two sections were taken in a single update tick after completing the multiblock. I seriously don't know what to do anymore. Hopefully someone here can figure it out.
-
[SOLVED] Getting TileEntity in onBlockDestroyed events
BenignBanana replied to BenignBanana's topic in Modder Support
Right. I was looking for an onSomething method, so I didn't see that one. Cheers for the help -
[SOLVED] Getting TileEntity in onBlockDestroyed events
BenignBanana posted a topic in Modder Support
Hey everyone, I ran into a bit of a snag when I tried to do multiblock cleanup on block break. If I try to get the TileEntity of my block inside the onBlockDestroyedByPlayer/Explosion methods it always returns null, preventing me from calling the cleanup method on it. Why is it always returning null and/or is there any other event I can use that still has the TileEntity active? The code is a bit obtuse because of the large framework I've built and the fact that it's kotlin, but I hope these snippets are enough to understand it. Inside my framework base block class: override fun onBlockDestroyedByPlayer(worldIn: World, pos: BlockPos, state: IBlockState) { breakEvent.fire(BlockBrokenEvent(worldIn, pos)) super.onBlockDestroyedByPlayer(worldIn, pos, state) } override fun onBlockDestroyedByExplosion(worldIn: World, pos: BlockPos, explosionIn: Explosion) { breakEvent.fire(BlockBrokenEvent(worldIn, pos)) super.onBlockDestroyedByExplosion(worldIn, pos, explosionIn) } And inside the multiblock part block base class: init { placeEvent += { if(!it.world.isRemote) { val te = it.world.getTileEntity(it.pos) as TileEntityMultiblockPart te.rebuild(it.pos) } } breakEvent += { if(!it.world.isRemote) { val te = it.world.getTileEntity(it.pos) as? TileEntityMultiblockPart //This is always null if(te != null && te.multiblockId.value != -1) (it.world.getEntityByID(te.multiblockId.value) as? EntityMultiblock)?.destroy() } } } Cheers