Initial commit (Forge 1291).
This commit is contained in:
376
patches/net/minecraftforge/common/DimensionManager.java.patch
Normal file
376
patches/net/minecraftforge/common/DimensionManager.java.patch
Normal file
@ -0,0 +1,376 @@
|
||||
--- ../src-base/minecraft/net/minecraftforge/common/DimensionManager.java
|
||||
+++ ../src-work/minecraft/net/minecraftforge/common/DimensionManager.java
|
||||
@@ -34,6 +34,15 @@
|
||||
import net.minecraft.world.storage.ISaveHandler;
|
||||
import net.minecraft.world.storage.SaveHandler;
|
||||
import net.minecraftforge.event.world.WorldEvent;
|
||||
+// Cauldron start
|
||||
+import net.minecraft.server.dedicated.DedicatedServer;
|
||||
+import net.minecraft.world.chunk.storage.AnvilSaveHandler;
|
||||
+import net.minecraftforge.cauldron.CauldronUtils;
|
||||
+import net.minecraftforge.common.util.EnumHelper;
|
||||
+import org.bukkit.World.Environment;
|
||||
+import org.bukkit.WorldCreator;
|
||||
+import org.bukkit.generator.ChunkGenerator;
|
||||
+// Cauldron end
|
||||
|
||||
public class DimensionManager
|
||||
{
|
||||
@@ -46,6 +55,11 @@
|
||||
private static BitSet dimensionMap = new BitSet(Long.SIZE << 4);
|
||||
private static ConcurrentMap<World, World> weakWorldMap = new MapMaker().weakKeys().weakValues().<World,World>makeMap();
|
||||
private static Multiset<Integer> leakedWorlds = HashMultiset.create();
|
||||
+ // Cauldron start
|
||||
+ private static Hashtable<Class<? extends WorldProvider>, Integer> classToProviders = new Hashtable<Class<? extends WorldProvider>, Integer>();
|
||||
+ private static ArrayList<Integer> bukkitDims = new ArrayList<Integer>(); // used to keep track of Bukkit dimensions
|
||||
+ private static final String FILE_SEPARATOR = System.getProperty("file.separator");
|
||||
+ // Cauldron end
|
||||
|
||||
public static boolean registerProviderType(int id, Class<? extends WorldProvider> provider, boolean keepLoaded)
|
||||
{
|
||||
@@ -53,7 +67,23 @@
|
||||
{
|
||||
return false;
|
||||
}
|
||||
+ // Cauldron start - register provider with bukkit and add appropriate config option
|
||||
+ String worldType = "unknown";
|
||||
+ if (id != -1 && id != 0 && id != 1) // ignore vanilla
|
||||
+ {
|
||||
+ worldType = provider.getSimpleName().toLowerCase();
|
||||
+ worldType = worldType.replace("worldprovider", "");
|
||||
+ worldType = worldType.replace("provider", "");
|
||||
+ registerBukkitEnvironment(id, worldType);
|
||||
+ }
|
||||
+ else
|
||||
+ {
|
||||
+ worldType = Environment.getEnvironment(id).name().toLowerCase();
|
||||
+ }
|
||||
+ keepLoaded = MinecraftServer.getServer().cauldronConfig.getBoolean("world-environment-settings." + worldType + ".keep-world-loaded", keepLoaded);
|
||||
+ // Cauldron end
|
||||
providers.put(id, provider);
|
||||
+ classToProviders.put(provider, id);
|
||||
spawnSettings.put(id, keepLoaded);
|
||||
return true;
|
||||
}
|
||||
@@ -157,28 +187,33 @@
|
||||
|
||||
public static Integer[] getIDs(boolean check)
|
||||
{
|
||||
- if (check)
|
||||
+ // Cauldron start - check config option and only log world leak messages if enabled
|
||||
+ if (MinecraftServer.getServer().cauldronConfig.worldLeakDebug.getValue())
|
||||
{
|
||||
- List<World> allWorlds = Lists.newArrayList(weakWorldMap.keySet());
|
||||
- allWorlds.removeAll(worlds.values());
|
||||
- for (ListIterator<World> li = allWorlds.listIterator(); li.hasNext(); )
|
||||
+ if (check)
|
||||
{
|
||||
- World w = li.next();
|
||||
- leakedWorlds.add(System.identityHashCode(w));
|
||||
- }
|
||||
- for (World w : allWorlds)
|
||||
- {
|
||||
- int leakCount = leakedWorlds.count(System.identityHashCode(w));
|
||||
- if (leakCount == 5)
|
||||
+ List<World> allWorlds = Lists.newArrayList(weakWorldMap.keySet());
|
||||
+ allWorlds.removeAll(worlds.values());
|
||||
+ for (ListIterator<World> li = allWorlds.listIterator(); li.hasNext(); )
|
||||
{
|
||||
- FMLLog.fine("The world %x (%s) may have leaked: first encounter (5 occurences).\n", System.identityHashCode(w), w.getWorldInfo().getWorldName());
|
||||
+ World w = li.next();
|
||||
+ leakedWorlds.add(System.identityHashCode(w));
|
||||
}
|
||||
- else if (leakCount % 5 == 0)
|
||||
+ for (World w : allWorlds)
|
||||
{
|
||||
- FMLLog.fine("The world %x (%s) may have leaked: seen %d times.\n", System.identityHashCode(w), w.getWorldInfo().getWorldName(), leakCount);
|
||||
+ int leakCount = leakedWorlds.count(System.identityHashCode(w));
|
||||
+ if (leakCount == 5)
|
||||
+ {
|
||||
+ FMLLog.fine("The world %x (%s) may have leaked: first encounter (5 occurences). Note: This may be a caused by a mod, plugin, or just a false-positive(No memory leak). If server crashes due to OOM, report to Cauldron.\n", System.identityHashCode(w), w.getWorldInfo().getWorldName());
|
||||
+ }
|
||||
+ else if (leakCount % 5 == 0)
|
||||
+ {
|
||||
+ FMLLog.fine("The world %x (%s) may have leaked: seen %d times. Note: This may be a caused by a mod, plugin, or just a false-positive(No memory leak). If server crashes due to OOM, report to Cauldron.\n", System.identityHashCode(w), w.getWorldInfo().getWorldName(), leakCount);
|
||||
+ }
|
||||
}
|
||||
}
|
||||
}
|
||||
+ // Cauldron end
|
||||
return getIDs();
|
||||
}
|
||||
public static Integer[] getIDs()
|
||||
@@ -191,12 +226,23 @@
|
||||
if (world != null)
|
||||
{
|
||||
worlds.put(id, world);
|
||||
- weakWorldMap.put(world, world);
|
||||
+ // Cauldron start - check config option and only log world leak messages if enabled
|
||||
+ if (MinecraftServer.getServer().cauldronConfig.worldLeakDebug.getValue())
|
||||
+ {
|
||||
+ weakWorldMap.put(world, world);
|
||||
+ }
|
||||
+ // handle all world adds here for Bukkit
|
||||
+ if (!MinecraftServer.getServer().worlds.contains(world))
|
||||
+ {
|
||||
+ MinecraftServer.getServer().worlds.add(world);
|
||||
+ }
|
||||
+ // Cauldron end
|
||||
MinecraftServer.getServer().worldTickTimes.put(id, new long[100]);
|
||||
FMLLog.info("Loading dimension %d (%s) (%s)", id, world.getWorldInfo().getWorldName(), world.func_73046_m());
|
||||
}
|
||||
else
|
||||
{
|
||||
+ MinecraftServer.getServer().worlds.remove(getWorld(id)); // Cauldron - remove world from our new world arraylist
|
||||
worlds.remove(id);
|
||||
MinecraftServer.getServer().worldTickTimes.remove(id);
|
||||
FMLLog.info("Unloading dimension %d", id);
|
||||
@@ -224,6 +270,7 @@
|
||||
}
|
||||
|
||||
public static void initDimension(int dim) {
|
||||
+ if (dim == 0) return; // Cauldron - overworld
|
||||
WorldServer overworld = getWorld(0);
|
||||
if (overworld == null)
|
||||
{
|
||||
@@ -231,6 +278,12 @@
|
||||
}
|
||||
try
|
||||
{
|
||||
+ // Cauldron start - Fixes MultiVerse issue when mods such as Twilight Forest try to hotload their dimension when using its WorldProvider
|
||||
+ if(net.minecraftforge.cauldron.CauldronHooks.craftWorldLoading)
|
||||
+ {
|
||||
+ return;
|
||||
+ }
|
||||
+ // Cauldron end
|
||||
DimensionManager.getProviderType(dim);
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -242,9 +295,52 @@
|
||||
ISaveHandler savehandler = overworld.getSaveHandler();
|
||||
WorldSettings worldSettings = new WorldSettings(overworld.getWorldInfo());
|
||||
|
||||
- WorldServer world = (dim == 0 ? overworld : new WorldServerMulti(mcServer, savehandler, overworld.getWorldInfo().getWorldName(), dim, worldSettings, overworld, mcServer.theProfiler));
|
||||
+ // Cauldron start - handles hotloading dimensions
|
||||
+ String worldType;
|
||||
+ String name;
|
||||
+ String oldName = "";
|
||||
+ Environment env = Environment.getEnvironment(getProviderType(dim));
|
||||
+ if (dim >= -1 && dim <= 1)
|
||||
+ {
|
||||
+ if ((dim == -1 && !mcServer.getAllowNether()) || (dim == 1 && !mcServer.server.getAllowEnd()))
|
||||
+ return;
|
||||
+ worldType = env.toString().toLowerCase();
|
||||
+ name = "DIM" + dim;
|
||||
+ }
|
||||
+ else
|
||||
+ {
|
||||
+ WorldProvider provider = WorldProvider.getProviderForDimension(dim);
|
||||
+ worldType = provider.getClass().getSimpleName().toLowerCase();
|
||||
+ worldType = worldType.replace("worldprovider", "");
|
||||
+ oldName = "world_" + worldType;
|
||||
+ worldType = worldType.replace("provider", "");
|
||||
+
|
||||
+ if (Environment.getEnvironment(DimensionManager.getProviderType(dim)) == null)
|
||||
+ env = DimensionManager.registerBukkitEnvironment(DimensionManager.getProviderType(provider.getClass()), worldType);
|
||||
+
|
||||
+ name = provider.getSaveFolder();
|
||||
+ if (name == null) name = "DIM0";
|
||||
+ }
|
||||
+ // add ability to disable dimensions
|
||||
+ if (!MinecraftServer.getServer().cauldronConfig.getBoolean("world-environment-settings." + worldType + ".enabled", true))
|
||||
+ return;
|
||||
+
|
||||
+ CauldronUtils.migrateWorlds(worldType, oldName, overworld.getWorldInfo().getWorldName(), name); // Cauldron
|
||||
+ ChunkGenerator gen = mcServer.server.getGenerator(name);
|
||||
+ if (mcServer instanceof DedicatedServer) {
|
||||
+ worldSettings.func_82750_a(((DedicatedServer) mcServer).getStringProperty("generator-settings", ""));
|
||||
+ }
|
||||
+ WorldServer world = new WorldServerMulti(mcServer, new AnvilSaveHandler(mcServer.server.getWorldContainer(), name, true), name, dim, worldSettings, overworld, mcServer.theProfiler, env, gen);
|
||||
+
|
||||
+ if (gen != null)
|
||||
+ {
|
||||
+ world.getWorld().getPopulators().addAll(gen.getDefaultPopulators(world.getWorld()));
|
||||
+ }
|
||||
+ mcServer.getConfigurationManager().setPlayerManager(mcServer.worlds.toArray(new WorldServer[mcServer.worlds.size()]));
|
||||
world.addWorldAccess(new WorldManager(mcServer, world));
|
||||
MinecraftForge.EVENT_BUS.post(new WorldEvent.Load(world));
|
||||
+ mcServer.server.getPluginManager().callEvent(new org.bukkit.event.world.WorldLoadEvent(world.getWorld()));
|
||||
+ // Cauldron end
|
||||
if (!mcServer.isSinglePlayer())
|
||||
{
|
||||
world.getWorldInfo().setGameType(mcServer.getGameType());
|
||||
@@ -253,6 +349,79 @@
|
||||
mcServer.func_147139_a(mcServer.func_147135_j());
|
||||
}
|
||||
|
||||
+ // Cauldron start - new method for handling creation of Bukkit dimensions. Currently supports MultiVerse
|
||||
+ public static WorldServer initDimension(WorldCreator creator, WorldSettings worldSettings) {
|
||||
+ WorldServer overworld = getWorld(0);
|
||||
+ if (overworld == null) {
|
||||
+ throw new RuntimeException("Cannot Hotload Dim: Overworld is not Loaded!");
|
||||
+ }
|
||||
+
|
||||
+ MinecraftServer mcServer = overworld.func_73046_m();
|
||||
+
|
||||
+ String worldType;
|
||||
+ String name;
|
||||
+
|
||||
+ int providerId = 0;
|
||||
+ if (creator.environment() != null)
|
||||
+ providerId = creator.environment().getId();
|
||||
+ try {
|
||||
+ providerId = getProviderType(providerId);
|
||||
+ }
|
||||
+ catch (IllegalArgumentException e)
|
||||
+ {
|
||||
+ // do nothing
|
||||
+ }
|
||||
+
|
||||
+ Environment env = creator.environment();
|
||||
+ worldType = env.name().toLowerCase();
|
||||
+ name = creator.name();
|
||||
+ int dim = 0;
|
||||
+ // Use saved dimension from level.dat if it exists. This guarantees that after a world is created, the same dimension will be used. Fixes issues with MultiVerse
|
||||
+ AnvilSaveHandler saveHandler = new AnvilSaveHandler(mcServer.server.getWorldContainer(), name, true);
|
||||
+ if (saveHandler.loadWorldInfo() != null)
|
||||
+ {
|
||||
+ int savedDim = saveHandler.loadWorldInfo().getDimension();
|
||||
+ if (savedDim != 0 && savedDim != -1 && savedDim != 1)
|
||||
+ {
|
||||
+ dim = savedDim;
|
||||
+ }
|
||||
+ }
|
||||
+ if (dim == 0)
|
||||
+ {
|
||||
+ dim = getNextFreeDimId();
|
||||
+ }
|
||||
+
|
||||
+ if (!isDimensionRegistered(dim)) // handle reloads properly
|
||||
+ {
|
||||
+ registerDimension(dim, providerId);
|
||||
+ addBukkitDimension(dim);
|
||||
+ }
|
||||
+ ChunkGenerator gen = creator.generator();
|
||||
+ if (mcServer instanceof DedicatedServer) {
|
||||
+ worldSettings.func_82750_a(((DedicatedServer) mcServer).getStringProperty("generator-settings", ""));
|
||||
+ }
|
||||
+
|
||||
+ WorldServer world = new WorldServerMulti(mcServer, saveHandler, name, dim, worldSettings, overworld, mcServer.theProfiler, env, gen);
|
||||
+
|
||||
+ if (gen != null)
|
||||
+ {
|
||||
+ world.getWorld().getPopulators().addAll(gen.getDefaultPopulators(world.getWorld()));
|
||||
+ }
|
||||
+ world.provider.dimensionId = dim; // Fix for TerrainControl injecting their own WorldProvider
|
||||
+ mcServer.getConfigurationManager().setPlayerManager(mcServer.worlds.toArray(new WorldServer[mcServer.worlds.size()]));
|
||||
+
|
||||
+ world.addWorldAccess(new WorldManager(mcServer, world));
|
||||
+ MinecraftForge.EVENT_BUS.post(new WorldEvent.Load(world));
|
||||
+ if (!mcServer.isSinglePlayer())
|
||||
+ {
|
||||
+ world.getWorldInfo().setGameType(mcServer.getGameType());
|
||||
+ }
|
||||
+ mcServer.func_147139_a(mcServer.func_147135_j());
|
||||
+
|
||||
+ return world;
|
||||
+ }
|
||||
+ // Cauldron end
|
||||
+
|
||||
public static WorldServer getWorld(int id)
|
||||
{
|
||||
return worlds.get(id);
|
||||
@@ -266,7 +435,7 @@
|
||||
public static boolean shouldLoadSpawn(int dim)
|
||||
{
|
||||
int id = getProviderType(dim);
|
||||
- return spawnSettings.containsKey(id) && spawnSettings.get(id);
|
||||
+ return ((spawnSettings.containsKey(id) && spawnSettings.get(id)) || (getWorld(dim) != null && getWorld(dim).keepSpawnInMemory)); // Cauldron added bukkit check
|
||||
}
|
||||
|
||||
static
|
||||
@@ -306,7 +475,8 @@
|
||||
}
|
||||
|
||||
public static void unloadWorld(int id) {
|
||||
- unloadQueue.add(id);
|
||||
+ if (!shouldLoadSpawn(id)) // Cauldron - prevent mods from force unloading if we have it disabled
|
||||
+ unloadQueue.add(id);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -315,26 +485,9 @@
|
||||
public static void unloadWorlds(Hashtable<Integer, long[]> worldTickTimes) {
|
||||
for (int id : unloadQueue) {
|
||||
WorldServer w = worlds.get(id);
|
||||
- try {
|
||||
- if (w != null)
|
||||
- {
|
||||
- w.saveAllChunks(true, null);
|
||||
- }
|
||||
- else
|
||||
- {
|
||||
- FMLLog.warning("Unexpected world unload - world %d is already unloaded", id);
|
||||
- }
|
||||
- } catch (MinecraftException e) {
|
||||
- e.printStackTrace();
|
||||
- }
|
||||
- finally
|
||||
+ if (w != null)
|
||||
{
|
||||
- if (w != null)
|
||||
- {
|
||||
- MinecraftForge.EVENT_BUS.post(new WorldEvent.Unload(w));
|
||||
- w.flush();
|
||||
- setWorld(id, null);
|
||||
- }
|
||||
+ MinecraftServer.getServer().server.unloadWorld(w.getWorld(), true); // Cauldron - unload through our new method for simplicity
|
||||
}
|
||||
}
|
||||
unloadQueue.clear();
|
||||
@@ -425,4 +578,45 @@
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+
|
||||
+ // Cauldron start - add registration for Bukkit Environments
|
||||
+ public static Environment registerBukkitEnvironment(int dim, String providerName)
|
||||
+ {
|
||||
+ Environment env = Environment.getEnvironment(dim);
|
||||
+ if (env == null) // Cauldron if environment not found, register one
|
||||
+ {
|
||||
+ providerName = providerName.replace("WorldProvider", "");
|
||||
+ env = EnumHelper.addBukkitEnvironment(dim, providerName.toUpperCase());
|
||||
+ Environment.registerEnvironment(env);
|
||||
+ }
|
||||
+ return env;
|
||||
+ }
|
||||
+
|
||||
+ public static int getProviderType(Class<? extends WorldProvider> provider)
|
||||
+ {
|
||||
+ return classToProviders.get(provider);
|
||||
+ }
|
||||
+
|
||||
+ public static void addBukkitDimension(int dim)
|
||||
+ {
|
||||
+ if (!bukkitDims.contains(dim))
|
||||
+ bukkitDims.add(dim);
|
||||
+ }
|
||||
+
|
||||
+ public static void removeBukkitDimension(int dim)
|
||||
+ {
|
||||
+ if (bukkitDims.contains(dim))
|
||||
+ bukkitDims.remove(bukkitDims.indexOf(dim));
|
||||
+ }
|
||||
+
|
||||
+ public static ArrayList<Integer> getBukkitDimensionIDs()
|
||||
+ {
|
||||
+ return bukkitDims;
|
||||
+ }
|
||||
+
|
||||
+ public static boolean isBukkitDimension(int dim)
|
||||
+ {
|
||||
+ return bukkitDims.contains(dim);
|
||||
+ }
|
||||
+ // Cauldron end
|
||||
}
|
45
patches/net/minecraftforge/common/ForgeHooks.java.patch
Normal file
45
patches/net/minecraftforge/common/ForgeHooks.java.patch
Normal file
@ -0,0 +1,45 @@
|
||||
--- ../src-base/minecraft/net/minecraftforge/common/ForgeHooks.java
|
||||
+++ ../src-work/minecraft/net/minecraftforge/common/ForgeHooks.java
|
||||
@@ -63,6 +63,7 @@
|
||||
import net.minecraftforge.event.world.BlockEvent;
|
||||
import net.minecraftforge.event.world.NoteBlockEvent;
|
||||
import static net.minecraft.init.Blocks.*;
|
||||
+import net.minecraftforge.common.util.FakePlayer; // Cauldron
|
||||
|
||||
public class ForgeHooks
|
||||
{
|
||||
@@ -439,6 +440,8 @@
|
||||
|
||||
public static BlockEvent.BreakEvent onBlockBreakEvent(World world, GameType gameType, EntityPlayerMP entityPlayer, int x, int y, int z)
|
||||
{
|
||||
+ // Cauldron - pre-cancel handled in BreakEvent
|
||||
+ /*
|
||||
// Logic from tryHarvestBlock for pre-canceling the event
|
||||
boolean preCancelEvent = false;
|
||||
if (gameType.isAdventure() && !entityPlayer.isCurrentToolAdventureModeExempt(x, y, z))
|
||||
@@ -449,9 +452,9 @@
|
||||
{
|
||||
preCancelEvent = true;
|
||||
}
|
||||
-
|
||||
+ */
|
||||
// Tell client the block is gone immediately then process events
|
||||
- if (world.getTileEntity(x, y, z) == null)
|
||||
+ if (world.getTileEntity(x, y, z) == null && !(entityPlayer instanceof FakePlayer)) // Cauldron - don't send packets to fakeplayers
|
||||
{
|
||||
S23PacketBlockChange packet = new S23PacketBlockChange(x, y, z, world);
|
||||
packet.field_148883_d = Blocks.air;
|
||||
@@ -463,11 +466,11 @@
|
||||
Block block = world.getBlock(x, y, z);
|
||||
int blockMetadata = world.getBlockMetadata(x, y, z);
|
||||
BlockEvent.BreakEvent event = new BlockEvent.BreakEvent(x, y, z, world, block, blockMetadata, entityPlayer);
|
||||
- event.setCanceled(preCancelEvent);
|
||||
+ // event.setCanceled(preCancelEvent); // Cauldron
|
||||
MinecraftForge.EVENT_BUS.post(event);
|
||||
|
||||
// Handle if the event is canceled
|
||||
- if (event.isCanceled())
|
||||
+ if (event.isCanceled() && !(entityPlayer instanceof FakePlayer)) // Cauldron - don't send packets to fakeplayers
|
||||
{
|
||||
// Let the client know the block still exists
|
||||
entityPlayer.playerNetServerHandler.sendPacket(new S23PacketBlockChange(x, y, z, world));
|
11
patches/net/minecraftforge/common/ForgeVersion.java.patch
Normal file
11
patches/net/minecraftforge/common/ForgeVersion.java.patch
Normal file
@ -0,0 +1,11 @@
|
||||
--- ../src-base/minecraft/net/minecraftforge/common/ForgeVersion.java
|
||||
+++ ../src-work/minecraft/net/minecraftforge/common/ForgeVersion.java
|
||||
@@ -25,7 +25,7 @@
|
||||
//This number is incremented every time a interface changes or new major feature is added, and reset every Minecraft version
|
||||
public static final int revisionVersion = 2;
|
||||
//This number is incremented every time Jenkins builds Forge, and never reset. Should always be 0 in the repo code.
|
||||
- public static final int buildVersion = 0;
|
||||
+ public static final int buildVersion = 1291; // Cauldron
|
||||
|
||||
private static Status status = PENDING;
|
||||
private static String target = null;
|
@ -0,0 +1,22 @@
|
||||
--- ../src-base/minecraft/net/minecraftforge/common/WorldSpecificSaveHandler.java
|
||||
+++ ../src-work/minecraft/net/minecraftforge/common/WorldSpecificSaveHandler.java
|
||||
@@ -1,6 +1,7 @@
|
||||
package net.minecraftforge.common;
|
||||
|
||||
import java.io.File;
|
||||
+import java.util.UUID;
|
||||
|
||||
import net.minecraft.world.chunk.storage.IChunkLoader;
|
||||
import net.minecraft.world.storage.IPlayerFileData;
|
||||
@@ -43,4 +44,11 @@
|
||||
return new File(dataDir, name + ".dat");
|
||||
}
|
||||
|
||||
+ // Cauldron start
|
||||
+ @Override
|
||||
+ public UUID getUUID()
|
||||
+ {
|
||||
+ return parent.getUUID();
|
||||
+ }
|
||||
+ // Cauldron end
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
--- ../src-base/minecraft/net/minecraftforge/common/chunkio/ChunkIOProvider.java
|
||||
+++ ../src-work/minecraft/net/minecraftforge/common/chunkio/ChunkIOProvider.java
|
||||
@@ -9,6 +9,9 @@
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
+import org.bukkit.Server;
|
||||
+import org.bukkit.craftbukkit.util.LongHash;
|
||||
+
|
||||
class ChunkIOProvider implements AsynchronousExecutor.CallBackProvider<QueuedChunk, net.minecraft.world.chunk.Chunk, Runnable, RuntimeException> {
|
||||
private final AtomicInteger threadNumber = new AtomicInteger(1);
|
||||
|
||||
@@ -41,13 +44,20 @@
|
||||
queuedChunk.loader.loadEntities(queuedChunk.world, queuedChunk.compound.getCompoundTag("Level"), chunk);
|
||||
MinecraftForge.EVENT_BUS.post(new ChunkDataEvent.Load(chunk, queuedChunk.compound)); // Don't call ChunkDataEvent.Load async
|
||||
chunk.lastSaveTime = queuedChunk.provider.worldObj.getTotalWorldTime();
|
||||
- queuedChunk.provider.loadedChunkHashMap.add(ChunkCoordIntPair.chunkXZ2Int(queuedChunk.x, queuedChunk.z), chunk);
|
||||
+ queuedChunk.provider.loadedChunkHashMap.put(LongHash.toLong(queuedChunk.x, queuedChunk.z), chunk);
|
||||
queuedChunk.provider.loadedChunks.add(chunk);
|
||||
chunk.onChunkLoad();
|
||||
|
||||
if (queuedChunk.provider.currentChunkProvider != null) {
|
||||
+ queuedChunk.provider.worldObj.timings.syncChunkLoadStructuresTimer.startTiming(); // Spigot
|
||||
queuedChunk.provider.currentChunkProvider.recreateStructures(queuedChunk.x, queuedChunk.z);
|
||||
+ queuedChunk.provider.worldObj.timings.syncChunkLoadStructuresTimer.stopTiming(); // Spigot
|
||||
}
|
||||
+
|
||||
+ Server server = queuedChunk.provider.worldObj.getServer();
|
||||
+ if (server != null) {
|
||||
+ server.getPluginManager().callEvent(new org.bukkit.event.world.ChunkLoadEvent(chunk.bukkitChunk, false));
|
||||
+ }
|
||||
|
||||
chunk.populateChunk(queuedChunk.provider, queuedChunk.provider, queuedChunk.x, queuedChunk.z);
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
--- ../src-base/minecraft/net/minecraftforge/common/network/ForgeNetworkHandler.java
|
||||
+++ ../src-work/minecraft/net/minecraftforge/common/network/ForgeNetworkHandler.java
|
||||
@@ -35,4 +35,9 @@
|
||||
clientChannel.pipeline().addAfter(handlerName, "DimensionHandler", new DimensionMessageHandler());
|
||||
clientChannel.pipeline().addAfter(handlerName, "FluidIdRegistryHandler", new FluidIdRegistryMessageHandler());
|
||||
}
|
||||
+
|
||||
+ public static FMLEmbeddedChannel getServerChannel()
|
||||
+ {
|
||||
+ return channelPair.get(Side.SERVER);
|
||||
+ }
|
||||
}
|
195
patches/net/minecraftforge/common/util/EnumHelper.java.patch
Normal file
195
patches/net/minecraftforge/common/util/EnumHelper.java.patch
Normal file
@ -0,0 +1,195 @@
|
||||
--- ../src-base/minecraft/net/minecraftforge/common/util/EnumHelper.java
|
||||
+++ ../src-work/minecraft/net/minecraftforge/common/util/EnumHelper.java
|
||||
@@ -21,7 +21,23 @@
|
||||
import net.minecraft.world.EnumSkyBlock;
|
||||
import net.minecraft.world.gen.structure.StructureStrongholdPieces.Stronghold.Door;
|
||||
import net.minecraftforge.classloading.FMLForgePlugin;
|
||||
+// Cauldron start
|
||||
+import cpw.mods.fml.relauncher.ReflectionHelper;
|
||||
+import net.minecraft.inventory.IInventory;
|
||||
+import net.minecraft.server.MinecraftServer;
|
||||
+import net.minecraft.tileentity.TileEntity;
|
||||
|
||||
+import org.apache.logging.log4j.Level;
|
||||
+import org.apache.logging.log4j.LogManager;
|
||||
+import org.apache.logging.log4j.Logger;
|
||||
+import org.bukkit.World;
|
||||
+import org.bukkit.WorldType;
|
||||
+import org.bukkit.block.Biome;
|
||||
+import org.bukkit.craftbukkit.entity.CraftEntity;
|
||||
+import org.bukkit.entity.EntityType;
|
||||
+import org.bukkit.event.inventory.InventoryType;
|
||||
+// Cauldron end
|
||||
+
|
||||
public class EnumHelper
|
||||
{
|
||||
private static Object reflectionFactory = null;
|
||||
@@ -30,6 +46,7 @@
|
||||
private static Method newFieldAccessor = null;
|
||||
private static Method fieldAccessorSet = null;
|
||||
private static boolean isSetup = false;
|
||||
+ private static final Logger logger = LogManager.getLogger();
|
||||
|
||||
//Some enums are decompiled with extra arguments, so lets check for that
|
||||
@SuppressWarnings("rawtypes")
|
||||
@@ -51,6 +68,73 @@
|
||||
{EnumRarity.class, EnumChatFormatting.class, String.class}
|
||||
};
|
||||
|
||||
+ // Cauldron start
|
||||
+ public static Biome addBukkitBiome(String name)
|
||||
+ {
|
||||
+ return (Biome)addEnum(Biome.class, name, new Class[0], new Object[0]);
|
||||
+ }
|
||||
+
|
||||
+ public static World.Environment addBukkitEnvironment(int id, String name)
|
||||
+ {
|
||||
+ if (!isSetup)
|
||||
+ {
|
||||
+ setup();
|
||||
+ }
|
||||
+
|
||||
+ return (World.Environment)addEnum(World.Environment.class, name, new Class[] { Integer.TYPE }, new Object[] { Integer.valueOf(id) });
|
||||
+ }
|
||||
+
|
||||
+ public static WorldType addBukkitWorldType(String name)
|
||||
+ {
|
||||
+ if (!isSetup)
|
||||
+ {
|
||||
+ setup();
|
||||
+ }
|
||||
+
|
||||
+ WorldType worldType = addEnum(WorldType.class, name, new Class [] { String.class }, new Object[] { name });
|
||||
+ Map<String, WorldType> BY_NAME = ReflectionHelper.getPrivateValue(WorldType.class, null, "BY_NAME");
|
||||
+ BY_NAME.put(name.toUpperCase(), worldType);
|
||||
+
|
||||
+ return worldType;
|
||||
+ }
|
||||
+
|
||||
+ public static EntityType addBukkitEntityType(String name, Class <? extends org.bukkit.entity.Entity> clazz, int typeId, boolean independent) {
|
||||
+ String entityType = name.replace("-", "_").toUpperCase();
|
||||
+ EntityType bukkitType = addEnum(EntityType.class, entityType, new Class[] { String.class, Class.class, Integer.TYPE, Boolean.TYPE }, new Object[] { name, clazz, typeId, independent });
|
||||
+
|
||||
+ Map<String, EntityType> NAME_MAP = ReflectionHelper.getPrivateValue(EntityType.class, null, "NAME_MAP");
|
||||
+ Map<Short, EntityType> ID_MAP = ReflectionHelper.getPrivateValue(EntityType.class, null, "ID_MAP");
|
||||
+
|
||||
+ NAME_MAP.put(name.toLowerCase(), bukkitType);
|
||||
+ ID_MAP.put((short)typeId, bukkitType);
|
||||
+
|
||||
+
|
||||
+ return bukkitType;
|
||||
+ }
|
||||
+
|
||||
+ public static InventoryType addInventoryType(TileEntity tileentity)
|
||||
+ {
|
||||
+ if (!IInventory.class.isAssignableFrom(tileentity.getClass())) return null;
|
||||
+ String id = (String)TileEntity.classToNameMap.get(tileentity.getClass());
|
||||
+
|
||||
+ try
|
||||
+ {
|
||||
+ IInventory teInv = (IInventory)tileentity;
|
||||
+ int size = teInv.getSizeInventory();
|
||||
+ return addEnum(org.bukkit.event.inventory.InventoryType.class, id, new Class[]{Integer.TYPE, String.class}, new Object[]{size, id});
|
||||
+ }
|
||||
+ catch (Throwable e)
|
||||
+ {
|
||||
+ if (MinecraftServer.getServer().tileEntityConfig.enableTEInventoryWarning.getValue())
|
||||
+ {
|
||||
+ logger.log(Level.WARN, "Could not create inventory type " + tileentity.getClass().getName() + " Exception: " + e.toString());
|
||||
+ logger.log(Level.WARN, "Could not determine default inventory size for type " + tileentity.getClass().getName() + " using size of 9");
|
||||
+ }
|
||||
+ return addEnum(org.bukkit.event.inventory.InventoryType.class, id, new Class[]{Integer.TYPE, String.class}, new Object[]{9, id});
|
||||
+ }
|
||||
+ }
|
||||
+ // Cauldron end
|
||||
+
|
||||
public static EnumAction addAction(String name)
|
||||
{
|
||||
return addEnum(EnumAction.class, name);
|
||||
@@ -280,6 +364,86 @@
|
||||
}
|
||||
}
|
||||
|
||||
+ // Cauldron start
|
||||
+ @SuppressWarnings("unchecked")
|
||||
+ public static <T extends Enum<?>> T replaceEnum(Class<T> enumType, String enumName, int ordinal, Class<?>[] paramTypes, Object[] paramValues)
|
||||
+ {
|
||||
+ if (!isSetup)
|
||||
+ {
|
||||
+ setup();
|
||||
+ }
|
||||
+
|
||||
+ Field valuesField = null;
|
||||
+ Field[] fields = enumType.getDeclaredFields();
|
||||
+
|
||||
+ for (Field field : fields)
|
||||
+ {
|
||||
+ String name = field.getName();
|
||||
+ if (name.equals("$VALUES") || name.equals("ENUM$VALUES")) //Added 'ENUM$VALUES' because Eclipse's internal compiler doesn't follow standards
|
||||
+ {
|
||||
+ valuesField = field;
|
||||
+ break;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ int flags = (FMLForgePlugin.RUNTIME_DEOBF ? Modifier.PUBLIC : Modifier.PRIVATE) | Modifier.STATIC | Modifier.FINAL | 0x1000 /*SYNTHETIC*/;
|
||||
+ if (valuesField == null)
|
||||
+ {
|
||||
+ String valueType = String.format("[L%s;", enumType.getName().replace('.', '/'));
|
||||
+
|
||||
+ for (Field field : fields)
|
||||
+ {
|
||||
+ if ((field.getModifiers() & flags) == flags &&
|
||||
+ field.getType().getName().replace('.', '/').equals(valueType)) //Apparently some JVMs return .'s and some don't..
|
||||
+ {
|
||||
+ valuesField = field;
|
||||
+ break;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ if (valuesField == null)
|
||||
+ {
|
||||
+ FMLLog.severe("Could not find $VALUES field for enum: %s", enumType.getName());
|
||||
+ FMLLog.severe("Runtime Deobf: %s", FMLForgePlugin.RUNTIME_DEOBF);
|
||||
+ FMLLog.severe("Flags: %s", String.format("%16s", Integer.toBinaryString(flags)).replace(' ', '0'));
|
||||
+ FMLLog.severe("Fields:");
|
||||
+ for (Field field : fields)
|
||||
+ {
|
||||
+ String mods = String.format("%16s", Integer.toBinaryString(field.getModifiers())).replace(' ', '0');
|
||||
+ FMLLog.severe(" %s %s: %s", mods, field.getName(), field.getType().getName());
|
||||
+ }
|
||||
+ return null;
|
||||
+ }
|
||||
+
|
||||
+ valuesField.setAccessible(true);
|
||||
+ try
|
||||
+ {
|
||||
+ Enum[] previousValues = (Enum[])(Enum[])valuesField.get(enumType);
|
||||
+ Enum[] newValues = new Enum[previousValues.length];
|
||||
+ Enum newValue = null;
|
||||
+ for (Enum enumValue : previousValues)
|
||||
+ {
|
||||
+ if (enumValue.ordinal() == ordinal)
|
||||
+ {
|
||||
+ newValue = makeEnum(enumType, enumName, ordinal, paramTypes, paramValues);
|
||||
+ newValues[enumValue.ordinal()] = newValue;
|
||||
+ }
|
||||
+ else newValues[enumValue.ordinal()] = enumValue;
|
||||
+ }
|
||||
+ List values = new ArrayList(Arrays.asList(newValues));
|
||||
+ setFailsafeFieldValue(valuesField, null, values.toArray((Enum[])(Enum[])Array.newInstance(enumType, 0)));
|
||||
+ cleanEnumCache(enumType);
|
||||
+ return (T) newValue;
|
||||
+ }
|
||||
+ catch (Exception e)
|
||||
+ {
|
||||
+ e.printStackTrace();
|
||||
+ throw new RuntimeException(e.getMessage(), e);
|
||||
+ }
|
||||
+ }
|
||||
+ // Cauldron end
|
||||
+
|
||||
static
|
||||
{
|
||||
if (!isSetup)
|
@ -0,0 +1,31 @@
|
||||
--- ../src-base/minecraft/net/minecraftforge/common/util/FakePlayerFactory.java
|
||||
+++ ../src-work/minecraft/net/minecraftforge/common/util/FakePlayerFactory.java
|
||||
@@ -35,12 +35,24 @@
|
||||
*/
|
||||
public static FakePlayer get(WorldServer world, GameProfile username)
|
||||
{
|
||||
- if (!fakePlayers.containsKey(username))
|
||||
+ // Cauldron start - Refactored below to avoid a hashCode check with a null GameProfile ID
|
||||
+ if (username == null || username.getName() == null) return null;
|
||||
+
|
||||
+ for (Map.Entry<GameProfile, FakePlayer> mapEntry : fakePlayers.entrySet())
|
||||
{
|
||||
- FakePlayer fakePlayer = new FakePlayer(world, username);
|
||||
- fakePlayers.put(username, fakePlayer);
|
||||
+ GameProfile gameprofile = mapEntry.getKey();
|
||||
+ if (gameprofile.getName().equals(username.getName()))
|
||||
+ {
|
||||
+ return mapEntry.getValue();
|
||||
+ }
|
||||
}
|
||||
-
|
||||
+ FakePlayer fakePlayer = new FakePlayer(world, username);
|
||||
+ if (username.getId() == null) // GameProfile hashCode check will fail with a null ID
|
||||
+ {
|
||||
+ username = new GameProfile(UUID.randomUUID(), username.getName()); // Create new GameProfile with random UUID
|
||||
+ }
|
||||
+ // Cauldron end
|
||||
+ fakePlayers.put(username, fakePlayer);
|
||||
return fakePlayers.get(username);
|
||||
}
|
||||
|
@ -0,0 +1,18 @@
|
||||
--- ../src-base/minecraft/net/minecraftforge/event/entity/living/LivingSpawnEvent.java
|
||||
+++ ../src-work/minecraft/net/minecraftforge/event/entity/living/LivingSpawnEvent.java
|
||||
@@ -48,6 +48,7 @@
|
||||
public CheckSpawn(EntityLiving entity, World world, float x, float y, float z)
|
||||
{
|
||||
super(entity, world, x, y, z);
|
||||
+ entity.spawnReason = "natural"; // Cauldron - used to handle CraftBukkit's SpawnReason with CustomSpawners
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,6 +72,7 @@
|
||||
public SpecialSpawn(EntityLiving entity, World world, float x, float y, float z)
|
||||
{
|
||||
super(entity, world, x, y, z);
|
||||
+ entity.spawnReason = "spawner"; // Cauldron - used to handle CraftBukkit's SpawnReason with CustomSpawners
|
||||
}
|
||||
}
|
||||
|
39
patches/net/minecraftforge/event/world/BlockEvent.java.patch
Normal file
39
patches/net/minecraftforge/event/world/BlockEvent.java.patch
Normal file
@ -0,0 +1,39 @@
|
||||
--- ../src-base/minecraft/net/minecraftforge/event/world/BlockEvent.java
|
||||
+++ ../src-work/minecraft/net/minecraftforge/event/world/BlockEvent.java
|
||||
@@ -16,6 +16,11 @@
|
||||
import net.minecraftforge.common.ForgeHooks;
|
||||
import net.minecraftforge.common.util.BlockSnapshot;
|
||||
|
||||
+// Cauldron start
|
||||
+import org.bukkit.craftbukkit.event.CraftEventFactory;
|
||||
+import net.minecraft.entity.player.EntityPlayerMP;
|
||||
+// Cauldron end
|
||||
+
|
||||
public class BlockEvent extends Event {
|
||||
private static final boolean DEBUG = Boolean.parseBoolean(System.getProperty("forge.debugBlockEvent", "false"));
|
||||
|
||||
@@ -80,17 +85,18 @@
|
||||
super(x, y, z, world, block, blockMetadata);
|
||||
this.player = player;
|
||||
|
||||
- if (block == null || !ForgeHooks.canHarvestBlock(block, player, blockMetadata) || // Handle empty block or player unable to break block scenario
|
||||
- block.canSilkHarvest(world, player, x, y, z, blockMetadata) && EnchantmentHelper.getSilkTouchModifier(player)) // If the block is being silk harvested, the exp dropped is 0
|
||||
+ // Cauldron start - handle event on bukkit side
|
||||
+ org.bukkit.event.block.BlockBreakEvent bukkitEvent = CraftEventFactory.callBlockBreakEvent(world, x, y, z, block, blockMetadata,
|
||||
+ (EntityPlayerMP) player);
|
||||
+ if (bukkitEvent.isCancelled())
|
||||
{
|
||||
- this.exp = 0;
|
||||
+ this.setCanceled(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
- int meta = block.getDamageValue(world, x, y, z);
|
||||
- int bonusLevel = EnchantmentHelper.getFortuneModifier(player);
|
||||
- this.exp = block.getExpDrop(world, meta, bonusLevel);
|
||||
+ this.exp = bukkitEvent.getExpToDrop();
|
||||
}
|
||||
+ // Cauldron end
|
||||
}
|
||||
|
||||
public EntityPlayer getPlayer()
|
20
patches/net/minecraftforge/oredict/OreDictionary.java.patch
Normal file
20
patches/net/minecraftforge/oredict/OreDictionary.java.patch
Normal file
@ -0,0 +1,20 @@
|
||||
--- ../src-base/minecraft/net/minecraftforge/oredict/OreDictionary.java
|
||||
+++ ../src-work/minecraft/net/minecraftforge/oredict/OreDictionary.java
|
||||
@@ -216,7 +216,7 @@
|
||||
{
|
||||
ShapedRecipes recipe = (ShapedRecipes)obj;
|
||||
ItemStack output = recipe.getRecipeOutput();
|
||||
- if (output != null && containsMatch(false, exclusions, output))
|
||||
+ if ((output != null && containsMatch(false, exclusions, output)) || output == null) // Cauldron - fixes NPE's with null recipes being added to forge
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -231,7 +231,7 @@
|
||||
{
|
||||
ShapelessRecipes recipe = (ShapelessRecipes)obj;
|
||||
ItemStack output = recipe.getRecipeOutput();
|
||||
- if (output != null && containsMatch(false, exclusions, output))
|
||||
+ if ((output != null && containsMatch(false, exclusions, output)) || output == null) // Cauldron - fixes NPE's with null recipes being added to forge
|
||||
{
|
||||
continue;
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
--- ../src-base/minecraft/net/minecraftforge/oredict/ShapedOreRecipe.java
|
||||
+++ ../src-work/minecraft/net/minecraftforge/oredict/ShapedOreRecipe.java
|
||||
@@ -6,6 +6,10 @@
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
+// Cauldron start
|
||||
+import org.bukkit.inventory.Recipe;
|
||||
+// Cauldron end
|
||||
+
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.item.crafting.IRecipe;
|
||||
import net.minecraft.inventory.InventoryCrafting;
|
||||
@@ -13,6 +17,7 @@
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.item.crafting.ShapedRecipes;
|
||||
import net.minecraft.world.World;
|
||||
+import net.minecraftforge.cauldron.inventory.CustomModRecipe;
|
||||
|
||||
public class ShapedOreRecipe implements IRecipe
|
||||
{
|
||||
@@ -25,6 +30,7 @@
|
||||
private int width = 0;
|
||||
private int height = 0;
|
||||
private boolean mirrored = true;
|
||||
+ private ShapedRecipes vanillaRecipe = null; // Cauldron - bukkit compatibility
|
||||
|
||||
public ShapedOreRecipe(Block result, Object... recipe){ this(new ItemStack(result), recipe); }
|
||||
public ShapedOreRecipe(Item result, Object... recipe){ this(new ItemStack(result), recipe); }
|
||||
@@ -127,6 +133,7 @@
|
||||
|
||||
ShapedOreRecipe(ShapedRecipes recipe, Map<ItemStack, String> replacements)
|
||||
{
|
||||
+ vanillaRecipe = recipe; // Cauldron - bukkit compatibility
|
||||
output = recipe.getRecipeOutput();
|
||||
width = recipe.recipeWidth;
|
||||
height = recipe.recipeHeight;
|
||||
@@ -255,4 +262,13 @@
|
||||
{
|
||||
return this.input;
|
||||
}
|
||||
+
|
||||
+ // Cauldron start - required for Bukkit API
|
||||
+ @Override
|
||||
+ public Recipe toBukkitRecipe() {
|
||||
+ if (vanillaRecipe != null)
|
||||
+ return vanillaRecipe.toBukkitRecipe();
|
||||
+ return new CustomModRecipe(this);
|
||||
+ }
|
||||
+ // Cauldron end
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
--- ../src-base/minecraft/net/minecraftforge/oredict/ShapelessOreRecipe.java
|
||||
+++ ../src-work/minecraft/net/minecraftforge/oredict/ShapelessOreRecipe.java
|
||||
@@ -6,6 +6,10 @@
|
||||
import java.util.Map.Entry;
|
||||
import java.util.List;
|
||||
|
||||
+// Cauldron start
|
||||
+import org.bukkit.inventory.Recipe;
|
||||
+// Cauldron end
|
||||
+
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.item.crafting.IRecipe;
|
||||
import net.minecraft.inventory.InventoryCrafting;
|
||||
@@ -13,11 +17,13 @@
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.item.crafting.ShapelessRecipes;
|
||||
import net.minecraft.world.World;
|
||||
+import net.minecraftforge.cauldron.inventory.CustomModRecipe;
|
||||
|
||||
public class ShapelessOreRecipe implements IRecipe
|
||||
{
|
||||
private ItemStack output = null;
|
||||
private ArrayList<Object> input = new ArrayList<Object>();
|
||||
+ private ShapelessRecipes vanillaRecipe = null; // Cauldron - bukkit compatibility
|
||||
|
||||
public ShapelessOreRecipe(Block result, Object... recipe){ this(new ItemStack(result), recipe); }
|
||||
public ShapelessOreRecipe(Item result, Object... recipe){ this(new ItemStack(result), recipe); }
|
||||
@@ -59,6 +65,7 @@
|
||||
@SuppressWarnings("unchecked")
|
||||
ShapelessOreRecipe(ShapelessRecipes recipe, Map<ItemStack, String> replacements)
|
||||
{
|
||||
+ vanillaRecipe = recipe; // Cauldron - bukkit compatibility
|
||||
output = recipe.getRecipeOutput();
|
||||
|
||||
for(ItemStack ingred : ((List<ItemStack>)recipe.recipeItems))
|
||||
@@ -146,4 +153,13 @@
|
||||
{
|
||||
return this.input;
|
||||
}
|
||||
+
|
||||
+ // Cauldron start - required for Bukkit API
|
||||
+ @Override
|
||||
+ public Recipe toBukkitRecipe() {
|
||||
+ if (vanillaRecipe != null)
|
||||
+ return vanillaRecipe.toBukkitRecipe();
|
||||
+ return new CustomModRecipe(this);
|
||||
+ }
|
||||
+ // Cauldron end
|
||||
}
|
Reference in New Issue
Block a user