-
Posts
266 -
Joined
-
Last visited
Everything posted by Kinniken
-
[1.12] Safe operations to run in seperate threads?
Kinniken replied to Kinniken's topic in Modder Support
Ok, from what I've gathered from other modders, accessing blockstates in loaded chunks is supposedly thread-safe, loading chunks definitely isn't. I've rewritten my code to always check the chunk load status while running threaded code, I'll see if it solves that issue. -
[1.12] Safe operations to run in seperate threads?
Kinniken replied to Kinniken's topic in Modder Support
Of the mod? 45k lines? And even if you mean just the classes involved, the geography one refers to so many other classes it's impossible to post here, and the pathing one is an A* system that's way more complex than this issue. I'm not asking for help debugging my code anyway, I'm asking for info on what is known regarding safe threading behaviour. -
Hi all, I've been debugging some occasional ConcurrentAccessException errors I've been occasionally having in my mod, generally thrown up in the Minecraft code like this: java.util.ConcurrentModificationException at java.util.HashMap$HashIterator.nextNode(HashMap.java:1442) at java.util.HashMap$KeyIterator.next(HashMap.java:1466) at net.minecraft.entity.EntityTracker.func_72788_a(EntityTracker.java:290) at net.minecraft.server.MinecraftServer.func_71190_q(MinecraftServer.java:777) at net.minecraft.server.dedicated.DedicatedServer.func_71190_q(DedicatedServer.java:396) at net.minecraft.server.MinecraftServer.func_71217_p(MinecraftServer.java:666) at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:524) at java.lang.Thread.run(Thread.java:748) I've narrowed it down not to faulty packet handling or GUI updates as I was hoping for, but to either my pathing thread or my "geography analysis" thread. Both do read-only operations on blocks (chunk.getBlockState() mostly), and they do it in separate threads to lessen CPU spikes. In 1.7 that worked fine, in 1.12 it seems to be unsafe. I'm hoping but can't yet prove that the actual issue is that I occasionally force a chunk to load and that this is the unsafe operation (by forcing Minecraft to update its entity list), in which case I could plausibly harden my code to make sure I don't do that. Has anybody done any analysis on this topic? I know Minecraft is horrible at thread-safety, but I'd really like a way to make this work, long path calculations used to cause very annoying CPU spikes before I threaded the whole thing and I'd rather not go back to it. Thanks!
-
[1.12] Half slabs display issue when smooth lighting is off?
Kinniken replied to Kinniken's topic in Modder Support
Thanks, that did it! -
[1.12] ItemStack NBT data synchronised in SP but not MP?
Kinniken replied to Kinniken's topic in Modder Support
There is an NBT on the client-side. I know it's basically a temporary cache for the canonical server version, but well, it does exist -
[1.12] ItemStack NBT data synchronised in SP but not MP?
Kinniken replied to Kinniken's topic in Modder Support
Ultimately that's what I did, there was little point saving it to NBT when the client-side NBT is volatile anyway. But I don't get the point of that "display" compound then. Anyway. -
[1.12] ItemStack NBT data synchronised in SP but not MP?
Kinniken replied to Kinniken's topic in Modder Support
Well yes, that's modding 101. My issue is that the data in question (the stack's custom name) is localization-dependent, and thus cannot be calculated server-side. -
[1.12] ItemStack NBT data synchronised in SP but not MP?
Kinniken replied to Kinniken's topic in Modder Support
Got it working. The issue was that setStackDisplayName() stores the calculated name in the NBT compound itself... which is promptly overwritten by the server-side data coming in. So presumably what Mojang had in mind was calculating the name server-side as well, but how to do that without localization? Instead I'm overriding getItemStackDisplayName() on the client-side and recalculating the name there. But that means doing it each time MC needs it, which is wasteful. Anybody has a better idea? -
[1.12] ItemStack NBT data synchronised in SP but not MP?
Kinniken replied to Kinniken's topic in Modder Support
With this fixed the problem persists, but I see the "right" name flashing. This likely means that it's properly calculated client-side before being overwritten by server data, so most likely my issue now is recalculating my name client-side once I've gotten the server data. Any handle where to do this? -
[1.12] ItemStack NBT data synchronised in SP but not MP?
Kinniken replied to Kinniken's topic in Modder Support
Ok, sorry. Right after posting this I realise that I have errors in my server log linked to this, calls to I18n that fails. Looks like the issue is actually server-side issues due to trying to refer to that and not client-side after all. Will check and adjust this thread after it's clearer. -
Hi all, I have some items where the name of the ItemStack varies based on NBT data stored in the stack, using setStackDisplayName() after some logic based on the content of getTagCompound(). In SP, this works fine. In MP, it doesn't, the name show is what I'd expect if the item's NBT tag was empty. Since it worked in SP without any particular synchronisation logic, I assumed that would be the same in MP. Is this wrong? Do I have to handle the sync myself, and if yes, is there a standard way? Thanks. The full class, if it helps: package org.millenaire.common.item; import org.millenaire.common.utilities.LanguageUtilities; import org.millenaire.common.utilities.MillCommonUtilities; import org.millenaire.common.utilities.WorldUtilities; import net.minecraft.client.resources.I18n; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.ActionResult; import net.minecraft.util.EnumHand; import net.minecraft.world.World; public class ItemPurse extends ItemMill { private static final String ML_PURSE_DENIER = "ml_Purse_DENIER"; private static final String ML_PURSE_DENIERARGENT = "ml_Purse_DENIERargent"; private static final String ML_PURSE_DENIEROR = "ml_Purse_DENIERor"; private static final String ML_PURSE_RAND = "ml_Purse_rand"; public ItemPurse(final String itemName) { super(itemName); setMaxStackSize(1); } @Override public ActionResult<ItemStack> onItemRightClick(final World worldIn, final EntityPlayer playerIn, final EnumHand handIn) { final ItemStack purse = playerIn.getHeldItem(handIn); if (totalDeniers(purse) > 0) { removeDeniersFromPurse(purse, playerIn); } else { storeDeniersInPurse(purse, playerIn); } return super.onItemRightClick(worldIn, playerIn, handIn); } private void removeDeniersFromPurse(final ItemStack purse, final EntityPlayer player) { if (purse.getTagCompound() != null) { final int DENIERs = purse.getTagCompound().getInteger(ML_PURSE_DENIER); final int DENIERargent = purse.getTagCompound().getInteger(ML_PURSE_DENIERARGENT); final int DENIERor = purse.getTagCompound().getInteger(ML_PURSE_DENIEROR); int result = MillCommonUtilities.putItemsInChest(player.inventory, MillItems.DENIER, DENIERs); purse.getTagCompound().setInteger(ML_PURSE_DENIER, DENIERs - result); result = MillCommonUtilities.putItemsInChest(player.inventory, MillItems.DENIER_ARGENT, DENIERargent); purse.getTagCompound().setInteger(ML_PURSE_DENIERARGENT, DENIERargent - result); result = MillCommonUtilities.putItemsInChest(player.inventory, MillItems.DENIER_OR, DENIERor); purse.getTagCompound().setInteger(ML_PURSE_DENIEROR, DENIERor - result); purse.getTagCompound().setInteger(ML_PURSE_RAND, player.world.isRemote ? 0 : 1); setItemName(purse); } } public void setDeniers(final ItemStack purse, final EntityPlayer player, final int amount) { final int denier = amount % 64; final int denier_argent = ((amount - denier) / 64) % 64; final int denier_or = (amount - denier - (denier_argent * 64)) / (64 * 64); setDeniers(purse, player, denier, denier_argent, denier_or); } public void setDeniers(final ItemStack purse, final EntityPlayer player, final int DENIER, final int DENIERargent, final int DENIERor) { if (purse.getTagCompound() == null) { purse.setTagCompound(new NBTTagCompound()); } purse.getTagCompound().setInteger(ML_PURSE_DENIER, DENIER); purse.getTagCompound().setInteger(ML_PURSE_DENIERARGENT, DENIERargent); purse.getTagCompound().setInteger(ML_PURSE_DENIEROR, DENIERor); purse.getTagCompound().setInteger(ML_PURSE_RAND, player.world.isRemote ? 0 : 1); setItemName(purse); } private void setItemName(final ItemStack purse) { if (purse.getTagCompound() == null) { purse.setStackDisplayName(I18n.format(MillItems.PURSE.getUnlocalizedName()+".name")); } else { final int DENIERs = purse.getTagCompound().getInteger(ML_PURSE_DENIER); final int DENIERargent = purse.getTagCompound().getInteger(ML_PURSE_DENIERARGENT); final int DENIERor = purse.getTagCompound().getInteger(ML_PURSE_DENIEROR); String label = ""; if (DENIERor != 0) { label = "\247" + LanguageUtilities.YELLOW + DENIERor + "o "; } if (DENIERargent != 0) { label += "\247" + LanguageUtilities.WHITE + DENIERargent + "a "; } if ((DENIERs != 0) || (label.length() == 0)) { label += "\247" + LanguageUtilities.ORANGE + DENIERs + "d"; } label = label.trim(); purse.setStackDisplayName("\247" + LanguageUtilities.WHITE + I18n.format(MillItems.PURSE.getUnlocalizedName()+".name") + ": " + label); } } private void storeDeniersInPurse(final ItemStack purse, final EntityPlayer player) { final int deniers = WorldUtilities.getItemsFromChest(player.inventory, MillItems.DENIER, 0, Integer.MAX_VALUE); final int deniersargent = WorldUtilities.getItemsFromChest(player.inventory, MillItems.DENIER_ARGENT, 0, Integer.MAX_VALUE); final int deniersor = WorldUtilities.getItemsFromChest(player.inventory, MillItems.DENIER_OR, 0, Integer.MAX_VALUE); final int total = totalDeniers(purse) + deniers + (deniersargent * 64) + (deniersor * 64 * 64); final int new_denier = total % 64; final int new_deniers_argent = ((total - new_denier) / 64) % 64; final int new_deniers_or = (total - new_denier - (new_deniers_argent * 64)) / (64 * 64); setDeniers(purse, player, new_denier, new_deniers_argent, new_deniers_or); } public int totalDeniers(final ItemStack purse) { if (purse.getTagCompound() == null) { return 0; } final int deniers = purse.getTagCompound().getInteger(ML_PURSE_DENIER); final int denier_argent = purse.getTagCompound().getInteger(ML_PURSE_DENIERARGENT); final int denier_or = purse.getTagCompound().getInteger(ML_PURSE_DENIEROR); return deniers + (denier_argent * 64) + (denier_or * 64 * 64); } }
-
Hi all, I have custom slabs that should normally behave exactly like normal ones as far as the display is concerned. When smooth lightning is on (minimum or maximum), it works fine. When smooth lightning is off, the top part of the slab (or the bottom if it's a top slab) is shown as a dark area. Here is my class: package org.millenaire.common.block; import java.util.Random; import org.millenaire.common.forge.Mill; import org.millenaire.common.item.IMetaBlockName; import net.minecraft.block.Block; import net.minecraft.block.BlockSlab; import net.minecraft.block.SoundType; import net.minecraft.block.material.MapColor; import net.minecraft.block.material.Material; import net.minecraft.block.properties.IProperty; import net.minecraft.block.properties.PropertyBool; import net.minecraft.block.properties.PropertyEnum; import net.minecraft.block.state.BlockStateContainer; import net.minecraft.block.state.IBlockState; import net.minecraft.client.renderer.block.model.ModelResourceLocation; import net.minecraft.entity.EntityLivingBase; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.util.EnumFacing; import net.minecraft.util.IStringSerializable; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; import net.minecraftforge.client.model.ModelLoader; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; public abstract class BlockPath extends BlockSlab implements IMetaBlockName { public static class BlockPathDouble extends BlockPath { public BlockPathDouble(final String blockName, MapColor color) { super(blockName, color); } @Override public boolean isDouble() { return true; } } public static class BlockPathSlab extends BlockPath { public BlockPathSlab(final String blockName, MapColor color) { super(blockName, color); } @Override public boolean isDouble() { return false; } } public static enum Variant implements IStringSerializable { DEFAULT; @Override public String getName() { return "default"; } } //Variant with only default as slab-related blocks need them public static final PropertyEnum<Variant> VARIANT = PropertyEnum.<Variant>create("variant", Variant.class); //whether the path is "stable", i.e. villagers won't clear it. All player-laid pathes are stable. public static final PropertyBool STABLE = PropertyBool.create("stable"); private final String singleSlabBlockName, doubleSlabName; /** * singleSlabBlockName to have a reference to it for item drops */ public BlockPath(final String blockName, MapColor color) { super(Material.GROUND, color); singleSlabBlockName = blockName + "_slab"; doubleSlabName = blockName; //same for single and double slabs setUnlocalizedName(Mill.MODID + "." + doubleSlabName); if (this.isDouble()) { setRegistryName(doubleSlabName); } else { setRegistryName(singleSlabBlockName); } IBlockState iblockstate = this.blockState.getBaseState(); iblockstate = iblockstate.withProperty(STABLE, false); if (!this.isDouble()) { iblockstate = iblockstate.withProperty(HALF, BlockSlab.EnumBlockHalf.BOTTOM); } this.setDefaultState(iblockstate); setHarvestLevel("shovel", 0); setHardness(0.8F); setCreativeTab(MillBlocks.tabMillenaire); setSoundType(SoundType.GROUND); } @Override protected BlockStateContainer createBlockState() { return this.isDouble() ? new BlockStateContainer(this, new IProperty[] { VARIANT, STABLE }) : new BlockStateContainer(this, new IProperty[] { VARIANT, HALF, STABLE }); } /** * Gets the metadata of the item this Block can drop. This method is called when * the block gets destroyed. It returns the metadata of the dropped item based * on the old metadata of the block. */ @Override public int damageDropped(final IBlockState state) { return 0; } @Override public ItemStack getItem(final World worldIn, final BlockPos pos, final IBlockState state) { return new ItemStack(Block.getBlockFromName(Mill.MODID+":"+singleSlabBlockName), 1, 0); } /** * Unlike regular slabs we drop the version corresponding to the actual block */ @Override public Item getItemDropped(final IBlockState state, final Random rand, final int fortune) { if (isDouble()) return Item.getItemFromBlock(Block.getBlockFromName(Mill.MODID+":"+doubleSlabName)); else return Item.getItemFromBlock(Block.getBlockFromName(Mill.MODID+":"+singleSlabBlockName)); } /** * Convert the BlockState into the correct metadata value */ @Override public int getMetaFromState(final IBlockState state) { int i = 0; if (state.getValue(STABLE)) { i |= 1; } if (!this.isDouble() && (state.getValue(HALF) == BlockSlab.EnumBlockHalf.TOP)) { i |= 8; } return i; } /** * Convert the given metadata into a BlockState for this Block */ @Override public IBlockState getStateFromMeta(final int meta) { IBlockState iblockstate = this.getDefaultState().withProperty(VARIANT, Variant.DEFAULT); if ((meta & 1) == 1) { iblockstate = iblockstate.withProperty(STABLE, true); } if (!this.isDouble()) { iblockstate = iblockstate.withProperty(HALF, (meta & 8) == 0 ? BlockSlab.EnumBlockHalf.BOTTOM : BlockSlab.EnumBlockHalf.TOP); } return iblockstate; } @Override public String getUnlocalizedName(int meta) { //we don't actually have variants for this block return getUnlocalizedName(); } @SideOnly(Side.CLIENT) public void initModel() { if (isDouble()) { ModelLoader.setCustomModelResourceLocation(Item.getItemFromBlock(this), 0, new ModelResourceLocation(getRegistryName(), "stable=false,variant=default")); } else { ModelLoader.setCustomModelResourceLocation(Item.getItemFromBlock(this), 0, new ModelResourceLocation(getRegistryName(), "half=bottom,stable=false,variant=default")); } } @Override public Comparable<?> getTypeForItem(final ItemStack stack) { return Variant.DEFAULT; } @Override public IProperty<?> getVariantProperty() { return VARIANT; } @Override public String getSpecialName(final ItemStack stack) { //we don't actually have variants for this block return getUnlocalizedName(); } /* * Gets the single slab of this block, whether or not it is the single block itself */ public BlockPath getSingleSlab() { return (BlockPath)Block.getBlockFromName(Mill.MODID+":"+singleSlabBlockName); } /* * Gets the double slab of this block, whether or not it is the double block itself */ public BlockPath getDoubleSlab() { return (BlockPath)Block.getBlockFromName(Mill.MODID+":"+doubleSlabName); } /* * Unlike normal slabs we always drop 1 since double blocks drop a full block */ public int quantityDropped(Random random) { return 1; } /* * Overriding the one from BlockSlab that can't deal without double slabs being set directly * */ @Override public IBlockState getStateForPlacement(World worldIn, BlockPos pos, EnumFacing facing, float hitX, float hitY, float hitZ, int meta, EntityLivingBase placer) { if (this.isDouble()) { return getStateFromMeta(meta).withProperty(STABLE, true); } else { IBlockState iblockstate = super.getStateForPlacement(worldIn, pos, facing, hitX, hitY, hitZ, meta, placer).withProperty(HALF, BlockSlab.EnumBlockHalf.BOTTOM).withProperty(STABLE, true); return facing != EnumFacing.DOWN && (facing == EnumFacing.UP || (double)hitY <= 0.5D) ? iblockstate : iblockstate.withProperty(HALF, BlockSlab.EnumBlockHalf.TOP); } } } One of my blockstate: { "forge_marker": 1, "variants": { "half=bottom,stable=false,variant=default": { "model": "millenaire:pathsandstone_bottom" }, "half=top,stable=false,variant=default": { "model": "millenaire:pathsandstone_top" }, "half=bottom,stable=true,variant=default": { "model": "millenaire:pathsandstone_bottom" }, "half=top,stable=true,variant=default": { "model": "millenaire:pathsandstone_top" } } } And one of my block model: { "parent": "block/half_slab", "textures": { "bottom": "millenaire:blocks/pathbottom", "top": "millenaire:blocks/pathsandstone", "side": "millenaire:blocks/pathsandstone_halfside", "particle" : "millenaire:blocks/pathsandstone" } } Those two JSONs are pretty much identical to those used by Minecraft's slabs, with just particles added to the model. Must be something stupid, but what? Any ideas? Thanks!
-
[1.12] Issues with the spawning painting-like entites
Kinniken replied to Kinniken's topic in Modder Support
I had to update that class so much for 1.12 I must have removed the equivalent mechanism by mistake. Well, if it's such a simple fix didn't matter. What annoys me is that I had carefully checked the position of the entity for that kind of issues and forgot the facing... -
[1.12] Issues with the spawning painting-like entites
Kinniken replied to Kinniken's topic in Modder Support
I knew it must be something obvious... Thanks a lot! I'll test that tonight. No idea how it was in 1.7 where I did not have that issue, weird. -
Hi all, I'm having issues with creating painting-like entities. Basically I'm trying something very close to vanilla paintings, with an entity extending EntityHanging. They seem to spawn properly server-side but they are not visible client-side; from the debugging I've done it seems they are sent to the client and even the added spawn data is read, but the position of the entity stays set at 0/0/0 client-side. Here is my code: Entity : package org.millenaire.common.entity; import java.io.IOException; import java.util.ArrayList; import org.millenaire.common.config.MillConfigValues; import org.millenaire.common.forge.Mill; import org.millenaire.common.item.MillItems; import org.millenaire.common.utilities.BlockItemUtilities; import org.millenaire.common.utilities.MillLog; import org.millenaire.common.utilities.Point; import org.millenaire.common.utilities.WorldUtilities; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufInputStream; import io.netty.buffer.ByteBufOutputStream; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityHanging; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.init.SoundEvents; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.EnumFacing; import net.minecraft.util.ResourceLocation; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; public class EntityWallDecoration extends EntityHanging implements IEntityAdditionalSpawnData { public static enum EnumWallDecoration { Griffon("Griffon", 16, 16, 0, 0, NORMAN_TAPESTRY), Oiseau("Oiseau", 16, 16, 16, 0, NORMAN_TAPESTRY), CorbeauRenard("CorbeauRenard", 2 * 16, 16, 2 * 16, 0, NORMAN_TAPESTRY), Serment( "Serment", 5 * 16, 3 * 16, 0, 16, NORMAN_TAPESTRY), MortHarold("MortHarold", 4 * 16, 3 * 16, 5 * 16, 16, NORMAN_TAPESTRY), Drakar( "Drakar", 6 * 16, 3 * 16, 9 * 16, 16, NORMAN_TAPESTRY), MontStMichel("MontStMichel", 3 * 16, 2 * 16, 0, 4 * 16, NORMAN_TAPESTRY), Bucherons("Bucherons", 3 * 16, 2 * 16, 3 * 16, 4 * 16, NORMAN_TAPESTRY), Cuisine("Cuisine", 3 * 16, 2 * 16, 6 * 16, 4 * 16, NORMAN_TAPESTRY), Flotte("Flotte", 15 * 16, 3 * 16, 0, 6 * 16, NORMAN_TAPESTRY), Chasse( "Chasse", 6 * 16, 3 * 16, 0, 9 * 16, NORMAN_TAPESTRY), Siege("Siege", 16 * 16, 3 * 16, 0, 12 * 16, NORMAN_TAPESTRY), Ganesh("Ganesh", 2 * 16, 3 * 16, 0, 0, INDIAN_STATUE), Kali("Kali", 2 * 16, 3 * 16, 2 * 16, 0, INDIAN_STATUE), Shiva("Shiva", 2 * 16, 3 * 16, 4 * 16, 0, INDIAN_STATUE), Osiyan("Osiyan", 2 * 16, 3 * 16, 6 * 16, 0, INDIAN_STATUE), Durga("Durga", 2 * 16, 3 * 16, 8 * 16, 0, INDIAN_STATUE), MayanTeal("MayanTeal", 2 * 16, 2 * 16, 0, 3 * 16, MAYAN_STATUE), MayanGold("MayanGold", 2 * 16, 2 * 16, 2 * 16, 3 * 16, MAYAN_STATUE), LargeJesus("LargeJesus", 2 * 16, 3 * 16, 0, 5 * 16, BYZANTINE_ICON_LARGE), LargeVirgin("LargeVirgin", 2 * 16, 3 * 16, 2 * 16, 5 * 16, BYZANTINE_ICON_LARGE), MediumVirgin1("MediumVirgin1", 2 * 16, 2 * 16, 0, 8 * 16, BYZANTINE_ICON_MEDIUM), MediumVirgin2("MediumVirgin2", 2 * 16, 2 * 16, 2 * 16, 8 * 16, BYZANTINE_ICON_MEDIUM), SmallJesus("SmallJesus", 16, 16, 0, 10 * 16, BYZANTINE_ICON_SMALL), SmallVirgin1("SmallVirgin1", 16, 16, 16, 10 * 16, BYZANTINE_ICON_SMALL), SmallVirgin2("SmallVirgin2", 16, 16, 2 * 16, 10 * 16, BYZANTINE_ICON_SMALL), SmallVirgin3("SmallVirgin3", 16, 16, 3 * 16, 10 * 16, BYZANTINE_ICON_SMALL); public static final int maxArtTitleLength = "SkullAndRoses".length(); public final String title; public final int sizeX; public final int sizeY; public final int offsetX; public final int offsetY; public final int type; private EnumWallDecoration(final String s1, final int j, final int k, final int l, final int i1, final int type) { title = s1; sizeX = j; sizeY = k; offsetX = l; offsetY = i1; this.type = type; } } public static final ResourceLocation WALL_DECORATION = new ResourceLocation(Mill.MODID, "WallDecoration"); public static final int NORMAN_TAPESTRY = 1; public static final int INDIAN_STATUE = 2; public static final int MAYAN_STATUE = 3; public static final int BYZANTINE_ICON_SMALL = 4; public static final int BYZANTINE_ICON_MEDIUM = 5; public static final int BYZANTINE_ICON_LARGE = 6; public static EntityWallDecoration createTapestry(final World world, Point p, final int type) { final EnumFacing orientation = guessOrientation(world, p); if (orientation == EnumFacing.WEST) { p = p.getEast(); } else if (orientation == EnumFacing.SOUTH) { p = p.getNorth(); } else if (orientation == EnumFacing.EAST) { p = p.getWest(); } else if (orientation == EnumFacing.NORTH) { p = p.getSouth(); } return new EntityWallDecoration(world, p.getBlockPos(), orientation, type, true); } private static EnumFacing guessOrientation(final World world, final Point p) { if (BlockItemUtilities.isBlockSolid(WorldUtilities.getBlock(world, p.getNorth()))) { return EnumFacing.SOUTH; } else if (BlockItemUtilities.isBlockSolid(WorldUtilities.getBlock(world, p.getSouth()))) { return EnumFacing.NORTH; } else if (BlockItemUtilities.isBlockSolid(WorldUtilities.getBlock(world, p.getEast()))) { return EnumFacing.WEST; } else if (BlockItemUtilities.isBlockSolid(WorldUtilities.getBlock(world, p.getWest()))) { return EnumFacing.EAST; } return EnumFacing.WEST; } public EnumWallDecoration millArt; public int type; public EntityWallDecoration(final World par1World) { super(par1World); } public EntityWallDecoration(final World world, final BlockPos pos, final EnumFacing facing, final int type, final boolean largestPossible) { super(world, pos); this.type = type; final ArrayList<EnumWallDecoration> arraylist = new ArrayList<EnumWallDecoration>(); int maxSize = 0; for (final EnumWallDecoration enumart : EnumWallDecoration.values()) { if (enumart.type == type) { this.millArt = enumart; this.updateFacingWithBoundingBox(facing); if (onValidSurface()) { if (!largestPossible && ((enumart.sizeX * enumart.sizeY) > maxSize)) { arraylist.clear(); } arraylist.add(enumart); maxSize = enumart.sizeX * enumart.sizeY; } } } if (arraylist.size() > 0) { millArt = arraylist.get(rand.nextInt(arraylist.size())); } if (MillConfigValues.LogBuildingPlan >= MillLog.MAJOR) { MillLog.major(this, "Creating wall decoration: " + pos + "/" + facing + "/" + type + "/" + largestPossible + ". Result: " + millArt.title + " picked amoung " + arraylist.size()); } this.updateFacingWithBoundingBox(facing); } @SideOnly(Side.CLIENT) public EntityWallDecoration(final World world, final int type) {// NO_UCD (instantiated by Forge) this(world); this.type = type; } /** * Get the item to drop */ public Item getDropItem() { if (type == NORMAN_TAPESTRY) { return MillItems.TAPESTRY; } else if (type == INDIAN_STATUE) { return MillItems.INDIAN_STATUE; } else if (type == MAYAN_STATUE) { return MillItems.MAYAN_STATUE; } else if (type == BYZANTINE_ICON_SMALL) { return MillItems.BYZANTINE_ICON_SMALL; } else if (type == BYZANTINE_ICON_MEDIUM) { return MillItems.BYZANTINE_ICON_MEDIUM; } else if (type == BYZANTINE_ICON_LARGE) { return MillItems.BYZANTINE_ICON_LARGE; } MillLog.error(this, "Unknown walldecoration type: "+type); return null; } @Override public int getHeightPixels() { return this.millArt.sizeY; } @Override public int getWidthPixels() { return this.millArt.sizeX; } /** * Called when this entity is broken. Entity parameter may be null. */ @Override public void onBroken(final Entity brokenEntity) { if (this.world.getGameRules().getBoolean("doEntityDrops")) { this.playSound(SoundEvents.ENTITY_PAINTING_BREAK, 1.0F, 1.0F); if (brokenEntity instanceof EntityPlayer) { EntityPlayer entityplayer = (EntityPlayer)brokenEntity; if (entityplayer.capabilities.isCreativeMode) { return; } } this.entityDropItem(new ItemStack(getDropItem()), 0.0F); } } @Override public void playPlaceSound() { this.playSound(SoundEvents.ENTITY_PAINTING_PLACE, 1.0F, 1.0F); } @Override public void onUpdate() { super.onUpdate(); } @Override public boolean onValidSurface() { return super.onValidSurface(); } @Override public void readEntityFromNBT(final NBTTagCompound nbttagcompound) { type = nbttagcompound.getInteger("Type"); final String s = nbttagcompound.getString("Motive"); for (final EnumWallDecoration enumart : EnumWallDecoration.values()) { if (enumart.title.equals(s)) { millArt = enumart; } } if (millArt == null) { millArt = EnumWallDecoration.Griffon; } if (type == 0) { type = NORMAN_TAPESTRY; } super.readEntityFromNBT(nbttagcompound); } @Override public void readSpawnData(final ByteBuf ds) { final ByteBufInputStream data = new ByteBufInputStream(ds); try { type = data.readByte(); final String title = data.readUTF(); for (final EnumWallDecoration enumart : EnumWallDecoration.values()) { if (enumart.title.equals(title)) { millArt = enumart; } } data.close(); } catch (final IOException e) { MillLog.printException("Exception for tapestry " + this, e); } } @Override public String toString() { return "Tapestry (" + millArt.title + ") "+super.toString(); } /** * Set the position and rotation values directly without any clamping. */ @SideOnly(Side.CLIENT) public void setPositionAndRotationDirect(double x, double y, double z, float yaw, float pitch, int posRotationIncrements, boolean teleport) { BlockPos blockpos = this.hangingPosition.add(x - this.posX, y - this.posY, z - this.posZ); this.setPosition((double)blockpos.getX(), (double)blockpos.getY(), (double)blockpos.getZ()); } /** * Sets the location and Yaw/Pitch of an entity in the world */ public void setLocationAndAngles(double x, double y, double z, float yaw, float pitch) { this.setPosition(x, y, z); } @Override public void writeEntityToNBT(final NBTTagCompound nbttagcompound) { nbttagcompound.setInteger("Type", type); nbttagcompound.setString("Motive", this.millArt.title); super.writeEntityToNBT(nbttagcompound); } @Override public void writeSpawnData(final ByteBuf ds) { final ByteBufOutputStream data = new ByteBufOutputStream(ds); try { data.write(type); data.writeUTF(millArt.title); } catch (final IOException e) { MillLog.printException("Exception for painting " + this, e); } finally { try { data.close(); } catch (IOException e) { MillLog.printException("Exception for painting " + this, e); } } } } Item spawning them: package org.millenaire.common.item; import org.millenaire.common.entity.EntityWallDecoration; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.ItemStack; import net.minecraft.util.EnumActionResult; import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumHand; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; public class ItemWallDecoration extends ItemMill { public int type; public ItemWallDecoration(final String itemName, final int type) { super(itemName); this.type = type; } @Override public EnumActionResult onItemUse(EntityPlayer player, World worldIn, BlockPos pos, EnumHand hand, EnumFacing facing, float hitX, float hitY, float hitZ) { ItemStack itemstack = player.getHeldItem(hand); BlockPos blockpos = pos.offset(facing); if (facing != EnumFacing.DOWN && facing != EnumFacing.UP && player.canPlayerEdit(blockpos, facing, itemstack)) { final EntityWallDecoration entityhanging = new EntityWallDecoration(worldIn, blockpos, facing, type, false); if (entityhanging != null && entityhanging.onValidSurface()) { if (!worldIn.isRemote) { entityhanging.playPlaceSound(); worldIn.spawnEntity(entityhanging); } itemstack.shrink(1); } return EnumActionResult.SUCCESS; } else { return EnumActionResult.FAIL; } } } Renderer: (whose factory is called when Minecraft initialise, but doRender never) package org.millenaire.client.render; import org.millenaire.common.entity.EntityWallDecoration; import org.millenaire.common.entity.EntityWallDecoration.EnumWallDecoration; import org.millenaire.common.forge.Mill; import net.minecraft.client.renderer.BufferBuilder; import net.minecraft.client.renderer.GlStateManager; import net.minecraft.client.renderer.OpenGlHelper; import net.minecraft.client.renderer.Tessellator; import net.minecraft.client.renderer.entity.Render; import net.minecraft.client.renderer.entity.RenderManager; import net.minecraft.client.renderer.vertex.DefaultVertexFormats; import net.minecraft.util.EnumFacing; import net.minecraft.util.ResourceLocation; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.MathHelper; import net.minecraftforge.fml.client.registry.IRenderFactory; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; /** * Basically a copy of RenderPainting * * Uses one texture for the Norman tapestries and a shared one for the statues and byzantine icons */ @SideOnly(Side.CLIENT) public class RenderWallDecoration extends Render<EntityWallDecoration> { public static final FactoryRenderWallDecoration FACTORY_WALL_DECORATION = new FactoryRenderWallDecoration(); public static final ResourceLocation textureTapestries = new ResourceLocation(Mill.MODID, "textures/painting/tapestry.png"); public static final ResourceLocation textureSculptures = new ResourceLocation(Mill.MODID, "textures/painting/sculptures.png"); public static class FactoryRenderWallDecoration implements IRenderFactory<EntityWallDecoration> { @Override public Render<? super EntityWallDecoration> createRenderFor(final RenderManager manager) { return new RenderWallDecoration(manager); } } protected RenderWallDecoration(final RenderManager renderManager) { super(renderManager); } /** * Renders the desired {@code T} type Entity. */ @Override public void doRender(final EntityWallDecoration entity, final double x, final double y, final double z, final float entityYaw, final float partialTicks) { GlStateManager.pushMatrix(); GlStateManager.translate(x, y, z); GlStateManager.rotate(180.0F - entityYaw, 0.0F, 1.0F, 0.0F); GlStateManager.enableRescaleNormal(); this.bindEntityTexture(entity); final EnumWallDecoration enumart = entity.millArt; GlStateManager.scale(0.0625F, 0.0625F, 0.0625F); if (this.renderOutlines) { GlStateManager.enableColorMaterial(); GlStateManager.enableOutlineMode(this.getTeamColor(entity)); } this.renderPainting(entity, enumart.sizeX, enumart.sizeY, enumart.offsetX, enumart.offsetY); if (this.renderOutlines) { GlStateManager.disableOutlineMode(); GlStateManager.disableColorMaterial(); } GlStateManager.disableRescaleNormal(); GlStateManager.popMatrix(); super.doRender(entity, x, y, z, entityYaw, partialTicks); } @Override protected ResourceLocation getEntityTexture(final EntityWallDecoration entity) { if (entity.type == EntityWallDecoration.NORMAN_TAPESTRY) { return textureTapestries; } else { return textureSculptures; } } /** * Same as the one in RenderPainting in 1.12, with unused variables removed */ private void renderPainting(final EntityWallDecoration painting, final int width, final int height, final int textureU, final int textureV) { float f = (float)(-width) / 2.0F; float f1 = (float)(-height) / 2.0F; for (int i = 0; i < width / 16; ++i) { for (int j = 0; j < height / 16; ++j) { float f15 = f + (float)((i + 1) * 16); float f16 = f + (float)(i * 16); float f17 = f1 + (float)((j + 1) * 16); float f18 = f1 + (float)(j * 16); this.setLightmap(painting, (f15 + f16) / 2.0F, (f17 + f18) / 2.0F); float f19 = (float)(textureU + width - i * 16) / 256.0F; float f20 = (float)(textureU + width - (i + 1) * 16) / 256.0F; float f21 = (float)(textureV + height - j * 16) / 256.0F; float f22 = (float)(textureV + height - (j + 1) * 16) / 256.0F; Tessellator tessellator = Tessellator.getInstance(); BufferBuilder bufferbuilder = tessellator.getBuffer(); bufferbuilder.begin(7, DefaultVertexFormats.POSITION_TEX_NORMAL); bufferbuilder.pos((double)f15, (double)f18, -0.5D).tex((double)f20, (double)f21).normal(0.0F, 0.0F, -1.0F).endVertex(); bufferbuilder.pos((double)f16, (double)f18, -0.5D).tex((double)f19, (double)f21).normal(0.0F, 0.0F, -1.0F).endVertex(); bufferbuilder.pos((double)f16, (double)f17, -0.5D).tex((double)f19, (double)f22).normal(0.0F, 0.0F, -1.0F).endVertex(); bufferbuilder.pos((double)f15, (double)f17, -0.5D).tex((double)f20, (double)f22).normal(0.0F, 0.0F, -1.0F).endVertex(); bufferbuilder.pos((double)f15, (double)f17, 0.5D).tex(0.75D, 0.0D).normal(0.0F, 0.0F, 1.0F).endVertex(); bufferbuilder.pos((double)f16, (double)f17, 0.5D).tex(0.8125D, 0.0D).normal(0.0F, 0.0F, 1.0F).endVertex(); bufferbuilder.pos((double)f16, (double)f18, 0.5D).tex(0.8125D, 0.0625D).normal(0.0F, 0.0F, 1.0F).endVertex(); bufferbuilder.pos((double)f15, (double)f18, 0.5D).tex(0.75D, 0.0625D).normal(0.0F, 0.0F, 1.0F).endVertex(); bufferbuilder.pos((double)f15, (double)f17, -0.5D).tex(0.75D, 0.001953125D).normal(0.0F, 1.0F, 0.0F).endVertex(); bufferbuilder.pos((double)f16, (double)f17, -0.5D).tex(0.8125D, 0.001953125D).normal(0.0F, 1.0F, 0.0F).endVertex(); bufferbuilder.pos((double)f16, (double)f17, 0.5D).tex(0.8125D, 0.001953125D).normal(0.0F, 1.0F, 0.0F).endVertex(); bufferbuilder.pos((double)f15, (double)f17, 0.5D).tex(0.75D, 0.001953125D).normal(0.0F, 1.0F, 0.0F).endVertex(); bufferbuilder.pos((double)f15, (double)f18, 0.5D).tex(0.75D, 0.001953125D).normal(0.0F, -1.0F, 0.0F).endVertex(); bufferbuilder.pos((double)f16, (double)f18, 0.5D).tex(0.8125D, 0.001953125D).normal(0.0F, -1.0F, 0.0F).endVertex(); bufferbuilder.pos((double)f16, (double)f18, -0.5D).tex(0.8125D, 0.001953125D).normal(0.0F, -1.0F, 0.0F).endVertex(); bufferbuilder.pos((double)f15, (double)f18, -0.5D).tex(0.75D, 0.001953125D).normal(0.0F, -1.0F, 0.0F).endVertex(); bufferbuilder.pos((double)f15, (double)f17, 0.5D).tex(0.751953125D, 0.0D).normal(-1.0F, 0.0F, 0.0F).endVertex(); bufferbuilder.pos((double)f15, (double)f18, 0.5D).tex(0.751953125D, 0.0625D).normal(-1.0F, 0.0F, 0.0F).endVertex(); bufferbuilder.pos((double)f15, (double)f18, -0.5D).tex(0.751953125D, 0.0625D).normal(-1.0F, 0.0F, 0.0F).endVertex(); bufferbuilder.pos((double)f15, (double)f17, -0.5D).tex(0.751953125D, 0.0D).normal(-1.0F, 0.0F, 0.0F).endVertex(); bufferbuilder.pos((double)f16, (double)f17, -0.5D).tex(0.751953125D, 0.0D).normal(1.0F, 0.0F, 0.0F).endVertex(); bufferbuilder.pos((double)f16, (double)f18, -0.5D).tex(0.751953125D, 0.0625D).normal(1.0F, 0.0F, 0.0F).endVertex(); bufferbuilder.pos((double)f16, (double)f18, 0.5D).tex(0.751953125D, 0.0625D).normal(1.0F, 0.0F, 0.0F).endVertex(); bufferbuilder.pos((double)f16, (double)f17, 0.5D).tex(0.751953125D, 0.0D).normal(1.0F, 0.0F, 0.0F).endVertex(); tessellator.draw(); } } } /** * Same as the one in RenderPainting in 1.12 */ private void setLightmap(final EntityWallDecoration painting, final float p_77008_2_, final float p_77008_3_) { int i = MathHelper.floor(painting.posX); int j = MathHelper.floor(painting.posY + (double)(p_77008_3_ / 16.0F)); int k = MathHelper.floor(painting.posZ); EnumFacing enumfacing = painting.facingDirection; if (enumfacing == EnumFacing.NORTH) { i = MathHelper.floor(painting.posX + (double)(p_77008_2_ / 16.0F)); } if (enumfacing == EnumFacing.WEST) { k = MathHelper.floor(painting.posZ - (double)(p_77008_2_ / 16.0F)); } if (enumfacing == EnumFacing.SOUTH) { i = MathHelper.floor(painting.posX - (double)(p_77008_2_ / 16.0F)); } if (enumfacing == EnumFacing.EAST) { k = MathHelper.floor(painting.posZ + (double)(p_77008_2_ / 16.0F)); } int l = this.renderManager.world.getCombinedLight(new BlockPos(i, j, k), 0); int i1 = l % 65536; int j1 = l / 65536; OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, (float)i1, (float)j1); GlStateManager.color(1.0F, 1.0F, 1.0F); } } Model renderer declaration: @SubscribeEvent public static void registerModels(final ModelRegistryEvent event) { MillItems.registerItemModels(); MillBlocks.registerItemBlockModels(); RenderingRegistry.registerEntityRenderingHandler(EntityGenericMale.class, RenderMillVillager.FACTORY_MALE); RenderingRegistry.registerEntityRenderingHandler(EntityGenericAsymmFemale.class, RenderMillVillager.FACTORY_FEMALE_ASYM); RenderingRegistry.registerEntityRenderingHandler(EntityGenericSymmFemale.class, RenderMillVillager.FACTORY_FEMALE_SYM); RenderingRegistry.registerEntityRenderingHandler(EntityWallDecoration.class, RenderWallDecoration.FACTORY_WALL_DECORATION); } And entity declaration in init: EntityRegistry.registerModEntity(EntityWallDecoration.WALL_DECORATION, EntityWallDecoration.class, EntityWallDecoration.WALL_DECORATION.getResourcePath(), id++, MODID, 64, 3, false); I'm skipping the item's declarations etc as the item itself works fine. And debugger when the entity is spawned client-side: Anybody has any idea? I feel like I must be missing something small, probably a mistake in how my entity is declared or something of that kind... Thanks!
-
Tried it, it works fine, thanks for the help.
-
Arg, that uses the exception from parseInt to switch between treating it as a meta and as values But the method within getBlockStatePropertyValueMap() looks like I exactly what I need indeed. Perfect!
-
Great, thanks! I've just had a quick look at the code used and it's simpler than I feared, no exceptions really, just iterates over the properties and get the actual value via an existing method. Shouldn't be hard to mimic this.
-
I'll do a bit of a necro on this since it is very close to what I'm wanting to do. Like the OP, I need to get a blockstate from a human-readable config file. My current one, inherited from MC 1.7, uses meta values, for example: birch leaves;minecraft:leaves;2;false;44/119/15 (with only the second and third values used to get the block state, the rest is mod-specific) I would however really like to switch to something value-based, something like: birch leaves;minecraft:leaves;variant=birch;false;44/119/15 The kind of description you find in the JSON files etc. It would be a lot more readable and easy to debug when something goes wrong. Is there anyway of doing this? I've been hunting through the MC code but if there's a clearly-defined way of doing it I haven't found it yet. Thanks.
-
The principle exists since 1.3, but's clearly been extended. Anyway I checked, in 1.7 for biomeName there was no issue because the field was accessible. And my packet system works fine in SP in 1.12 and I have a few million mod downloads for 1.7 of people who obviously managed to play with my packet system... assume all you want. Reworking it to use the new Forge system is a huge amount of work, and you have no way of knowing whether it's worth it or not.
-
[1.12] Threading crash when accessing chunks from GUI
Kinniken replied to Kinniken's topic in Modder Support
It worked in 1.7. I've had thousands of players over the four years of the 1.7 version with this bug never reported, I saw it myself and had it reported to me several times for 1.12 in two days of having an alpha out. WorldSavedData is great but not suitable for saving the amount of data I need. It's not the perfect solution for everything. For one thing it requires saving everything to NBT, which is not cool for saving thousands of building points for planned constructions. Plus it does't allow you to finally control what gets sent to the client and what doesn't; in my case just sending everything would be a waste of bandwidth. -
Hi all, I'm having at least two method calls that work fine in SP and MP when in development and in SP when running Minecraft, but fail when running a server: java.lang.NoSuchMethodError: net.minecraft.world.biome.Biome.func_185359_l()Ljava/lang/String; at org.millenaire.common.WorldGenVillage.generateVillageAtPoint_findVillageType(WorldGenVillage.java:768) (That's Biome.getBiomeName()) and java.lang.NoSuchMethodError: net.minecraft.network.play.server.SPacketCustomPayload.func_180735_b()Lnet/minecraft/network/PacketBuffer; at net.minecraftforge.fml.common.network.internal.FMLProxyPacket.<init>(FMLProxyPacket.java:62) at org.millenaire.common.network.ServerSender.sendPacketToPlayer(ServerSender.java:378) (That's CPacketCustomPayload.getBufferData()) I "fixed" the first one by using reflection to access biomeName directly. Not a real solution though. Is this a known Forge problem? I saw references online to other people having the issue with getBiomeName, but no proper solution. Thanks.
-
[1.12] Forcing the refresh of chest contents client-side
Kinniken replied to Kinniken's topic in Modder Support
SSP and MP are mostly the same, since 1.3 IIRC (god was that update a main to deal with). However for a problem like the one I'm having the difference can be significant, if only due to the reduced lag. There are too many classes to all list, but the gist of it is: In ContainerTrade that extends Container, in slotClick(), in the case of the player buying goods and hence taking them from the building's chests: while ((taken < toTake) && (i < resManager.chests.size())) { final TileEntityChest chest = resManager.chests.get(i).getMillChest(world); if (chest != null) { taken += WorldUtilities.getItemsFromChest(chest, item, meta, toTake - taken); } i++; } You can ignore how I get that list of chest positions, that's not really relevant. Then for each chest: static public int getItemsFromChest(final IInventory chest, final Item item, final int meta, final int toTake) { if (chest == null) { return 0; } int nb = 0; int maxSlot = chest.getSizeInventory() - 1; for (int i = maxSlot; (i >= 0) && (nb < toTake); i--) { final ItemStack stack = chest.getStackInSlot(i); if ((stack != null) && (stack.getItem() == item) && ((stack.getItemDamage() == meta) || (meta == -1))) { if (stack.getCount() <= (toTake - nb)) { nb += stack.getCount(); chest.setInventorySlotContents(i, ItemStack.EMPTY); } else { chest.decrStackSize(i, toTake - nb); nb = toTake; } } return nb; } Simplifying the code a bit. For me the only part that seems like it could be relevant is that I take items via either setInventorySlotContents (if I'm taking a whole stack) or decrStackSize (if only part of it). Is there something else I should do to get Forge to update the chest client-side ASAP? Thanks