diff --git a/patches/minecraft/net/minecraft/block/BlockFarmland.java.patch b/patches/minecraft/net/minecraft/block/BlockFarmland.java.patch index 9342f7364..dc12cb21c 100644 --- a/patches/minecraft/net/minecraft/block/BlockFarmland.java.patch +++ b/patches/minecraft/net/minecraft/block/BlockFarmland.java.patch @@ -22,3 +22,12 @@ } private static boolean func_176530_e(IWorldReaderBase p_176530_0_, BlockPos p_176530_1_) { +@@ -102,7 +102,7 @@ + } + } + +- return false; ++ return net.minecraftforge.common.FarmlandWaterManager.hasBlockWaterTicket(p_176530_0_, p_176530_1_); + } + + public IItemProvider func_199769_a(IBlockState p_199769_1_, World p_199769_2_, BlockPos p_199769_3_, int p_199769_4_) { diff --git a/src/main/java/net/minecraftforge/common/FarmlandWaterManager.java b/src/main/java/net/minecraftforge/common/FarmlandWaterManager.java new file mode 100644 index 000000000..d7f84bcd1 --- /dev/null +++ b/src/main/java/net/minecraftforge/common/FarmlandWaterManager.java @@ -0,0 +1,145 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2018. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.minecraftforge.common; + +import com.google.common.base.Preconditions; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.IWorld; +import net.minecraft.world.IWorldReaderBase; +import net.minecraft.world.World; +import net.minecraft.world.chunk.IChunk; +import net.minecraftforge.common.ticket.AABBTicket; +import net.minecraftforge.common.ticket.ChunkTicketManager; +import net.minecraftforge.common.ticket.MultiTicketManager; +import net.minecraftforge.common.ticket.SimpleTicket; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public class FarmlandWaterManager +{ + private static final Int2ObjectMap>> customWaterHandler = new Int2ObjectOpenHashMap<>(); + + /** + * Adds a custom ticket. + * Use {@link #addAABBTicket(World, AxisAlignedBB)} if you just need a ticket that can water a certain area. + *
+ * If you don't want to water the region anymore, call {@link SimpleTicket#invalidate()}. Also call this + * when the region this is unloaded (e.g. your TE is unloaded or the block is removed), and validate once it is loaded + * @param world The world where the region should be marked. Only server-side worlds are allowed + * @param ticket Your ticket you want to have registered + * @param chunkPoses The chunkPoses where the ticket is located + * @return The ticket for your requested region. + */ + @SuppressWarnings("unchecked") + public static> T addCustomTicket(World world, T ticket, ChunkPos... chunkPoses) + { + Preconditions.checkArgument(!world.isRemote, "Water region is only determined server-side"); + Preconditions.checkArgument(chunkPoses.length > 0, "Need at least one chunk pos"); + Map> ticketMap = customWaterHandler.computeIfAbsent(world.getDimension().getType().getId(), id -> new HashMap<>()); + if (chunkPoses.length == 1) + { + ticket.setBackend(ticketMap.computeIfAbsent(chunkPoses[0], ChunkTicketManager::new)); + } + else + { + ChunkTicketManager[] tickets = new ChunkTicketManager[chunkPoses.length]; + for (int i = 0; i < chunkPoses.length; i++) + tickets[i] = ticketMap.computeIfAbsent(chunkPoses[i], ChunkTicketManager::new); + ticket.setBackend(new MultiTicketManager<>(tickets)); + } + ticket.validate(); + return ticket; + } + + /** + * Convenience method to add a ticket that is backed by an AABB. + *
+ * If you don't want to water the region anymore, call {@link SimpleTicket#invalidate()}. Also call this + * when the region this is unloaded (e.g. your TE is unloaded or the block is removed), and validate once it is loaded + *
+ * The AABB in the ticket is immutable + * @param world The world where the region should be marked. Only server-side worlds are allowed + * @param aabb The region where blocks should be watered + * @return The ticket for your requested region. + */ + public static AABBTicket addAABBTicket(World world, AxisAlignedBB aabb) + { + //First calculate all chunks the aabb is in + ChunkPos leftUp = new ChunkPos(((int) aabb.minX) >> 4, ((int) aabb.minZ) >> 4); + ChunkPos rightDown = new ChunkPos(((int) aabb.maxX) >> 4, ((int) aabb.maxZ) >> 4); + Set posSet = new HashSet<>(); + for (int x = leftUp.x; x <= rightDown.x; x++) + { + for (int z = leftUp.z; z <= rightDown.z; z++) + { + posSet.add(new ChunkPos(x, z)); + } + } + return addCustomTicket(world, new AABBTicket(aabb), posSet.toArray(new ChunkPos[0])); + } + + /** + * Tests if a block is in a region that is watered by blocks. This does not check vanilla water, see {@link net.minecraft.block.BlockFarmland#hasWater(World, BlockPos)} + * @return true if there is a ticket with an AABB that includes your block + */ + public static boolean hasBlockWaterTicket(IWorldReaderBase world, BlockPos pos) + { + ChunkTicketManager ticketManager = getTicketManager(new ChunkPos(pos.getX() >> 4, pos.getZ() >> 4), world); + if (ticketManager != null) + { + Vec3d posAsVec3d = new Vec3d(pos); + for (SimpleTicket ticket : ticketManager.getTickets()) { + if (ticket.matches(posAsVec3d)) + return true; + } + } + return false; + } + + static void removeTickets(IChunk chunk) + { + ChunkTicketManager ticketManager = getTicketManager(chunk.getPos(), chunk.getWorldForge()); + if (ticketManager != null) + { + for (SimpleTicket ticket : ticketManager.getTickets()) + { + ticket.invalidate(); + } + } + } + + private static ChunkTicketManager getTicketManager(ChunkPos pos, IWorldReaderBase world) { + Preconditions.checkArgument(!world.isRemote(), "Water region is only determined server-side"); + Map> ticketMap = customWaterHandler.get(world.getDimension().getType().getId()); + if (ticketMap == null) + { + return null; + } + return ticketMap.get(pos); + } +} diff --git a/src/main/java/net/minecraftforge/common/ForgeInternalHandler.java b/src/main/java/net/minecraftforge/common/ForgeInternalHandler.java index c768b6892..e07ff105b 100644 --- a/src/main/java/net/minecraftforge/common/ForgeInternalHandler.java +++ b/src/main/java/net/minecraftforge/common/ForgeInternalHandler.java @@ -27,6 +27,7 @@ import net.minecraft.world.WorldServer; import net.minecraftforge.client.CloudRenderer; import net.minecraftforge.common.util.FakePlayerFactory; import net.minecraftforge.event.entity.EntityJoinWorldEvent; +import net.minecraftforge.event.world.ChunkEvent; import net.minecraftforge.event.world.WorldEvent; import net.minecraftforge.eventbus.api.EventPriority; import net.minecraftforge.eventbus.api.SubscribeEvent; @@ -80,5 +81,12 @@ public class ForgeInternalHandler if (event.phase == Phase.END) CloudRenderer.updateCloudSettings(); } + + @SubscribeEvent + public void onChunkUnload(ChunkEvent.Unload event) + { + if (!event.getWorld().isRemote()) + FarmlandWaterManager.removeTickets(event.getChunk()); + } } diff --git a/src/main/java/net/minecraftforge/common/ticket/AABBTicket.java b/src/main/java/net/minecraftforge/common/ticket/AABBTicket.java new file mode 100644 index 000000000..c98c0bf1d --- /dev/null +++ b/src/main/java/net/minecraftforge/common/ticket/AABBTicket.java @@ -0,0 +1,42 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2018. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.minecraftforge.common.ticket; + +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.Vec3d; + +import javax.annotation.Nonnull; + +public class AABBTicket extends SimpleTicket +{ + @Nonnull + public final AxisAlignedBB axisAlignedBB; + + public AABBTicket(@Nonnull AxisAlignedBB axisAlignedBB) + { + this.axisAlignedBB = axisAlignedBB; + } + + @Override + public boolean matches(Vec3d toMatch) + { + return this.axisAlignedBB.contains(toMatch); + } +} diff --git a/src/main/java/net/minecraftforge/common/ticket/ChunkTicketManager.java b/src/main/java/net/minecraftforge/common/ticket/ChunkTicketManager.java new file mode 100644 index 000000000..549f86662 --- /dev/null +++ b/src/main/java/net/minecraftforge/common/ticket/ChunkTicketManager.java @@ -0,0 +1,56 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2018. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.minecraftforge.common.ticket; + +import net.minecraft.util.math.ChunkPos; + +import java.util.Collection; +import java.util.Collections; +import java.util.Set; +import java.util.WeakHashMap; + +public class ChunkTicketManager implements ITicketGetter +{ + private final Set> tickets = Collections.newSetFromMap(new WeakHashMap<>()); + public final ChunkPos pos; + + public ChunkTicketManager(ChunkPos pos) + { + this.pos = pos; + } + + @Override + public void add(SimpleTicket ticket) + { + this.tickets.add(ticket); + } + + @Override + public void remove(SimpleTicket ticket) + { + this.tickets.remove(ticket); + } + + @Override + public Collection> getTickets() + { + return tickets; + } +} diff --git a/src/main/java/net/minecraftforge/common/ticket/ITicketGetter.java b/src/main/java/net/minecraftforge/common/ticket/ITicketGetter.java new file mode 100644 index 000000000..8d574699b --- /dev/null +++ b/src/main/java/net/minecraftforge/common/ticket/ITicketGetter.java @@ -0,0 +1,27 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2018. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.minecraftforge.common.ticket; + +import java.util.Collection; + +public interface ITicketGetter extends ITicketManager +{ + Collection> getTickets(); +} diff --git a/src/main/java/net/minecraftforge/common/ticket/ITicketManager.java b/src/main/java/net/minecraftforge/common/ticket/ITicketManager.java new file mode 100644 index 000000000..61ffd32b5 --- /dev/null +++ b/src/main/java/net/minecraftforge/common/ticket/ITicketManager.java @@ -0,0 +1,27 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2018. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.minecraftforge.common.ticket; + +public interface ITicketManager +{ + void add(SimpleTicket ticket); + + void remove(SimpleTicket ticket); +} diff --git a/src/main/java/net/minecraftforge/common/ticket/MultiTicketManager.java b/src/main/java/net/minecraftforge/common/ticket/MultiTicketManager.java new file mode 100644 index 000000000..9fc88e243 --- /dev/null +++ b/src/main/java/net/minecraftforge/common/ticket/MultiTicketManager.java @@ -0,0 +1,45 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2018. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.minecraftforge.common.ticket; + +public class MultiTicketManager implements ITicketManager +{ + private final ITicketGetter[] ticketManagers; + + @SafeVarargs + public MultiTicketManager(ITicketGetter... ticketManagers) + { + this.ticketManagers = ticketManagers; + } + + @Override + public void add(SimpleTicket ticket) + { + for (ITicketGetter manager : ticketManagers) + manager.add(ticket); + } + + @Override + public void remove(SimpleTicket ticket) + { + for (ITicketGetter manager : ticketManagers) + manager.remove(ticket); + } +} diff --git a/src/main/java/net/minecraftforge/common/ticket/SimpleTicket.java b/src/main/java/net/minecraftforge/common/ticket/SimpleTicket.java new file mode 100644 index 000000000..b8b85c333 --- /dev/null +++ b/src/main/java/net/minecraftforge/common/ticket/SimpleTicket.java @@ -0,0 +1,84 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2018. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.minecraftforge.common.ticket; + +import com.google.common.base.Preconditions; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Common class for a simple ticket based system. + * @param The type that will be used to check if your ticket matches + */ +public abstract class SimpleTicket +{ + @Nullable + private ITicketManager manager; + protected boolean isValid = false; + + /** + * Internal method that sets the collection from the managing system. + *
+ * Should not be called if you just want to register a ticket to a system like the {@link net.minecraftforge.common.FarmlandWaterManager} + */ + public final void setBackend(@Nonnull ITicketManager ticketManager) + { + Preconditions.checkState(this.manager == null, "Ticket is already registered to a managing system"); + this.manager = ticketManager; + } + + /** + * Checks if your ticket is still registered in the system. + */ + public boolean isValid() + { + return isValid; + } + + /** + * Removes the ticket from the managing system. + * After this call, any calls to {@link #isValid()} should return false unless it is registered again using {@link #validate()} + */ + public void invalidate() + { + Preconditions.checkState(this.manager != null, "Ticket is not registered to a managing system"); + if (this.isValid()) + { + this.manager.remove(this); + } + this.isValid = false; + } + + /** + * Re-adds your ticket to the system. + */ + public void validate() + { + Preconditions.checkState(this.manager != null, "Ticket is not registered to a managing system"); + if (!this.isValid()) + { + this.manager.add(this); + } + this.isValid = true; + } + + public abstract boolean matches(T toMatch); +} diff --git a/src/test/java/net/minecraftforge/debug/block/FarmlandWaterTest.java b/src/test/java/net/minecraftforge/debug/block/FarmlandWaterTest.java new file mode 100644 index 000000000..85dea243d --- /dev/null +++ b/src/test/java/net/minecraftforge/debug/block/FarmlandWaterTest.java @@ -0,0 +1,182 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2018. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.minecraftforge.debug.block; + +import net.minecraft.block.Block; +import net.minecraft.block.material.Material; +import net.minecraft.block.state.IBlockState; +import net.minecraft.creativetab.CreativeTabs; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.Item; +import net.minecraft.item.ItemBlock; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumHand; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.text.TextComponentString; +import net.minecraft.world.World; +import net.minecraftforge.common.FarmlandWaterManager; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.common.ticket.AABBTicket; +import net.minecraftforge.event.RegistryEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.registry.GameRegistry; +import org.apache.logging.log4j.Logger; + +import javax.annotation.Nullable; + +@Mod(modid = FarmlandWaterTest.ID, name = "Farmland Water Test", version = "1.0.0", acceptableRemoteVersions = "*") +public class FarmlandWaterTest +{ + //This adds a block that creates a 4x4x4 watered region when activated + private static Logger logger; + private static Block testBlock; + static final String ID = "farmlandwatertest"; + + @Mod.EventHandler + public void preInit(FMLPreInitializationEvent event) + { + logger = event.getModLog(); + MinecraftForge.EVENT_BUS.register(FarmlandWaterTest.class); + } + + @SubscribeEvent + public static void registerBlocks(RegistryEvent.Register event) + { + testBlock = new TestBlock(); + event.getRegistry().register(testBlock.setRegistryName(new ResourceLocation(ID, "test_block")).setCreativeTab(CreativeTabs.MISC).setUnlocalizedName("Farmland Water Test Block")); + GameRegistry.registerTileEntity(TestTileEntity.class, new ResourceLocation(ID, "test_te")); + } + + @SubscribeEvent + public static void registerItems(RegistryEvent.Register event) + { + event.getRegistry().register(new ItemBlock(testBlock).setRegistryName(new ResourceLocation(ID, "test_block"))); + } + + public static class TestBlock extends Block + { + + public TestBlock() + { + super(Material.ROCK); + } + + @Override + public boolean hasTileEntity(IBlockState state) + { + return true; + } + + @Nullable + @Override + public TileEntity createTileEntity(World world, IBlockState state) + { + return new TestTileEntity(); + } + + @Override + public boolean onBlockActivated(World world, BlockPos pos, IBlockState state, EntityPlayer player, EnumHand hand, EnumFacing facing, float hitX, float hitY, float hitZ) + { + if (world.isRemote) + return true; + TestTileEntity tileEntity = (TestTileEntity) world.getTileEntity(pos); + if (tileEntity == null) + { + return false; + } + tileEntity.isActive = !tileEntity.isActive; + tileEntity.updateTicket(); + player.sendStatusMessage(new TextComponentString("Changed block powered state to " + tileEntity.isActive), true); + logger.info("Changed block powered state at {} to {}", pos, tileEntity.isActive); + return true; + } + + @Override + public void breakBlock(World world, BlockPos pos, IBlockState state) + { + if (world.isRemote) + return; + TestTileEntity tileEntity = (TestTileEntity) world.getTileEntity(pos); + if (tileEntity == null) + return; + tileEntity.farmlandTicket.invalidate(); + } + } + + public static class TestTileEntity extends TileEntity + { + private AABBTicket farmlandTicket; + private boolean isActive = false; + + @Override + public void onLoad() + { + if (!world.isRemote) + { + farmlandTicket = FarmlandWaterManager.addAABBTicket(world, new AxisAlignedBB(pos).grow(4D)); + updateTicket(); + } + } + + private void updateTicket() + { + if (world.isRemote) + return; + if (isActive) + { + farmlandTicket.validate(); + } + else + { + farmlandTicket.invalidate(); + } + } + + @Override + public NBTTagCompound writeToNBT(NBTTagCompound compound) + { + compound = super.writeToNBT(compound); + compound.setBoolean("active", isActive); + return compound; + } + + @Override + public void readFromNBT(NBTTagCompound compound) + { + super.readFromNBT(compound); + isActive = compound.getBoolean("active"); + } + + @Override + public void onChunkUnload() + { + if (!world.isRemote) + { + farmlandTicket.invalidate(); + } + } + } +} diff --git a/src/test/resources/assets/farmlandwatertest/blockstates/test_block.json b/src/test/resources/assets/farmlandwatertest/blockstates/test_block.json new file mode 100644 index 000000000..0ca7c1abe --- /dev/null +++ b/src/test/resources/assets/farmlandwatertest/blockstates/test_block.json @@ -0,0 +1,10 @@ +{ + "forge_marker": 1, + "defaults": { + "model": "minecraft:bedrock" + }, + "variants": { + "normal": [{}], + "inventory": [{}] + } +}