Jump to content

IceMetalPunk

Members
  • Posts

    402
  • Joined

  • Last visited

Everything posted by IceMetalPunk

  1. ๐Ÿคฆโ€โ™‚๏ธ So... yeah. Apparently, even though I added System JRE (and deleted and re-added it multiple times) to my project build path, and even though my JAVA_HOME is pointing to the JDK 17.... Eclipse still treated "System JRE" as "the JRE that was bundled deep in the directory structure of this installation of Eclipse" instead of, you know, my actual system's JRE ๐Ÿ˜‘ Thank you for pointing me in the right direction!
  2. As I said, I have JDK 17 installed and my JAVA_HOME is pointing to it. Specifically, JDK 17.0.2.
  3. It's been a long time since I've made a mod, and I'm trying to set up one for 1.18.1 using Eclipse. (Specifically, it's Forge MDK 39.0.66.) Whenever I run the runClient configuration, even with just the basic example mod code with no code changes, I get this error: I've made sure I have JDK 17 installed, my JAVA_HOME environment variable points to it, I've done gradlew clean and refreshed dependencies, re-ran genEclipseRuns, and even re-added the system JRE library to the project build path. Nothing seems to stop this error. What am I doing wrong and how do I fix it?
  4. I suppose I could have the validate() method return either a triple or an optional pair to indicate isValid, tier, and range, rather than storing it in the block class's fields. That's definitely something I'll look into, thanks for the suggestion! I didn't realize the BabyEntitySpawnEvent fired before the baby was moved to the correct position, but looking at the breeding method now with that in mind, I see it. After adding a moveTo for the clones in the event handler, everything is working perfectly now! Thank you for all your help with everything! โค๏ธ
  5. this.isValid and this.tier are (re)set in the validate() method, which is called right before they're used, so they're both definitely re-evaluated immediately before use. getRangeBB is only being used by operate() method overrides, which is called also in the code immediately following a (re)set in validate(). None of those variables are ever being used without first being re-evaluated getRenderLayer is only meant to indicate when a block doesn't use the default layer so it can be set accordingly. Is RenderType.SOLID the default? If so, I can make that the default return value instead. You're right about the block registry; I thought I was going to have those PABlock#register methods do a bit more when I first structured it, but then ended up not, so it's an artifact of that. I can easily change that over to remove that method, thanks. The thing about BlockItems, though, is that I designed the system so I could just add blocks and they'd automatically add their own BlockItems without me having to do twice as much work for every block I add. I suppose I can refactor such that the ItemBlock is created locally in the block's registerBlockItem method instead of stored in a class field, but that's nearly the same code -- what exactly is the benefit of rejecting any reference to the ItemBlock in the block class and manually doubling the amount of new classes I make? The allOmens list in OmenBlock is used for the Patchouli rendering of the multiblock structures. Since each omen is a separate block rather than a block state property variant (which was a choice that I'm not passionate about nor passionate against), to have the render cycle among all the omen types I needed a way to get a list of all those omen blocks, so I just created that list. I hope this answers your questions
  6. I fully understand how blocks work. This mod started as an exercise in making functional blocks without adding tile entities. It may look like the block is storing mutable state, but it's not *really* doing that; any variables that seem like they could differ between blocks in the world are only ever used by the same piece of code that sets them, and they're always reset/re-evaluated at the beginning of any such code. I understand that internally, this means the same instance is overwriting its values for every block that evaluates them, but as that was the motivation for making this mod, I'm happy with it as long as it works. I know that it's an unusual way to handle things, and I'm okay with that. ...of course it *would* be a single-character copypasta typo at the root of all this ๐Ÿ˜‚ Thank you so much! And thank you for mentioning inflate; I remember thinking the method name was a bit strange, but I couldn't find one called "expand" which I thought would be the name for the opposite of the "contract" method, and "expandTowards" was the closest, so I went with that (and it did seem to work for me in my tests somehow; I must have coincidentally set things up in the perfect position for the numbers to work out ๐Ÿ˜‚). After fixing the i -> j typo, the game no longer locks up, which is good, but... it still also doesn't work It *says* it's successfully cloning the baby sheep, but I only see one. However, if I use a command like /say or /kill, it reports the total number of sheep correctly, including clones. I though maybe there was a client/server sync piece I was missing, so I looked into how the SummonCommand works to spawn entities, and saw it's done very differently from how I was doing it. So I reworked the spawning bit using the same method SummonCommand does (where applicable), and now... same result: no visible clones, but /say and /kill report the correct number of sheep including clones. I've pushed up the changes to the repo, but here's the new event handler if you don't feel like functionally testing it; I hope you can spot what I'm doing wrong that makes SummonCommand work but my spawning fail: package com.icemetalpunk.psychicaltars.events; import java.lang.reflect.Field; import java.util.Optional; import java.util.UUID; import com.icemetalpunk.psychicaltars.helpers.FakePlayerHelper; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityType; import net.minecraft.entity.MobEntity; import net.minecraft.entity.SpawnReason; import net.minecraft.entity.passive.AnimalEntity; import net.minecraft.nbt.CompoundNBT; import net.minecraft.world.DifficultyInstance; import net.minecraft.world.server.ServerWorld; import net.minecraftforge.common.util.FakePlayer; import net.minecraftforge.event.entity.living.BabyEntitySpawnEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.ObfuscationReflectionHelper; public class PAEventHandler { private static final Field loveCauseField = ObfuscationReflectionHelper.findField(AnimalEntity.class, "field_146084_br"); @SubscribeEvent public void babySpawnHandler(final BabyEntitySpawnEvent event) { MobEntity parentA = event.getParentA(); MobEntity parentB = event.getParentB(); MobEntity child = event.getChild(); if (!(parentA instanceof AnimalEntity) || !(parentB instanceof AnimalEntity) || !(child instanceof AnimalEntity)) { return; } AnimalEntity animalParentA = (AnimalEntity) parentA; AnimalEntity animalParentB = (AnimalEntity) parentB; AnimalEntity animalChild = (AnimalEntity) child; ServerWorld server = animalParentA.getServer().overworld(); FakePlayer nullBreeder = FakePlayerHelper.getOrCreate(server, "Breeder 0"); UUID loveUUID; try { loveUUID = (UUID) loveCauseField.get(animalParentA); } catch (IllegalArgumentException | IllegalAccessException e) { e.printStackTrace(); return; } Optional<FakePlayer> optionalPlayer = FakePlayerHelper.getByUUID(server, loveUUID); if (!optionalPlayer.isPresent()) { return; } FakePlayer player = optionalPlayer.get(); CompoundNBT nbtTag = new CompoundNBT(); animalChild.save(nbtTag); nbtTag.remove("UUID"); for (int i = 1; i <= 4; ++i) { if (player == FakePlayerHelper.getOrCreate(server, "Breeder " + i)) { animalParentA.setInLove(nullBreeder); animalParentA.resetLove(); animalParentB.setInLove(nullBreeder); animalParentB.resetLove(); for (int j = 0; j < i; ++j) { Entity babyClone = EntityType.loadEntityRecursive(nbtTag, server, (baby) -> { return baby; }); if (babyClone != null) { DifficultyInstance diff = server.getCurrentDifficultyAt(animalChild.blockPosition()); ((AnimalEntity) babyClone).finalizeSpawn(server, diff, SpawnReason.BREEDING, null, null); if (server.tryAddFreshEntityWithPassengers(babyClone)) { System.out.println("Spawned clone " + j + "!"); } else { System.out.println("Failed to tryAdd clone " + j + "!"); } } else { System.out.println("Failed to spawn clone " + j + "!"); } } break; } } } }
  7. Thank you! That will be very useful! I've updated the reflection name to the SRG name and that part seems to still be working. OK, I've just pushed up a repo: https://github.com/IceMetalPunk/psychic-altars When testing, you'll need to create a "telepathic altar" to trigger the breeding code; which is a multiblock listed in the Patchouli handbook already. It's a Telepathic Altar block on top of any inventory, with the inventory surrounded by 8 omen-matched blocks (that is, omenstone or one of the upgrade omen types). At least one of the cardinal direction omens should be an Efficiency omen, as that's what initiates the cloning behavior. Then put a couple sheep within 1 block of the central altar block, fill the inventory with wheat, and wait for it to breed. You can right-click the altar block to validate the multiblock faster (it'll turn red when it's active). I'd suggest the other 3 upgrade omens should be speed omens, as the base speed is intentionally a slow 60 seconds per operation; with 3 speed omens, you should only need to wait about 15 seconds for it to trigger. The freezing happens once the animals breed from the altar and try to spawn a baby.
  8. Yeah, I'm aware the error handling is terrible; this code is very much the prototyping, "just trying to make it work first, then I'll go back and clean it up later" kind of code. I made all of the changes you mentioned, except using the SRG name; how would I find that? I used to use MCPBot, but it seems that hasn't updated past 1.15 and loveCause isn't in those mappings. I checked in the .gradle folder and eventually found joined.tsrg, but that doesn't contain the deobfuscated names, so I'm not sure how to locate this particular field's SRG name in it. More importantly, I switched over to using EntityType.create instead of calling the entity constructor directly, and it didn't help: the game still locks up as soon as it tries to create the entity. This is the new code in the event handler (I'm fully aware there are one or two now-unused variables); I've tried it with and without the world.addFreshEntity line, and both still lock up (I get the feeling it's freezing on creation of the new entity, before it even gets to that line anyway, but I could be wrong). package com.icemetalpunk.psychicaltars.events; import java.lang.reflect.Field; import java.util.Optional; import java.util.UUID; import com.icemetalpunk.psychicaltars.helpers.FakePlayerHelper; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityType; import net.minecraft.entity.MobEntity; import net.minecraft.entity.passive.AnimalEntity; import net.minecraft.nbt.CompoundNBT; import net.minecraft.world.server.ServerWorld; import net.minecraftforge.common.util.FakePlayer; import net.minecraftforge.event.entity.living.BabyEntitySpawnEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.ObfuscationReflectionHelper; public class PAEventHandler { private static final Field loveCauseField = ObfuscationReflectionHelper.findField(AnimalEntity.class, "loveCause"); @SubscribeEvent public void babySpawnHandler(final BabyEntitySpawnEvent event) { System.out.println("In handler!"); MobEntity parentA = event.getParentA(); MobEntity parentB = event.getParentB(); MobEntity child = event.getChild(); if (!(parentA instanceof AnimalEntity) || !(parentB instanceof AnimalEntity) || !(child instanceof AnimalEntity)) { return; } AnimalEntity animalParentA = (AnimalEntity) parentA; AnimalEntity animalParentB = (AnimalEntity) parentB; AnimalEntity animalChild = (AnimalEntity) child; ServerWorld world = animalParentA.getServer().overworld(); FakePlayer nullBreeder = FakePlayerHelper.getOrCreate(world, "Breeder 0"); System.out.println("Is Animal in world " + world); UUID loveUUID; try { loveUUID = (UUID) loveCauseField.get(animalParentA); } catch (IllegalArgumentException | IllegalAccessException e) { e.printStackTrace(); return; } System.out.println("UUID: " + loveUUID); Optional<FakePlayer> optionalPlayer = FakePlayerHelper.getByUUID(world, loveUUID); if (!optionalPlayer.isPresent()) { System.out.println("Not present"); return; } FakePlayer player = optionalPlayer.get(); System.out.println("Player: " + player.getName()); CompoundNBT nbtTag = new CompoundNBT(); animalChild.save(nbtTag); nbtTag.remove("UUID"); EntityType<?> type = animalChild.getType(); for (int i = 1; i <= 4; ++i) { if (player == FakePlayerHelper.getOrCreate(world, "Breeder " + i)) { System.out.println("Is Breeder " + i); animalParentA.setInLove(nullBreeder); animalParentA.resetLove(); animalParentB.setInLove(nullBreeder); animalParentB.resetLove(); for (int j = 0; j < i; ++i) { Optional<Entity> babyClone = EntityType.create(nbtTag.copy(), world); babyClone.ifPresent(baby -> world.addFreshEntity(baby)); } break; } } } }
  9. Taking your advice, I refactored it all. I made a FakePlayerHelper class that keeps track of all the fake players, constructing the UUIDs based on the name rather than being random, reusing GameProfiles for the same names when constructing different FakePlayers for different worlds, etc. Then I used reflection in the event handler to pull the UUID from the private field in AnimalEntity directly and look that up via the helper's maps. That solved the NPEs... but now, when a baby sheep is born and triggers the BabyEntitySpawnEvent handler code, the whole game slowly locks up. As in, all entities freeze except the player, no new entities can spawn, and then eventually the player locks up, too, and I have to force quit. No errors thrown, just freezing. Here's the new code, including all the debug output. FakePlayerHelper class: package com.icemetalpunk.psychicaltars.helpers; import java.util.HashMap; import java.util.Optional; import java.util.UUID; import org.apache.commons.lang3.tuple.Pair; import com.mojang.authlib.GameProfile; import net.minecraft.world.server.ServerWorld; import net.minecraftforge.common.util.FakePlayer; public class FakePlayerHelper { private static HashMap<Pair<ServerWorld, String>, FakePlayer> FAKE_PLAYERS = new HashMap<>(); private static HashMap<UUID, GameProfile> UUID_PROFILE_MAP = new HashMap<>(); private static HashMap<Pair<ServerWorld, UUID>, FakePlayer> UUID_PLAYER_MAP = new HashMap<>(); public static FakePlayer getOrCreate(ServerWorld world, String name) { if (FAKE_PLAYERS.containsKey(Pair.of(world, name))) { return FAKE_PLAYERS.get(Pair.of(world, name)); } UUID uuid = UUID.nameUUIDFromBytes(name.getBytes()); GameProfile profile = getGameProfileFromUUID(uuid).orElseGet(() -> { return new GameProfile(uuid, name); }); FakePlayer player = new FakePlayer(world, profile); FAKE_PLAYERS.put(Pair.of(world, name), player); UUID_PROFILE_MAP.put(uuid, profile); UUID_PLAYER_MAP.put(Pair.of(world, uuid), player); return player; } // Below is just an alias for better semantics when you don't want to store the result public static FakePlayer create(ServerWorld world, String name) { return getOrCreate(world, name); } private static Optional<GameProfile> getGameProfileFromUUID(UUID uuid) { return Optional.ofNullable(UUID_PROFILE_MAP.get(uuid)); } public static Optional<FakePlayer> getByUUID(ServerWorld world, UUID uuid) { return Optional.ofNullable(UUID_PLAYER_MAP.get(Pair.of(world, uuid))); } } FMLServerStarting event handler: @SubscribeEvent public void onServerStarting(FMLServerStartingEvent event) { event.getServer().getAllLevels().forEach(world -> { FakePlayerHelper.create(world, "Breeder 0"); FakePlayerHelper.create(world, "Breeder 1"); FakePlayerHelper.create(world, "Breeder 2"); FakePlayerHelper.create(world, "Breeder 3"); FakePlayerHelper.create(world, "Breeder 4"); }); } Event handler class: package com.icemetalpunk.psychicaltars.events; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.util.Optional; import java.util.UUID; import com.icemetalpunk.psychicaltars.helpers.FakePlayerHelper; import net.minecraft.entity.EntityType; import net.minecraft.entity.MobEntity; import net.minecraft.entity.passive.AnimalEntity; import net.minecraft.nbt.CompoundNBT; import net.minecraft.world.World; import net.minecraft.world.server.ServerWorld; import net.minecraftforge.common.util.FakePlayer; import net.minecraftforge.event.entity.living.BabyEntitySpawnEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.ObfuscationReflectionHelper; public class PAEventHandler { private static Field loveCauseField = ObfuscationReflectionHelper.findField(AnimalEntity.class, "loveCause"); @SubscribeEvent public void babySpawnHandler(final BabyEntitySpawnEvent event) { System.out.println("In handler!"); MobEntity parentA = event.getParentA(); MobEntity parentB = event.getParentB(); MobEntity child = event.getChild(); if (!(parentA instanceof AnimalEntity) || !(parentB instanceof AnimalEntity) || !(child instanceof AnimalEntity)) { return; } AnimalEntity animalParentA = (AnimalEntity) parentA; AnimalEntity animalParentB = (AnimalEntity) parentB; AnimalEntity animalChild = (AnimalEntity) child; ServerWorld world = animalParentA.getServer().overworld(); FakePlayer nullBreeder = FakePlayerHelper.getOrCreate(world, "Breeder 0"); System.out.println("Is Animal in world " + world); UUID loveUUID; try { loveUUID = (UUID) loveCauseField.get(animalParentA); } catch (IllegalArgumentException | IllegalAccessException e) { e.printStackTrace(); return; } System.out.println("UUID: " + loveUUID); Optional<FakePlayer> optionalPlayer = FakePlayerHelper.getByUUID(world, loveUUID); if (!optionalPlayer.isPresent()) { System.out.println("Not present"); return; } FakePlayer player = optionalPlayer.get(); System.out.println("Player: " + player.getName()); CompoundNBT nbtTag = new CompoundNBT(); animalChild.save(nbtTag); nbtTag.remove("UUID"); EntityType<?> type = animalChild.getType(); for (int i = 1; i <= 4; ++i) { if (player == FakePlayerHelper.getOrCreate(world, "Breeder " + i)) { System.out.println("Is Breeder " + i); animalParentA.setInLove(nullBreeder); animalParentA.resetLove(); animalParentB.setInLove(nullBreeder); animalParentB.resetLove(); for (int j = 0; j < i; ++i) { AnimalEntity babyClone; try { babyClone = animalChild.getClass().getConstructor(EntityType.class, World.class) .newInstance(type, world); babyClone.load(nbtTag.copy()); world.addFreshEntity(babyClone); } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) { e.printStackTrace(); return; } } break; } } } } The output I'm getting properly recognizes and reaches "Breeder 1" (the first println within the outer 'for' loop in the event handler), but then I get no more output and everything starts locking up. Am I not cloning the baby animal correctly?
  10. I hate having to ask so many questions here in such a short time, but I tried to figure this one out on my own and hit a wall. I have a FakePlayer that I create in the FMLServerStartingEvent, and I have a block that breeds animals using the FakePlayer as their "love cause" via AnimalEntity#setInLove. This is all working, and the animals are breeding fine. However, when a baby is born, I'm hooking into the BabyEntitySpawnEvent and trying to check whether either of the parents was bred by this FakePlayer, and if so, do some stuff (specifically, spawn a clone of the baby). This wasn't working; at first, no errors, it just wouldn't spawn the extra baby. So I started debugging with some System.out.printlns, and boom, it started crashing with an NPE when trying to work with the event's getCausedByPlayer() value. I dug in some more, and after some breakpoints and debugging, I found that while the UUID of the FakePlayer is properly being set as the animal's loveCause value, the getInLoveCause() method is returning null anyway because apparently the FakePlayer is not actually in the world's players list; when I test with just me in a singleplayer world, there is only one player on the list with a different UUID than the fake player (which, I have to assume, is me). Since a player with a matching UUID can't be found on the list, it returns null instead of the correct FakePlayer entity. I thought the FakePlayer would be added to the world automatically on construction, since the world is passed to the constructor, but no big deal, right? I just added a world.addNewPlayer call in the FMLServerStartingEvent, right after constructing it, to add the FakePlayer to the world list. Except... now, Curios (which my mod supports) crashes with an NPE because apparently it's getting an EntityJoinWorld event for this fake player that doesn't have all the connection fields it's expecting (at least, it's something like that from what I can tell). So I guess what I'm asking is, "What is the correct way to create a FakePlayer such that it's actually added to the server player list so things like AnimalEntity#getInLoveCause work properly with it?" Here's how I'm currently creating the FakePlayers (FAKE_PLAYERS is a static hashmap of worlds to hashmaps of strings to FakePlayers, so I can look up specific players to indicate different blocks have triggered the breeding; I'm creating a set of FakePlayers per world because I believe that's necessary if I want to be able to handle this FakePlayer breeding in every dimension, but correct me if I'm wrong about that): @SubscribeEvent public void onServerStarting(FMLServerStartingEvent event) { AtomicReference<Integer> i = new AtomicReference<>(0); event.getServer().getAllLevels().forEach(world -> { int index = i.getAndAccumulate(0, (c, v) -> c + 1); HashMap<String, FakePlayer> playerMap = new HashMap<>(); playerMap.put("Breeder_0", new FakePlayer(world, new GameProfile(UUID.randomUUID(), "Breeder_0_" + index))); playerMap.put("Breeder_1", new FakePlayer(world, new GameProfile(UUID.randomUUID(), "Breeder_1_" + index))); playerMap.put("Breeder_2", new FakePlayer(world, new GameProfile(UUID.randomUUID(), "Breeder_2_" + index))); playerMap.put("Breeder_3", new FakePlayer(world, new GameProfile(UUID.randomUUID(), "Breeder_3_" + index))); playerMap.put("Breeder_4", new FakePlayer(world, new GameProfile(UUID.randomUUID(), "Breeder_4_" + index))); FAKE_PLAYERS.put(world, playerMap); for (FakePlayer player : playerMap.values()) { world.addNewPlayer(player); } }); } I saw the FakePlayerFactory class, but from what I can tell all that adds is automatic recordkeeping of the fake players created, and it wouldn't help me here, right?
  11. ๐Ÿคฆโ€โ™‚๏ธ It would be a misunderstanding like that... Thank you, it works perfectly now! You're becoming quite invaluable to my return to modding, and I fully appreciate all your help!
  12. In 1.12, it was easy to have things occur on a block at regular intervals without a block entity: just schedule a block tick, override the block's tick method, and everything worked. I'm trying that now in 1.16.5 and getting nowhere. I have this in my block's class: @Override public void setPlacedBy(World world, BlockPos pos, BlockState state, @Nullable LivingEntity player, ItemStack stack) { super.setPlacedBy(world, pos, state, player, stack); System.out.println("Placed block!"); world.getChunk(pos).getBlockTicks().scheduleTick(pos, this, 1); if (world.getChunk(pos).getBlockTicks().hasScheduledTick(pos, this)) { System.out.println("SCHEDULED TICK"); } else { System.out.println("DID NOT SCHEDULE TICK"); } } It always logs "DID NOT SCHEDULE TICK". I continued testing by implementing the block's tick method, which I saw is now deprecated, to log stuff, and it's never called. Since it's deprecated, it looks like I'm meant to somehow handle block ticks through the block state's tick method, but I'm not sure how to do that since block states go through layers of builders and abstraction. I tried just creating my own class that extends BlockState, except it implements the tick method, and then copying the Block class's constructor's code for registering state except using my custom class, and that also failed to get any results. How am I meant to schedule and handle block ticks in the new version of Forge? I can't even seem to find any documentation on the changes online anywhere.
  13. Ah! Using reflection to prevent crashing is the trick I needed to know! Thank you, I'll work on implementing it when I get a chance; hopefully it should go smoothly, if not, I'll ask more here with more details. EDIT Worked perfectly the first try ๐Ÿ˜ Thank you for your help!
  14. I haven't made a working mod since 1.12, but I'm starting a new one for 1.16.5 and I'd like it to support the Curios API. I've gotten that working, but I realized that in order to register a Curios slot, I need to reference the Curios API SlotTypeMessage class. I was hoping I could make Curios support optional, i.e. register the slot if the Curios mod is installed, or just gracefully ignore it if not, but if I'm referencing SlotTypeMessage and someone installs my mod without including Curios, won't that just crash? Is there something special I need to do to make the support optional? Or am I totally misunderstanding how dependencies are handled and worrying about crashes that won't happen after all?
  15. Client/server sync will be the death of me. I have a block with a tile entity, and a tile entity renderer bound to it. This tile entity has a single item stack as a pseudo-inventory and a fillLevel property. The renderer is supposed to render the item stack (if it's not empty) and then render another block inside this block with a state determined by the fillLevel. At one point, it was mostly working, except that after putting an item in, when the tick method would finish processing it and clear the item (set its item stack to an empty one), it would continue rendering the item. In my attempts to fix that, I somehow broke it more: now it won't render the fillLevel block anymore, either! Debug output from within the TER's render method runs, so it's definitely bound correctly; it just prints out the full item stack even after it should be cleared, which is why I thought (and still think) it's a server/client sync issue. What I don't understand is that if I remove the item "manually" (i.e. on a block activation), it correctly stops rendering, but if the tick method clears the item (using the same method as block activation does!), then it doesn't update. I've been debugging it for days, and I'm not getting anywhere. I discovered that the getUpdateTag and handleUpdateTag methods (which I thought were the bases of client/server sync) are only getting called once when the block is placed and then never again, even as methods are called that update the items or fillLevel. I even created a utility method that calls World#markAndNotifyBlock as well as marking the tile entity dirty -- I thought for SURE that would force a sync -- and it did nothing. Can someone please help me figure out where I'm going wrong? Below are my block, tile entity, renderer, and update helper classes (any questions about other classes referenced, please feel free to ask). ScarletAlchemyHelpers.java public class ScarletAlchemyHelpers { public static void updateTileEntity(TileEntity te) { World world = te.getWorld(); BlockState state = te.getBlockState(); BlockPos pos = te.getPos(); world.markAndNotifyBlock(pos, world.getChunk(pos.getX(), pos.getZ()), state, state, 3); te.markDirty(); } } ScarletCondenserTileEntity.java public class ScarletCondenserTileEntity extends TileEntity implements ITickableTileEntity, ISidedInventory { protected int fillLevel = 0; protected ItemStack inventory = ItemStack.EMPTY; public static final int MAX_FILL = 90; public ScarletCondenserTileEntity() { super(ScarletCondenserBlock.teType); } @Override @Nullable public SUpdateTileEntityPacket getUpdatePacket() { CompoundNBT nbtTagCompound = new CompoundNBT(); this.write(nbtTagCompound); return new SUpdateTileEntityPacket(this.pos, 42, nbtTagCompound); } @Override public void onDataPacket(NetworkManager network, SUpdateTileEntityPacket packet) { this.read(packet.getNbtCompound()); } @Override public CompoundNBT getUpdateTag() { CompoundNBT nbtTagCompound = new CompoundNBT(); this.write(nbtTagCompound); return nbtTagCompound; } @Override public void handleUpdateTag(CompoundNBT tag) { this.read(tag); } @Override public void read(CompoundNBT compound) { this.pos = new BlockPos(compound.getInt("x"), compound.getInt("y"), compound.getInt("z")); this.fillLevel = compound.getInt("fillLevel"); this.inventory = ItemStack.read(compound.getCompound("Item")); } @Override public CompoundNBT write(CompoundNBT compound) { ResourceLocation resourcelocation = TileEntityType.getId(this.getType()); if (resourcelocation == null) { throw new RuntimeException(this.getClass() + " is missing a mapping! This is a bug!"); } else { compound.putString("id", resourcelocation.toString()); compound.putInt("x", this.pos.getX()); compound.putInt("y", this.pos.getY()); compound.putInt("z", this.pos.getZ()); compound.putInt("fillLevel", this.fillLevel); CompoundNBT item = new CompoundNBT(); compound.put("Item", this.inventory.write(item)); return compound; } } public int getFillLevel() { return this.fillLevel; } public int addSmoke(int amount) { int result = Math.min(amount, MAX_FILL - this.fillLevel); this.fillLevel += result; return result; } public int removeSmoke(int amount) { int result = Math.min(amount, this.fillLevel); this.fillLevel -= result; return result; } public int getSmokeState() { if (this.fillLevel <= 0) { return -1; } float smokeAge = 3.0f - (float) this.fillLevel * 3.0f / (float) MAX_FILL; return MathHelper.floor(smokeAge); } protected void collectSmoke(int amount) { Stream<BlockPos> surroundings = BlockPos.getAllInBox(this.pos.add(-1, -1, -1), this.pos.add(1, 1, 1)) .map(BlockPos::toImmutable); int realAmount = Math.min(MAX_FILL - this.fillLevel, amount); List<BlockPos> posList = surroundings.collect(Collectors.toList()); for (BlockPos bPos : posList) { if (bPos.equals(this.pos)) { continue; } TileEntity te = this.world.getTileEntity(bPos); if (!(te instanceof ISASmokeProvider)) { continue; } ISASmokeProvider provider = (ISASmokeProvider) te; if (realAmount > 0) { int taken = provider.removeSmoke(realAmount); realAmount -= taken; this.fillLevel += taken; } } } @Override public void tick() { // FIXME: Fill level and empty state not reflected in renderer :( if (!this.isEmpty()) { Pair<Integer, ItemStack> bestRecipe = ScarletCondenserRecipes.getMostExpensive(this.inventory); if (bestRecipe != null) { if (this.fillLevel < MAX_FILL) { this.collectSmoke(1); ScarletAlchemyHelpers.updateTileEntity(this); } if (this.fillLevel >= MAX_FILL) { int amountUsed = bestRecipe.getLeft(); if (this.inventory.getCount() > amountUsed) { this.inventory.shrink(amountUsed); } else { this.clear(); } this.getWorld().addEntity(new ItemEntity(world, pos.getX() + 0.5, pos.getY() + 1, pos.getZ() + 0.5, bestRecipe.getRight())); this.fillLevel -= MAX_FILL; ScarletAlchemyHelpers.updateTileEntity(this); } } } } @Override public int getSizeInventory() { return 1; } @Override public boolean isEmpty() { return this.inventory == null || this.inventory.isEmpty(); } @Override public ItemStack getStackInSlot(int index) { if (index != 0) { return ItemStack.EMPTY; } return this.inventory; } @Override public ItemStack decrStackSize(int index, int count) { if (index != 0) { return ItemStack.EMPTY; } this.inventory.shrink(count); return this.inventory; } @Override public ItemStack removeStackFromSlot(int index) { if (index != 0) { return ItemStack.EMPTY; } ItemStack temp = this.inventory; this.inventory = ItemStack.EMPTY; ScarletAlchemyHelpers.updateTileEntity(this); return temp; } @Override public void setInventorySlotContents(int index, ItemStack stack) { if (index == 0) { this.inventory = stack.copy(); ScarletAlchemyHelpers.updateTileEntity(this); } } public boolean addItem(ItemStack stack, PlayerEntity player) { Item item = stack.getItem(); if (!ScarletCondenserRecipes.hasRecipe(item)) { if (item == Items.AIR && !this.isEmpty() && player.isCrouching()) { World world = this.getWorld(); BlockPos pos = this.getPos(); world.addEntity( new ItemEntity(world, pos.getX() + 0.5, pos.getY() + 1, pos.getZ() + 0.5, this.inventory)); this.fillLevel = 0; this.clear(); ScarletAlchemyHelpers.updateTileEntity(this); return true; } return false; } else if (this.canInsertItem(0, stack, Direction.NORTH)) { ItemStack current = stack.copy(); current.grow(this.inventory.getCount()); this.setInventorySlotContents(0, current); if (!player.isCreative()) { stack.shrink(stack.getCount()); } ScarletAlchemyHelpers.updateTileEntity(this); return true; } return false; } @Override public boolean isUsableByPlayer(PlayerEntity player) { return true; } @Override public void clear() { this.inventory = ItemStack.EMPTY; ScarletAlchemyHelpers.updateTileEntity(this); } @Override public CompoundNBT serializeNBT() { return this.write(new CompoundNBT()); } @Override public void deserializeNBT(CompoundNBT nbt) { this.read(nbt); ; } @Override public int[] getSlotsForFace(Direction side) { return new int[] { 0 }; } @Override public boolean canInsertItem(int index, ItemStack itemStackIn, Direction direction) { if (index != 0) { return false; } if (this.isEmpty()) { return true; } int maxSize = this.inventory.getMaxStackSize(); return itemStackIn.isItemEqual(this.inventory) && itemStackIn.getCount() + this.inventory.getCount() <= maxSize; } @Override public boolean canExtractItem(int index, ItemStack stack, Direction direction) { if (index != 0) { return false; } return !this.isEmpty() && ItemStack.areItemStacksEqual(this.inventory, stack); } } ScarletCondenserTileEntityRenderer.java public class ScarletCondenserTileEntityRenderer extends TileEntityRenderer<ScarletCondenserTileEntity> { ItemEntity itemEnt = new ItemEntity(null, 0, 0, 0, ItemStack.EMPTY); double rotation = 0.0f; public ScarletCondenserTileEntityRenderer(TileEntityRendererDispatcher rendererDispatcherIn) { super(rendererDispatcherIn); } @Override public void render(ScarletCondenserTileEntity tileEntity, float partialTicks, MatrixStack matrixStack, IRenderTypeBuffer buffer, int combinedLight, int combinedOverlay) { matrixStack.push(); if (!tileEntity.isEmpty()) { // Logger.getLogger(ScarletAlchemy.MOD_ID).log(Level.WARNING, "THEDEBUGGER_RENDERER: Not empty = " + tileEntity.getStackInSlot(0)); matrixStack.push(); itemEnt.setWorld(tileEntity.getWorld()); itemEnt.setItem(tileEntity.getStackInSlot(0)); rotation = MathHelper.wrapDegrees(rotation + 0.025); matrixStack.translate(0.5d, 0.3d, 0.5d); matrixStack.rotate(Vector3f.YP.rotation((float) rotation)); Minecraft.getInstance().getRenderManager().getRenderer(itemEnt).render(itemEnt, 0.0f, partialTicks, matrixStack, buffer, combinedLight); matrixStack.pop(); } matrixStack.scale(0.98f, 0.98f, 0.98f); matrixStack.translate(0.01f, 0.01f, 0.01f); int smokeStateAge = tileEntity.getSmokeState(); if (smokeStateAge >= 0) { BlockState smokeState = ScarletAlchemyBlocks.scarlet_smoke.getDefaultState().with(ScarletSmokeBlock.AGE, smokeStateAge); Minecraft.getInstance().getBlockRendererDispatcher().renderBlock(smokeState, matrixStack, buffer, combinedLight, combinedOverlay, null); } matrixStack.pop(); } } ScarletCondenserBlock.java public class ScarletCondenserBlock extends Block implements SABlock, SATileEntityProvider, ISidedInventoryProvider { protected SABlockAbilities blockAbilities = new SABlockAbilities(this); public static TileEntityType<ScarletCondenserTileEntity> teType; public ScarletCondenserBlock() { super(Block.Properties.create(Material.GLASS).sound(SoundType.GLASS).notSolid()); this.setRegistryName(new ResourceLocation(ScarletAlchemy.MOD_ID, "scarlet_condenser")); } @Override public boolean hasTileEntity(BlockState state) { return true; } @Override public boolean hasBlockItem() { return this.blockAbilities.hasBlockItem(); } @Override public BlockItem createBlockItem() { return this.blockAbilities.createBlockItem(new Item.Properties().maxStackSize(64)); } @Override public void registerItem(IForgeRegistry<Item> reg) { this.blockAbilities.registerItem(reg); } @OnlyIn(Dist.CLIENT) public float getAmbientOcclusionLightValue(BlockState state, IBlockReader worldIn, BlockPos pos) { return 1.0F; } public boolean propagatesSkylightDown(BlockState state, IBlockReader reader, BlockPos pos) { return true; } public boolean causesSuffocation(BlockState p_229869_1_, IBlockReader p_229869_2_, BlockPos p_229869_3_) { return false; } public boolean isNormalCube(BlockState state, IBlockReader worldIn, BlockPos pos) { return false; } @Override public ActionResultType onBlockActivated(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult rayTraceResult) { TileEntity te = world.getTileEntity(pos); if (!(te instanceof ScarletCondenserTileEntity)) { return ActionResultType.PASS; } ScarletCondenserTileEntity scTileEntity = (ScarletCondenserTileEntity) te; ItemStack stack = player.getHeldItem(hand); if (scTileEntity.addItem(stack, player)) { return ActionResultType.CONSUME; } return ActionResultType.PASS; } @Override public void registerTileEntity(IForgeRegistry<TileEntityType<? extends TileEntity>> reg) { teType = TileEntityType.Builder.create(() -> new ScarletCondenserTileEntity(), this).build(null); teType.setRegistryName(this.getRegistryName()); reg.register(teType); } @Override public TileEntity createTileEntity(final BlockState state, final IBlockReader world) { return new ScarletCondenserTileEntity(); } @Override public ISidedInventory createInventory(BlockState state, IWorld world, BlockPos pos) { TileEntity te = world.getTileEntity(pos); if (te != null && te instanceof ScarletCondenserTileEntity) { return (ScarletCondenserTileEntity) te; } return null; } }
  16. Well, after hours of scratching my head and debugging, I figured it out. I was creating the tile entity type manually rather than using the builder, and apparently that caused all kinds of problems with how the generic parameters were being interpreted. I switched to using the builder and now everything works. Whoo!
  17. I'm trying to bind a simple TER to a tile entity of mine. Just as a test/learning exercise, I've created a blank TER that has an empty render method, and I'm trying to bind it to an existing tile entity in my mod. But Eclipse is complaining that the renderer type isn't correct. I have a tile entity called RedstoneVaporizerTileEntity, which extends the base TileEntity class. Then I have this TER (ignore the naming, as I said it's just a test): public class CompressorTileEntityRenderer extends TileEntityRenderer<RedstoneVaporizerTileEntity> { public CompressorTileEntityRenderer(TileEntityRendererDispatcher rendererDispatcherIn) { super(rendererDispatcherIn); } @Override public void render(RedstoneVaporizerTileEntity tileEntityIn, float partialTicks, MatrixStack matrixStackIn, IRenderTypeBuffer bufferIn, int combinedLightIn, int combinedOverlayIn) { } } I'm trying to bind it in the FMLClientSetup event handler like this: ClientRegistry.bindTileEntityRenderer(RedstoneVaporizerBlock.teType, CompressorTileEntityRenderer::new); (The RedstoneVaporizerBlock has a public static field called teType with the registered tile entity type, BTW.) Eclipse is complaining that "The constructed object of type CompressorTileEntityRenderer is incompatible with the descriptor's return type: TileEntityRenderer<? super T>" I don't understand quote what I'm doing wrong? The TER class extends TileEntityRenderer<RedstoneVaporizerTileEntity>, and that TE class extends TileEntity. Isn't that the expected return type? What am I missing?
  18. Yes! That was the missing piece, thank you so much! I could have sworn that method was related to movement, not rendering, probably because the field it clears is also cleared in the doesNotBlockMovement method... I guess it's just by default that things which don't block movement aren't considered to be solid renders, either. Anyway, thanks again, I'm glad this is finally solved
  19. I'm already using the setRenderLayer method, but I'm setting it to the translucent layer. It needs to show the inner contents with an alpha channel (which it now is), so I can't use the cutout layer. I see vanilla stained glass blocks, ice blocks, etc. use the translucent layer as well, so shouldn't that do the job?
  20. Thank you! I think I'm starting to understand how culling and face definitions work a little better now. I've gotten my block to look nearly perfect. The only issue I'm having now is that when I place this block above a solid block, like grass, the block below culls its top face, which you can see through the transparent parts of my block's texture. This doesn't happen with vanilla transparent blocks, like glass, but I can't figure out what I need to specify to stop that from happening with mine. The vanilla glass blocks don't seem to have any special settings in their models for this, so I thought it must be code related, but I've copied the methods from AbstractGlassBlock that I thought were related to rendering, and it still has this issue. Here's the new models: https://www.dropbox.com/s/zl9x3ok7ipx649q/SA_Models_2.zip?dl=1 And here's the code for the ScarletCollectorBlock class: package com.icemetalpunk.scarlet_alchemy.blocks; import java.util.HashSet; import com.icemetalpunk.scarlet_alchemy.ScarletAlchemy; import com.icemetalpunk.scarlet_alchemy.tiles.SATileEntityProvider; import com.icemetalpunk.scarlet_alchemy.tiles.ScarletCollectorTileEntity; import net.minecraft.block.Block; import net.minecraft.block.BlockState; import net.minecraft.block.material.Material; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.BlockItem; import net.minecraft.item.Item; import net.minecraft.state.IntegerProperty; import net.minecraft.state.StateContainer; import net.minecraft.tileentity.TileEntity; import net.minecraft.tileentity.TileEntityType; import net.minecraft.util.ActionResultType; import net.minecraft.util.Hand; import net.minecraft.util.ResourceLocation; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockRayTraceResult; import net.minecraft.world.IBlockReader; import net.minecraft.world.World; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.registries.IForgeRegistry; public class ScarletCollectorBlock extends Block implements SABlock, SATileEntityProvider { /* * FIXME: Model has broken/weird culling, making the filled smoke invisible * among other issues. */ protected SABlockAbilities blockAbilities = new SABlockAbilities(this); public static TileEntityType<?> teType; public static final IntegerProperty SMOKE_LEVEL = IntegerProperty.create("level", 0, 9); public ScarletCollectorBlock() { super(Block.Properties.create(Material.ROCK).hardnessAndResistance(3.5F)); this.setRegistryName(new ResourceLocation(ScarletAlchemy.MOD_ID, "scarlet_collector")); this.setDefaultState(this.stateContainer.getBaseState().with(SMOKE_LEVEL, Integer.valueOf(0))); } @Override protected void fillStateContainer(StateContainer.Builder<Block, BlockState> builder) { builder.add(SMOKE_LEVEL); } @Override public boolean hasTileEntity(BlockState state) { return true; } @Override public TileEntity createTileEntity(final BlockState state, final IBlockReader world) { return new ScarletCollectorTileEntity(); } @OnlyIn(Dist.CLIENT) public float func_220080_a(BlockState state, IBlockReader worldIn, BlockPos pos) { return 1.0F; } public boolean propagatesSkylightDown(BlockState state, IBlockReader reader, BlockPos pos) { return true; } public boolean func_229869_c_(BlockState p_229869_1_, IBlockReader p_229869_2_, BlockPos p_229869_3_) { return false; } public boolean isNormalCube(BlockState state, IBlockReader worldIn, BlockPos pos) { return false; } @Override public ActionResultType func_225533_a_(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult rayTraceResult) { // TOOD: Add right-click functionality? return ActionResultType.PASS; } @Override public boolean hasBlockItem() { return this.blockAbilities.hasBlockItem(); } @Override public BlockItem createBlockItem() { return this.blockAbilities.createBlockItem(new Item.Properties().maxStackSize(64)); } @Override public void registerItem(IForgeRegistry<Item> reg) { this.blockAbilities.registerItem(reg); } @Override public void registerTileEntity(IForgeRegistry<TileEntityType<? extends TileEntity>> reg) { HashSet<Block> blockSet = new HashSet<Block>(); blockSet.add(this); teType = (new TileEntityType<ScarletCollectorTileEntity>(() -> new ScarletCollectorTileEntity(), blockSet, null)).setRegistryName(this.getRegistryName()); reg.register(teType); } } I know some of those methods are missing their mappings; that's how they are in my copy of the AbstractGlassBlock class as well. Not sure why, but it seemed to work for another block of mine (scarlet smoke, a full translucent block), so I copied it over. I wondered if it was related to the material, but I tried using Material.GLASS instead of Material.ROCK and it still culled the top of the block below mine. Any suggestions? EDIT: I also found the isOpaqueCube method, but it seems that method is final and can't be overridden, so I'm not sure if there's something I need to do to work with that or not. I know vanilla glass blocks don't touch that, though, and they work, so... yeah, I'm lost
  21. I'm having some trouble with the model of a new block I've added in a mod I'm developing. The block is basically supposed to look like an upside-down composter in shape, with different textures. It can be filled, which is tied to a tile entity that updates its block state. The F3 menu shows that the block state is updating correctly as it fills, but even at state level=9, it never renders the "scarlet smoke" inside. I've gotten a similar block to work just fine right-side up without transparency, but this one (which uses the "translucent" render layer) isn't rendering the inner contents. (It also looks like there may be some weird rendering along the edge of the open face, but that's more minor.) I basically copied the model for my working right-side-up block, which in turn was slightly modified from the vanilla composter model. I then tried to modify the numbers and such to "flip it" so the open side was at the bottom instead of the top, and that's when things went wrong. I'm not sure if I have culling settings wrong, or if I've miscalculated the numbers for the vertices... I just know I can't get it to render right. Here's the blockstate, model, and texture files: https://www.dropbox.com/s/06vcd8kdd6yi3et/SA_Models.zip?dl=1 Can someone help me figure out where I've gone wrong? Rendering/modeling/art has never been my strong suit in any development...
  22. I was working on my 1.15.2 mod, which so far just has a single block/block item/tile entity. Everything was working fine, and then I edited a texture. Yes, as far as I know, the only thing I changed was a texture. Now, whenever I try to run the client/server, it crashes during startup. Looking through the logs, I see no cause for it; just the usual Yggdrasil warning and then a seemingly random "Dev lost connection", followed by the server shutting down because of the connection loss. Here's the crash log: https://www.dropbox.com/s/rvdl8p1q4kt5j57/EclipseOutput.log?dl=0 Can anyone see what I missed? I've closed Eclipse and re-opened it, with no luck; it still crashes the same way on load every time, before it even gets to the main menu. EDIT: So... finally figured out what I missed. I have no idea how this changed, but somehow Eclipse started running using the runData launch config instead of the runClient. I definitely never intentionally switched that. Mystery (and) problem solved; just switched back to runClient and it all works now. *Sigh*
  23. Unfortunately, I'm not even to the point of registering the serializer yet. I'm getting these errors from Eclipse right in the IDE, long before I even run anything, as soon as I type the code
  24. Even if I do that, I still have the same errors trying to create the serializer for it. Still says the IFactory type is not visible (although parametrized with FuranceRecipe instead of AbstractCookingRecipe now, but the same error otherwise).
×
×
  • Create New...

Important Information

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