diff --git a/fml/patches/minecraft/net/minecraft/block/Block.java.patch b/fml/patches/minecraft/net/minecraft/block/Block.java.patch index 5f824bc2d..7e49ded73 100644 --- a/fml/patches/minecraft/net/minecraft/block/Block.java.patch +++ b/fml/patches/minecraft/net/minecraft/block/Block.java.patch @@ -12,7 +12,7 @@ public class Block { - public static final RegistryNamespaced field_149771_c = new RegistryNamespacedDefaultedByKey("air"); -+ public static final RegistryNamespaced field_149771_c = GameData.blockRegistry; ++ public static final RegistryNamespaced field_149771_c = GameData.getBlockRegistry(); private CreativeTabs field_149772_a; protected String field_149768_d; public static final Block.SoundType field_149769_e = new Block.SoundType("stone", 1.0F, 1.0F); diff --git a/fml/patches/minecraft/net/minecraft/client/LoadingScreenRenderer.java.patch b/fml/patches/minecraft/net/minecraft/client/LoadingScreenRenderer.java.patch new file mode 100644 index 000000000..fe5853480 --- /dev/null +++ b/fml/patches/minecraft/net/minecraft/client/LoadingScreenRenderer.java.patch @@ -0,0 +1,26 @@ +--- ../src-base/minecraft/net/minecraft/client/LoadingScreenRenderer.java ++++ ../src-work/minecraft/net/minecraft/client/LoadingScreenRenderer.java +@@ -1,5 +1,6 @@ + package net.minecraft.client; + ++import cpw.mods.fml.client.FMLClientHandler; + import cpw.mods.fml.relauncher.Side; + import cpw.mods.fml.relauncher.SideOnly; + import net.minecraft.client.gui.Gui; +@@ -147,6 +148,8 @@ + GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT); + } + ++ if (!FMLClientHandler.instance().handleLoadingScreen(scaledresolution)) ++ { + Tessellator tessellator = Tessellator.field_78398_a; + this.field_73725_b.func_110434_K().func_110577_a(Gui.field_110325_k); + float f = 32.0F; +@@ -184,6 +187,7 @@ + OpenGlHelper.func_148821_a(770, 771, 1, 0); + this.field_73725_b.field_71466_p.func_78261_a(this.field_73726_c, (l - this.field_73725_b.field_71466_p.func_78256_a(this.field_73726_c)) / 2, i1 / 2 - 4 - 16, 16777215); + this.field_73725_b.field_71466_p.func_78261_a(this.field_73727_a, (l - this.field_73725_b.field_71466_p.func_78256_a(this.field_73727_a)) / 2, i1 / 2 - 4 + 8, 16777215); ++ } + this.field_146588_g.func_147609_e(); + + if (OpenGlHelper.func_148822_b()) diff --git a/fml/patches/minecraft/net/minecraft/client/Minecraft.java.patch b/fml/patches/minecraft/net/minecraft/client/Minecraft.java.patch index 00d133e9b..9739784ef 100644 --- a/fml/patches/minecraft/net/minecraft/client/Minecraft.java.patch +++ b/fml/patches/minecraft/net/minecraft/client/Minecraft.java.patch @@ -1,16 +1,17 @@ --- ../src-base/minecraft/net/minecraft/client/Minecraft.java +++ ../src-work/minecraft/net/minecraft/client/Minecraft.java -@@ -1,6 +1,9 @@ +@@ -1,6 +1,10 @@ package net.minecraft.client; import com.google.common.collect.Lists; + +import cpw.mods.fml.client.FMLClientHandler; +import cpw.mods.fml.common.FMLCommonHandler; ++import cpw.mods.fml.common.StartupQuery; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import io.netty.util.concurrent.GenericFutureListener; -@@ -146,6 +149,8 @@ +@@ -146,6 +150,8 @@ import org.lwjgl.opengl.PixelFormat; import org.lwjgl.util.glu.GLU; @@ -19,21 +20,7 @@ @SideOnly(Side.CLIENT) public class Minecraft implements IPlayerUsage { -@@ -323,8 +328,11 @@ - - public void func_71404_a(CrashReport p_71404_1_) - { -- this.field_71434_R = true; -- this.field_71433_S = p_71404_1_; -+ if (!FMLClientHandler.instance().handlingCrash(p_71404_1_)) -+ { -+ this.field_71434_R = true; -+ this.field_71433_S = p_71404_1_; -+ } - } - - public void func_71377_b(CrashReport p_71377_1_) -@@ -448,7 +456,7 @@ +@@ -448,7 +454,7 @@ this.field_110451_am = new SimpleReloadableResourceManager(this.field_110452_an); this.field_135017_as = new LanguageManager(this.field_110452_an, this.field_71474_y.field_74363_ab); this.field_110451_am.func_110542_a(this.field_135017_as); @@ -42,7 +29,7 @@ this.field_71446_o = new TextureManager(this.field_110451_am); this.field_110451_am.func_110542_a(this.field_71446_o); this.field_147127_av = new SoundHandler(this.field_110451_am, this.field_71474_y); -@@ -508,12 +516,13 @@ +@@ -508,12 +514,13 @@ this.field_71446_o.func_130088_a(TextureMap.field_110576_c, new TextureMap(1, "textures/items")); GL11.glViewport(0, 0, this.field_71443_c, this.field_71440_d); this.field_71452_i = new EffectRenderer(this.field_71441_e, this.field_71446_o); @@ -57,7 +44,7 @@ } else { -@@ -527,6 +536,7 @@ +@@ -527,6 +534,7 @@ this.func_71352_k(); } @@ -65,7 +52,7 @@ Display.setVSyncEnabled(this.field_71474_y.field_74352_v); } -@@ -916,9 +926,11 @@ +@@ -916,9 +924,11 @@ if (!this.field_71454_w) { @@ -77,7 +64,7 @@ } GL11.glFlush(); -@@ -1496,6 +1508,8 @@ +@@ -1496,6 +1506,8 @@ --this.field_71467_ac; } @@ -86,7 +73,7 @@ this.field_71424_I.func_76320_a("gui"); if (!this.field_71445_n) -@@ -1646,6 +1660,7 @@ +@@ -1646,6 +1658,7 @@ this.field_71462_r.func_146274_d(); } } @@ -94,7 +81,7 @@ } if (this.field_71429_W > 0) -@@ -1787,6 +1802,7 @@ +@@ -1787,6 +1800,7 @@ } } } @@ -102,7 +89,7 @@ } } -@@ -1978,12 +1994,15 @@ +@@ -1978,12 +1992,15 @@ this.field_71453_ak.func_74428_b(); } @@ -118,7 +105,20 @@ this.func_71403_a((WorldClient)null); System.gc(); ISaveHandler isavehandler = this.field_71469_aa.func_75804_a(p_71371_1_, false); -@@ -2094,6 +2113,7 @@ +@@ -2019,6 +2036,12 @@ + + while (!this.field_71437_Z.func_71200_ad()) + { ++ if (!StartupQuery.check()) ++ { ++ func_71403_a(null); ++ func_147108_a(null); ++ return; ++ } + String s2 = this.field_71437_Z.func_71195_b_(); + + if (s2 != null) +@@ -2094,6 +2117,7 @@ this.field_110448_aq.func_148529_f(); this.func_71351_a((ServerData)null); this.field_71455_al = false; diff --git a/fml/patches/minecraft/net/minecraft/item/Item.java.patch b/fml/patches/minecraft/net/minecraft/item/Item.java.patch index f2a83cb9f..c577fca1e 100644 --- a/fml/patches/minecraft/net/minecraft/item/Item.java.patch +++ b/fml/patches/minecraft/net/minecraft/item/Item.java.patch @@ -14,7 +14,7 @@ public class Item { - public static final RegistryNamespaced field_150901_e = new RegistryNamespaced(); -+ public static final RegistryNamespaced field_150901_e = GameData.itemRegistry; ++ public static final RegistryNamespaced field_150901_e = GameData.getItemRegistry(); protected static final UUID field_111210_e = UUID.fromString("CB3F55D3-645C-4F38-A497-9C13A33DB5CF"); private CreativeTabs field_77701_a; protected static Random field_77697_d = new Random(); diff --git a/fml/patches/minecraft/net/minecraft/server/MinecraftServer.java.patch b/fml/patches/minecraft/net/minecraft/server/MinecraftServer.java.patch index 5970b575f..94f0a0c80 100644 --- a/fml/patches/minecraft/net/minecraft/server/MinecraftServer.java.patch +++ b/fml/patches/minecraft/net/minecraft/server/MinecraftServer.java.patch @@ -1,6 +1,6 @@ --- ../src-base/minecraft/net/minecraft/server/MinecraftServer.java +++ ../src-work/minecraft/net/minecraft/server/MinecraftServer.java -@@ -4,6 +4,10 @@ +@@ -4,6 +4,11 @@ import com.mojang.authlib.GameProfile; import com.mojang.authlib.minecraft.MinecraftSessionService; import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService; @@ -8,10 +8,20 @@ +import cpw.mods.fml.common.FMLCommonHandler; +import cpw.mods.fml.common.Loader; +import cpw.mods.fml.common.LoaderState; ++import cpw.mods.fml.common.StartupQuery; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import io.netty.buffer.ByteBuf; -@@ -381,6 +385,7 @@ +@@ -333,7 +338,7 @@ + + public void func_71260_j() + { +- if (!this.field_71290_O) ++ if (!this.field_71290_O && Loader.instance().hasReachedState(LoaderState.SERVER_STARTED)) + { + field_147145_h.info("Stopping server"); + +@@ -381,6 +386,7 @@ { if (this.func_71197_b()) { @@ -19,7 +29,7 @@ long i = func_130071_aq(); long l = 0L; this.field_147147_p.func_151315_a(new ChatComponentText(this.field_71286_C)); -@@ -425,6 +430,7 @@ +@@ -425,12 +431,17 @@ Thread.sleep(1L); this.field_71296_Q = true; } @@ -27,18 +37,17 @@ } else { -@@ -462,7 +468,10 @@ - { - try - { -+ if (Loader.instance().hasReachedState(LoaderState.SERVER_STARTED)) -+ { - this.func_71260_j(); -+ } - this.field_71316_v = true; + this.func_71228_a((CrashReport)null); } - catch (Throwable throwable) -@@ -471,6 +480,8 @@ + } ++ catch (StartupQuery.AbortedException e) ++ { ++ // ignore silently ++ } + catch (Throwable throwable1) + { + field_147145_h.error("Encountered an unexpected exception", throwable1); +@@ -471,6 +482,8 @@ } finally { @@ -47,7 +56,7 @@ this.func_71240_o(); } } -@@ -513,6 +524,7 @@ +@@ -513,6 +526,7 @@ { long i = System.nanoTime(); AxisAlignedBB.func_72332_a().func_72298_a(); @@ -55,7 +64,7 @@ ++this.field_71315_w; if (this.field_71295_T) -@@ -566,6 +578,7 @@ +@@ -566,6 +580,7 @@ this.field_71304_b.func_76319_b(); this.field_71304_b.func_76319_b(); @@ -63,7 +72,7 @@ } public void func_71190_q() -@@ -593,6 +606,7 @@ +@@ -593,6 +608,7 @@ } this.field_71304_b.func_76320_a("tick"); @@ -71,7 +80,7 @@ CrashReport crashreport; try -@@ -617,6 +631,7 @@ +@@ -617,6 +633,7 @@ throw new ReportedException(crashreport); } @@ -79,7 +88,15 @@ this.field_71304_b.func_76319_b(); this.field_71304_b.func_76320_a("tracker"); worldserver.func_73039_n().func_72788_a(); -@@ -695,7 +710,7 @@ +@@ -648,6 +665,7 @@ + + public void func_71256_s() + { ++ StartupQuery.reset(); + (new Thread("Server thread") + { + private static final String __OBFID = "CL_00001418"; +@@ -695,7 +713,7 @@ public String getServerModName() { diff --git a/fml/patches/minecraft/net/minecraft/world/storage/SaveHandler.java.patch b/fml/patches/minecraft/net/minecraft/world/storage/SaveHandler.java.patch index 38007f857..dddda84c7 100644 --- a/fml/patches/minecraft/net/minecraft/world/storage/SaveHandler.java.patch +++ b/fml/patches/minecraft/net/minecraft/world/storage/SaveHandler.java.patch @@ -6,11 +6,11 @@ import java.io.IOException; + +import cpw.mods.fml.common.FMLCommonHandler; -+import cpw.mods.fml.common.registry.GameRegistryException; ++import cpw.mods.fml.common.StartupQuery; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.nbt.CompressedStreamTools; import net.minecraft.nbt.NBTTagCompound; -@@ -107,14 +110,22 @@ +@@ -107,20 +110,29 @@ NBTTagCompound nbttagcompound; NBTTagCompound nbttagcompound1; @@ -27,14 +27,21 @@ + FMLCommonHandler.instance().handleWorldDataLoad(this, worldInfo, nbttagcompound); + return worldInfo; } -+ catch (GameRegistryException gre) ++ catch (StartupQuery.AbortedException e) + { -+ throw gre; ++ throw e; + } catch (Exception exception1) { exception1.printStackTrace(); -@@ -129,8 +140,14 @@ + } + } + ++ FMLCommonHandler.instance().confirmBackupLevelDatUse(this); + file1 = new File(this.field_75770_b, "level.dat_old"); + + if (file1.exists()) +@@ -129,8 +141,14 @@ { nbttagcompound = CompressedStreamTools.func_74796_a(new FileInputStream(file1)); nbttagcompound1 = nbttagcompound.func_74775_l("Data"); @@ -43,14 +50,14 @@ + FMLCommonHandler.instance().handleWorldDataLoad(this, worldInfo, nbttagcompound); + return worldInfo; } -+ catch (GameRegistryException gre) ++ catch (StartupQuery.AbortedException e) + { -+ throw gre; ++ throw e; + } catch (Exception exception) { exception.printStackTrace(); -@@ -146,6 +163,8 @@ +@@ -146,6 +164,8 @@ NBTTagCompound nbttagcompound2 = new NBTTagCompound(); nbttagcompound2.func_74782_a("Data", nbttagcompound1); @@ -59,7 +66,7 @@ try { File file1 = new File(this.field_75770_b, "level.dat_new"); -@@ -184,6 +203,8 @@ +@@ -184,6 +204,8 @@ NBTTagCompound nbttagcompound1 = new NBTTagCompound(); nbttagcompound1.func_74782_a("Data", nbttagcompound); diff --git a/fml/src/main/java/cpw/mods/fml/client/FMLClientHandler.java b/fml/src/main/java/cpw/mods/fml/client/FMLClientHandler.java index 06c2c8161..9f10b5727 100644 --- a/fml/src/main/java/cpw/mods/fml/client/FMLClientHandler.java +++ b/fml/src/main/java/cpw/mods/fml/client/FMLClientHandler.java @@ -21,10 +21,9 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; -import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; + import net.minecraft.client.Minecraft; import net.minecraft.client.entity.EntityClientPlayerMP; import net.minecraft.client.gui.Gui; @@ -32,6 +31,7 @@ import net.minecraft.client.gui.GuiIngameMenu; import net.minecraft.client.gui.GuiMainMenu; import net.minecraft.client.gui.GuiScreen; import net.minecraft.client.gui.GuiSelectWorld; +import net.minecraft.client.gui.ScaledResolution; import net.minecraft.client.gui.ServerListEntryNormal; import net.minecraft.client.multiplayer.GuiConnecting; import net.minecraft.client.multiplayer.ServerData; @@ -55,7 +55,11 @@ import net.minecraft.network.ServerStatusResponse; import net.minecraft.server.MinecraftServer; import net.minecraft.util.ResourceLocation; import net.minecraft.world.WorldSettings; +import net.minecraft.world.storage.SaveFormatOld; + import org.apache.logging.log4j.Level; +import org.lwjgl.input.Mouse; + import com.google.common.base.Strings; import com.google.common.base.Throwables; import com.google.common.collect.BiMap; @@ -67,11 +71,11 @@ import com.google.common.collect.Maps; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; + import cpw.mods.fml.client.registry.RenderingRegistry; import cpw.mods.fml.common.DummyModContainer; import cpw.mods.fml.common.DuplicateModsFoundException; import cpw.mods.fml.common.FMLCommonHandler; -import cpw.mods.fml.common.FMLContainer; import cpw.mods.fml.common.FMLLog; import cpw.mods.fml.common.IFMLSidedHandler; import cpw.mods.fml.common.Loader; @@ -81,14 +85,11 @@ import cpw.mods.fml.common.MissingModsException; import cpw.mods.fml.common.ModContainer; import cpw.mods.fml.common.ModMetadata; import cpw.mods.fml.common.ObfuscationReflectionHelper; -import cpw.mods.fml.common.WorldAccessContainer; +import cpw.mods.fml.common.StartupQuery; import cpw.mods.fml.common.WrongMinecraftVersionException; -import cpw.mods.fml.common.event.FMLMissingMappingsEvent; -import cpw.mods.fml.common.event.FMLMissingMappingsEvent.Action; import cpw.mods.fml.common.eventhandler.EventBus; import cpw.mods.fml.common.network.FMLNetworkEvent; import cpw.mods.fml.common.registry.GameData; -import cpw.mods.fml.common.registry.GameRegistryException; import cpw.mods.fml.common.registry.LanguageRegistry; import cpw.mods.fml.common.toposort.ModSortingException; import cpw.mods.fml.relauncher.Side; @@ -424,6 +425,53 @@ public class FMLClientHandler implements IFMLSidedHandler client.displayGuiScreen(gui); } + @Override + public void queryUser(StartupQuery query) throws InterruptedException + { + if (query.getResult() == null) + { + client.displayGuiScreen(new GuiNotification(query)); + } + else + { + client.displayGuiScreen(new GuiConfirmation(query)); + } + + if (query.isSynchronous()) + { + while (client.currentScreen instanceof GuiNotification) + { + if (Thread.interrupted()) throw new InterruptedException(); + + client.loadingScreen.resetProgresAndWorkingMessage(""); + + Thread.sleep(50); + } + + client.loadingScreen.resetProgresAndWorkingMessage(""); // make sure the blank screen is being drawn at the end + } + } + + public boolean handleLoadingScreen(ScaledResolution scaledResolution) + { + if (client.currentScreen instanceof GuiNotification) + { + int width = scaledResolution.getScaledWidth(); + int height = scaledResolution.getScaledHeight(); + int mouseX = Mouse.getX() * width / client.displayWidth; + int mouseZ = height - Mouse.getY() * height / client.displayHeight - 1; + + client.currentScreen.drawScreen(mouseX, mouseZ, 0); + client.currentScreen.handleInput(); + + return true; + } + else + { + return false; + } + } + public WorldClient getWorldClient() { return client.theWorld; @@ -447,6 +495,12 @@ public class FMLClientHandler implements IFMLSidedHandler // NOOP } + @Override + public File getSavesDirectory() + { + return ((SaveFormatOld) client.getSaveLoader()).savesDirectory; + } + @Override public MinecraftServer getServer() { @@ -595,41 +649,16 @@ public class FMLClientHandler implements IFMLSidedHandler showGuiScreen(new GuiOldSaveLoadConfirm(dirName, saveName, selectWorldGUI)); } else - { - - launchIntegratedServerCallback(dirName, saveName); - } - } - - private CountDownLatch gameReleaseLatch; - private Thread clientWaiter; - private GameRegistryException gre; - - public void launchIntegratedServerCallback(String dirName, String saveName) - { - try { try { - Thread.interrupted(); - gameReleaseLatch = new CountDownLatch(1); - clientWaiter = Thread.currentThread(); client.launchIntegratedServer(dirName, saveName, (WorldSettings)null); - System.out.printf("POKEE %b\n", Thread.currentThread().isInterrupted()); - gameReleaseLatch.await(); } - catch (InterruptedException ie) + catch (StartupQuery.AbortedException e) { - Thread.interrupted(); - throw gre; + // ignore } } - catch (GameRegistryException gre) - { - client.loadWorld(null); - showGuiScreen(new GuiModItemsMissing(gre.getItems(), gre.getMessage())); - } - Thread.interrupted(); } public void showInGameModOptions(GuiIngameMenu guiIngameMenu) @@ -813,70 +842,6 @@ public class FMLClientHandler implements IFMLSidedHandler } } - public void setDefaultMissingAction(FMLMissingMappingsEvent.Action action) - { - this.defaultMissingAction = action; - } - - private Action defaultMissingAction = FMLMissingMappingsEvent.Action.FAIL; - - @Override - public Action getDefaultMissingAction() - { - return defaultMissingAction; - } - - @Override - public void serverLoadedSuccessfully() - { - if (gameReleaseLatch!=null) - { - gameReleaseLatch.countDown(); - } - } - - @Override - public void failedServerLoading(RuntimeException ex, WorldAccessContainer wac) - { - if (wac instanceof FMLContainer && ex instanceof GameRegistryException) - { - try - { - gre = (GameRegistryException) ex; - Executors.newSingleThreadExecutor().submit(new Callable() - { - // This needs to happen in a separate thread so that the server can "crash" - // The three pokes are for the client - it's in a sleep loop, and needs to get - // out of it - @Override - public Void call() throws Exception - { - System.err.println("POKE"); - clientWaiter.interrupt(); - Thread.sleep(50); - System.err.println("POKE"); - clientWaiter.interrupt(); - Thread.sleep(50); - System.err.println("POKE"); - clientWaiter.interrupt(); - return null; - } - - }); - - } - catch (Throwable t) - { - FMLLog.log(Level.ERROR, t, "stuff"); - } - } - } - - public boolean handlingCrash(CrashReport report) - { - return report.getCrashCause() instanceof GameRegistryException; - } - @Override public boolean shouldAllowPlayerLogins() { diff --git a/fml/src/main/java/cpw/mods/fml/client/GuiConfirmation.java b/fml/src/main/java/cpw/mods/fml/client/GuiConfirmation.java new file mode 100644 index 000000000..6f3e1fa62 --- /dev/null +++ b/fml/src/main/java/cpw/mods/fml/client/GuiConfirmation.java @@ -0,0 +1,33 @@ +package cpw.mods.fml.client; + +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.gui.GuiOptionButton; +import net.minecraft.client.resources.I18n; +import cpw.mods.fml.common.StartupQuery; + +public class GuiConfirmation extends GuiNotification +{ + public GuiConfirmation(StartupQuery query) + { + super(query); + } + + @SuppressWarnings("unchecked") + @Override + public void initGui() + { + this.buttonList.add(new GuiOptionButton(0, this.width / 2 - 155, this.height - 38, I18n.format("gui.yes"))); + this.buttonList.add(new GuiOptionButton(1, this.width / 2 - 155 + 160, this.height - 38, I18n.format("gui.no"))); + } + + @Override + protected void actionPerformed(GuiButton button) + { + if (button.enabled && (button.id == 0 || button.id == 1)) + { + FMLClientHandler.instance().showGuiScreen(null); + query.setResult(button.id == 0); + query.finish(); + } + } +} diff --git a/fml/src/main/java/cpw/mods/fml/client/GuiModItemsMissing.java b/fml/src/main/java/cpw/mods/fml/client/GuiModItemsMissing.java deleted file mode 100644 index 4335bfc91..000000000 --- a/fml/src/main/java/cpw/mods/fml/client/GuiModItemsMissing.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Forge Mod Loader - * Copyright (c) 2012-2013 cpw. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser Public License v2.1 - * which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html - * - * Contributors: - * cpw - implementation - */ - -package cpw.mods.fml.client; - -import java.util.List; -import net.minecraft.client.gui.GuiButton; -import net.minecraft.client.gui.GuiScreen; -import net.minecraft.client.resources.I18n; - -public class GuiModItemsMissing extends GuiScreen -{ - private List missingItems; - private String message; - - public GuiModItemsMissing(List items, String message) - { - this.missingItems = items; - this.message = message; - } - - @SuppressWarnings("unchecked") - @Override - public void initGui() - { - this.buttonList.add(new GuiButton(1, this.width / 2 - 75, this.height - 38, I18n.format("gui.done"))); - } - - @Override - protected void actionPerformed(GuiButton p_73875_1_) - { - if (p_73875_1_.enabled && p_73875_1_.id == 1) - { - FMLClientHandler.instance().showGuiScreen(null); - } - } - @Override - public void drawScreen(int p_73863_1_, int p_73863_2_, float p_73863_3_) - { - this.drawDefaultBackground(); - int offset = 85; - this.drawCenteredString(this.fontRendererObj, "Forge Mod Loader could load this save", this.width / 2, offset, 0xFFFFFF); - offset += 10; - this.drawCenteredString(this.fontRendererObj, String.format("There are %d unassigned blocks and items in this save", missingItems.size()), this.width / 2, offset, 0xFFFFFF); - offset += 10; - this.drawCenteredString(this.fontRendererObj, "You will not be able to load until they are present again", this.width / 2, offset, 0xFFFFFF); - super.drawScreen(p_73863_1_, p_73863_2_, p_73863_3_); - } -} diff --git a/fml/src/main/java/cpw/mods/fml/client/GuiNotification.java b/fml/src/main/java/cpw/mods/fml/client/GuiNotification.java new file mode 100644 index 000000000..ffbcc6bc1 --- /dev/null +++ b/fml/src/main/java/cpw/mods/fml/client/GuiNotification.java @@ -0,0 +1,62 @@ +package cpw.mods.fml.client; + +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.resources.I18n; +import cpw.mods.fml.common.StartupQuery; + +public class GuiNotification extends GuiScreen +{ + public GuiNotification(StartupQuery query) + { + this.query = query; + } + + @SuppressWarnings("unchecked") + @Override + public void initGui() + { + this.buttonList.add(new GuiButton(0, this.width / 2 - 100, this.height - 38, I18n.format("gui.done"))); + } + + @Override + protected void actionPerformed(GuiButton button) + { + if (button.enabled && button.id == 0) + { + FMLClientHandler.instance().showGuiScreen(null); + query.finish(); + } + } + + @Override + public void drawScreen(int p_73863_1_, int p_73863_2_, float p_73863_3_) + { + this.drawDefaultBackground(); + + String[] lines = query.getText().split("\n"); + + int spaceAvailable = this.height - 38 - 20; + int spaceRequired = Math.min(spaceAvailable, 10 + 10 * lines.length); + + int offset = 10 + (spaceAvailable - spaceRequired) / 2; // vertically centered + + for (String line : lines) + { + if (offset >= spaceAvailable) + { + this.drawCenteredString(this.fontRendererObj, "...", this.width / 2, offset, 0xFFFFFF); + break; + } + else + { + if (!line.isEmpty()) this.drawCenteredString(this.fontRendererObj, line, this.width / 2, offset, 0xFFFFFF); + offset += 10; + } + } + + super.drawScreen(p_73863_1_, p_73863_2_, p_73863_3_); + } + + protected final StartupQuery query; +} diff --git a/fml/src/main/java/cpw/mods/fml/client/GuiOldSaveLoadConfirm.java b/fml/src/main/java/cpw/mods/fml/client/GuiOldSaveLoadConfirm.java index 922a554b5..426daa06b 100644 --- a/fml/src/main/java/cpw/mods/fml/client/GuiOldSaveLoadConfirm.java +++ b/fml/src/main/java/cpw/mods/fml/client/GuiOldSaveLoadConfirm.java @@ -2,14 +2,19 @@ package cpw.mods.fml.client; import java.io.File; import java.io.IOException; + import net.minecraft.client.gui.GuiButton; import net.minecraft.client.gui.GuiLabel; import net.minecraft.client.gui.GuiScreen; import net.minecraft.client.gui.GuiSelectWorld; import net.minecraft.client.gui.GuiYesNo; +import net.minecraft.world.WorldSettings; + import org.apache.logging.log4j.Level; + import cpw.mods.fml.common.FMLLog; import cpw.mods.fml.common.ObfuscationReflectionHelper; +import cpw.mods.fml.common.StartupQuery; import cpw.mods.fml.common.ZipperUtil; public class GuiOldSaveLoadConfirm extends GuiYesNo { @@ -65,9 +70,15 @@ public class GuiOldSaveLoadConfirm extends GuiYesNo { return; } FMLClientHandler.instance().showGuiScreen(null); - FMLClientHandler.instance().launchIntegratedServerCallback(dirName, saveName); + + try + { + mc.launchIntegratedServer(dirName, saveName, (WorldSettings)null); + } + catch (StartupQuery.AbortedException e) + { + // ignore + } } - } - } diff --git a/fml/src/main/java/cpw/mods/fml/common/FMLCommonHandler.java b/fml/src/main/java/cpw/mods/fml/common/FMLCommonHandler.java index 2c57a4eca..96fc9a07a 100644 --- a/fml/src/main/java/cpw/mods/fml/common/FMLCommonHandler.java +++ b/fml/src/main/java/cpw/mods/fml/common/FMLCommonHandler.java @@ -12,9 +12,12 @@ package cpw.mods.fml.common; +import java.io.File; +import java.lang.ref.WeakReference; import java.util.List; import java.util.Map; import java.util.Set; + import net.minecraft.crash.CrashReport; import net.minecraft.crash.CrashReportCategory; import net.minecraft.entity.item.EntityItem; @@ -29,8 +32,10 @@ import net.minecraft.server.MinecraftServer; import net.minecraft.world.World; import net.minecraft.world.storage.SaveHandler; import net.minecraft.world.storage.WorldInfo; + import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Logger; + import com.google.common.base.Joiner; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; @@ -39,7 +44,7 @@ import com.google.common.collect.Lists; import com.google.common.collect.MapMaker; import com.google.common.collect.Maps; import com.google.common.collect.Sets; -import cpw.mods.fml.common.event.FMLMissingMappingsEvent; + import cpw.mods.fml.common.eventhandler.EventBus; import cpw.mods.fml.common.gameevent.InputEvent; import cpw.mods.fml.common.gameevent.PlayerEvent; @@ -79,6 +84,7 @@ public class FMLCommonHandler private List brandingsNoMC; private List crashCallables = Lists.newArrayList(Loader.instance().getCallableCrashInformation()); private Set handlerSet = Sets.newSetFromMap(new MapMaker().weakKeys().makeMap()); + private WeakReference handlerToCheck; private EventBus eventBus = new EventBus(); /** * The FML event bus. Subscribe here for FML related events @@ -266,7 +272,6 @@ public class FMLCommonHandler public boolean handleServerStarting(MinecraftServer server) { - sidedDelegate.serverLoadedSuccessfully(); return Loader.instance().serverStarting(server); } @@ -280,6 +285,10 @@ public class FMLCommonHandler Loader.instance().serverStopping(); } + public File getSavesDirectory() { + return sidedDelegate.getSavesDirectory(); + } + public MinecraftServer getMinecraftServerInstance() { return sidedDelegate.getServer(); @@ -290,6 +299,11 @@ public class FMLCommonHandler sidedDelegate.showGuiScreen(clientGuiElement); } + public void queryUser(StartupQuery query) throws InterruptedException + { + sidedDelegate.queryUser(query); + } + public void onServerStart(MinecraftServer dedicatedServer) { FMLServerHandler.instance(); @@ -372,6 +386,7 @@ public class FMLCommonHandler return; } handlerSet.add(handler); + handlerToCheck = new WeakReference(handler); // for confirmBackupLevelDatUse Map additionalProperties = Maps.newHashMap(); worldInfo.setAdditionalProperties(additionalProperties); for (ModContainer mc : Loader.instance().getModList()) @@ -381,20 +396,29 @@ public class FMLCommonHandler WorldAccessContainer wac = ((InjectedModContainer)mc).getWrappedWorldAccessContainer(); if (wac != null) { - try - { - wac.readData(handler, worldInfo, additionalProperties, tagCompound.getCompoundTag(mc.getModId())); - } - catch (RuntimeException ex) - { - sidedDelegate.failedServerLoading(ex, wac); - throw ex; - } + wac.readData(handler, worldInfo, additionalProperties, tagCompound.getCompoundTag(mc.getModId())); } } } } + public void confirmBackupLevelDatUse(SaveHandler handler) + { + if (handlerToCheck == null || handlerToCheck.get() != handler) { + // only run if the save has been initially loaded + handlerToCheck = null; + return; + } + + String text = "Forge Mod Loader detected that the backup level.dat is being used.\n\n" + + "This may happen due to a bug or corruption, continuing can damage\n" + + "your world beyond repair or lose data / progress.\n\n" + + "It's recommended to create a world backup before continuing."; + + boolean confirmed = StartupQuery.confirm(text); + if (!confirmed) StartupQuery.abort(); + } + public boolean shouldServerBeKilledQuietly() { if (sidedDelegate == null) @@ -514,11 +538,6 @@ public class FMLCommonHandler sidedDelegate.fireNetRegistrationEvent(bus(), manager, channelSet, channel, side); } - public FMLMissingMappingsEvent.Action getDefaultMissingAction() - { - return sidedDelegate.getDefaultMissingAction(); - } - public boolean shouldAllowPlayerLogins() { return sidedDelegate.shouldAllowPlayerLogins(); diff --git a/fml/src/main/java/cpw/mods/fml/common/FMLContainer.java b/fml/src/main/java/cpw/mods/fml/common/FMLContainer.java index 1f8d54ada..f2d264b63 100644 --- a/fml/src/main/java/cpw/mods/fml/common/FMLContainer.java +++ b/fml/src/main/java/cpw/mods/fml/common/FMLContainer.java @@ -15,19 +15,26 @@ package cpw.mods.fml.common; import java.io.File; import java.security.cert.Certificate; import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; + import net.minecraft.item.Item; import net.minecraft.nbt.NBTBase; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; import net.minecraft.world.storage.SaveHandler; import net.minecraft.world.storage.WorldInfo; + import org.apache.logging.log4j.Level; + import com.google.common.collect.Maps; import com.google.common.eventbus.EventBus; import com.google.common.eventbus.Subscribe; + import cpw.mods.fml.client.FMLFileResourcePack; import cpw.mods.fml.client.FMLFolderResourcePack; import cpw.mods.fml.common.asm.FMLSanityChecker; @@ -36,7 +43,6 @@ import cpw.mods.fml.common.network.NetworkCheckHandler; import cpw.mods.fml.common.network.NetworkRegistry; import cpw.mods.fml.common.network.internal.FMLNetworkHandler; import cpw.mods.fml.common.registry.GameData; -import cpw.mods.fml.common.registry.GameRegistryException; import cpw.mods.fml.relauncher.Side; /** @@ -95,6 +101,7 @@ public class FMLContainer extends DummyModContainer implements WorldAccessContai list.appendTag(mod); } fmlData.setTag("ModList", list); + // name <-> id mappings NBTTagList dataList = new NBTTagList(); FMLLog.fine("Gathering id map for writing to world save %s", info.getWorldName()); Map itemList = GameData.buildItemDataList(); @@ -106,6 +113,29 @@ public class FMLContainer extends DummyModContainer implements WorldAccessContai dataList.appendTag(tag); } fmlData.setTag("ItemData", dataList); + // blocked ids + fmlData.setIntArray("BlockedItemIds", GameData.getBlockedIds()); + // block aliases + NBTTagList blockAliasList = new NBTTagList(); + for (Entry entry : GameData.getBlockRegistry().getAliases().entrySet()) + { + NBTTagCompound tag = new NBTTagCompound(); + tag.setString("K", entry.getKey()); + tag.setString("V", entry.getValue()); + blockAliasList.appendTag(tag); + } + fmlData.setTag("BlockAliases", blockAliasList); + // item aliases + NBTTagList itemAliasList = new NBTTagList(); + for (Entry entry : GameData.getItemRegistry().getAliases().entrySet()) + { + NBTTagCompound tag = new NBTTagCompound(); + tag.setString("K", entry.getKey()); + tag.setString("V", entry.getValue()); + itemAliasList.appendTag(tag); + } + fmlData.setTag("ItemAliases", itemAliasList); + return fmlData; } @@ -132,6 +162,9 @@ public class FMLContainer extends DummyModContainer implements WorldAccessContai } } } + + List failedElements = null; + if (tag.hasKey("ModItemData")) { FMLLog.info("Attempting to convert old world data to new system. This may be trouble!"); @@ -172,26 +205,65 @@ public class FMLContainer extends DummyModContainer implements WorldAccessContai dataList.put(itemLabel, itemId); } } - List failedElements = GameData.injectWorldIDMap(dataList, true, true); - if (!failedElements.isEmpty()) - { - throw new GameRegistryException("Failed to load the world - there are fatal block and item id issues", failedElements); - } + failedElements = GameData.injectWorldIDMap(dataList, true, true); + } else if (tag.hasKey("ItemData")) { - NBTTagList list = tag.getTagList("ItemData", (byte)10); + // name <-> id mappings + NBTTagList list = tag.getTagList("ItemData", 10); Map dataList = Maps.newLinkedHashMap(); for (int i = 0; i < list.tagCount(); i++) { NBTTagCompound dataTag = list.getCompoundTagAt(i); dataList.put(dataTag.getString("K"), dataTag.getInteger("V")); } - List failedElements = GameData.injectWorldIDMap(dataList, true, true); - if (!failedElements.isEmpty()) + + Set blockedIds = new HashSet(); + + if (!tag.hasKey("BlockedItemIds")) // no blocked id info -> old 1.7 save { - throw new GameRegistryException("Failed to load the world - there are fatal block and item id issues", failedElements); + // old early 1.7 save potentially affected by the registry mapping bug + // fix the ids the best we can... + GameData.fixBrokenIds(dataList, blockedIds); } + + // blocked ids + for (int id : tag.getIntArray("BlockedItemIds")) + { + blockedIds.add(id); + } + // block aliases + Map blockAliases = new HashMap(); + list = tag.getTagList("BlockAliases", 10); + for (int i = 0; i < list.tagCount(); i++) + { + NBTTagCompound dataTag = list.getCompoundTagAt(i); + blockAliases.put(dataTag.getString("K"), dataTag.getString("V")); + } + // item aliases + Map itemAliases = new HashMap(); + list = tag.getTagList("ItemAliases", 10); + for (int i = 0; i < list.tagCount(); i++) + { + NBTTagCompound dataTag = list.getCompoundTagAt(i); + itemAliases.put(dataTag.getString("K"), dataTag.getString("V")); + } + + failedElements = GameData.injectWorldIDMap(dataList, blockedIds, blockAliases, itemAliases, true, true); + } + + if (failedElements != null && !failedElements.isEmpty()) + { + String text = "Forge Mod Loader could not load this save.\n\n" + + "There are "+failedElements.size()+" unassigned blocks and items in this save.\n" + + "You will not be able to load until they are present again.\n\n" + + "Missing Blocks/Items:\n"; + + for (String s : failedElements) text += s + "\n"; + + StartupQuery.notify(text); + StartupQuery.abort(); } } diff --git a/fml/src/main/java/cpw/mods/fml/common/FMLLog.java b/fml/src/main/java/cpw/mods/fml/common/FMLLog.java index 19cd725ae..863ab49ea 100644 --- a/fml/src/main/java/cpw/mods/fml/common/FMLLog.java +++ b/fml/src/main/java/cpw/mods/fml/common/FMLLog.java @@ -45,6 +45,13 @@ public class FMLLog log(Level.ERROR, format, data); } + public static void bigWarning(String format, Object... data) + { + log(Level.WARN, "****************************************"); + log(Level.WARN, "* "+format, data); + log(Level.WARN, "****************************************"); + } + public static void warning(String format, Object... data) { log(Level.WARN, format, data); diff --git a/fml/src/main/java/cpw/mods/fml/common/IFMLSidedHandler.java b/fml/src/main/java/cpw/mods/fml/common/IFMLSidedHandler.java index 4a3e86aef..0d4cf8b39 100644 --- a/fml/src/main/java/cpw/mods/fml/common/IFMLSidedHandler.java +++ b/fml/src/main/java/cpw/mods/fml/common/IFMLSidedHandler.java @@ -12,13 +12,13 @@ package cpw.mods.fml.common; +import java.io.File; import java.util.List; import java.util.Set; import net.minecraft.network.INetHandler; import net.minecraft.network.NetworkManager; import net.minecraft.server.MinecraftServer; -import cpw.mods.fml.common.event.FMLMissingMappingsEvent; import cpw.mods.fml.common.eventhandler.EventBus; import cpw.mods.fml.relauncher.Side; @@ -32,10 +32,14 @@ public interface IFMLSidedHandler void showGuiScreen(Object clientGuiElement); + void queryUser(StartupQuery query) throws InterruptedException; + void beginServerLoading(MinecraftServer server); void finishServerLoading(); + File getSavesDirectory(); + MinecraftServer getServer(); boolean shouldServerShouldBeKilledQuietly(); @@ -56,11 +60,5 @@ public interface IFMLSidedHandler void fireNetRegistrationEvent(EventBus bus, NetworkManager manager, Set channelSet, String channel, Side side); - FMLMissingMappingsEvent.Action getDefaultMissingAction(); - - void serverLoadedSuccessfully(); - - void failedServerLoading(RuntimeException ex, WorldAccessContainer wac); - boolean shouldAllowPlayerLogins(); } diff --git a/fml/src/main/java/cpw/mods/fml/common/LoadController.java b/fml/src/main/java/cpw/mods/fml/common/LoadController.java index 1f6131094..bbee723ab 100644 --- a/fml/src/main/java/cpw/mods/fml/common/LoadController.java +++ b/fml/src/main/java/cpw/mods/fml/common/LoadController.java @@ -130,7 +130,7 @@ public class LoadController FMLLog.severe("Fatal errors were detected during the transition from %s to %s. Loading cannot continue", oldState, desiredState); StringBuilder sb = new StringBuilder(); printModStates(sb); - FMLLog.severe(sb.toString()); + FMLLog.severe("%s", sb.toString()); if (errors.size()>0) { FMLLog.severe("The following problems were captured during this phase"); diff --git a/fml/src/main/java/cpw/mods/fml/common/Loader.java b/fml/src/main/java/cpw/mods/fml/common/Loader.java index c7be7ee62..8320dbfda 100644 --- a/fml/src/main/java/cpw/mods/fml/common/Loader.java +++ b/fml/src/main/java/cpw/mods/fml/common/Loader.java @@ -17,13 +17,17 @@ import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.net.MalformedURLException; +import java.util.ArrayList; import java.util.Comparator; import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; + import org.apache.logging.log4j.Level; + import com.google.common.base.CharMatcher; import com.google.common.base.Function; import com.google.common.base.Joiner; @@ -45,6 +49,7 @@ import com.google.common.collect.Ordering; import com.google.common.collect.SetMultimap; import com.google.common.collect.Sets; import com.google.common.collect.TreeMultimap; + import cpw.mods.fml.common.LoaderState.ModState; import cpw.mods.fml.common.ModContainer.Disableable; import cpw.mods.fml.common.discovery.ModDiscoverer; @@ -56,6 +61,7 @@ import cpw.mods.fml.common.event.FMLModIdMappingEvent; import cpw.mods.fml.common.functions.ArtifactVersionNameFunction; import cpw.mods.fml.common.functions.ModIdFunction; import cpw.mods.fml.common.registry.GameData; +import cpw.mods.fml.common.registry.GameRegistry.Type; import cpw.mods.fml.common.toposort.ModSorter; import cpw.mods.fml.common.toposort.ModSortingException; import cpw.mods.fml.common.toposort.ModSortingException.SortingExceptionData; @@ -453,7 +459,6 @@ public class Loader { initializeLoader(); mods = Lists.newArrayList(); - GameData.fixupRegistries(); namedMods = Maps.newHashMap(); modController = new LoadController(this); modController.transition(LoaderState.LOADING, false); @@ -493,7 +498,6 @@ public class Loader } modController.transition(LoaderState.PREINITIALIZATION, false); modController.distributeStateMessage(LoaderState.PREINITIALIZATION, disc.getASMTable(), canonicalConfigDir); - GameData.freezeData(); modController.transition(LoaderState.INITIALIZATION, false); } @@ -676,6 +680,7 @@ public class Loader modController.distributeStateMessage(LoaderState.POSTINITIALIZATION); modController.transition(LoaderState.AVAILABLE, false); modController.distributeStateMessage(LoaderState.AVAILABLE); + GameData.freezeData(); // Dump the custom registry data map, if necessary GameData.dumpRegistry(minecraftDir); FMLLog.info("Forge Mod Loader has successfully loaded %d mod%s", mods.size(), mods.size() == 1 ? "" : "s"); @@ -842,41 +847,74 @@ public class Loader return true; } - public List fireMissingMappingEvent(ArrayListMultimap missing, boolean isLocalWorld) + /** + * Fire a FMLMissingMappingsEvent to let mods determine how blocks/items defined in the world + * save, but missing from the runtime, are to be handled. + * + * @param missing Map containing missing names with their associated id, blocks need to come before items for remapping. + * @param isLocalWorld Whether this is executing for a world load (local/server) or a client. + * @param gameData GameData instance where the new map's config is to be loaded into. + * @return List with the mapping results. + */ + public List fireMissingMappingEvent(LinkedHashMap missing, boolean isLocalWorld, GameData gameData, Map remaps) { - if (!missing.isEmpty()) + if (missing.isEmpty()) // nothing to do { - FMLLog.fine("There are %d mappings missing - attempting a mod remap", missing.size()); - ArrayListMultimap missingMappings = ArrayListMultimap.create(); - List remaps = Lists.newArrayList(); - for (Map.Entry mapping : missing.entries()) + return ImmutableList.of(); + } + + FMLLog.fine("There are %d mappings missing - attempting a mod remap", missing.size()); + ArrayListMultimap missingMappings = ArrayListMultimap.create(); + + for (Map.Entry mapping : missing.entrySet()) + { + int id = mapping.getValue(); + MissingMapping m = new MissingMapping(mapping.getKey(), id); + missingMappings.put(m.name.substring(0, m.name.indexOf(':')), m); + } + + FMLMissingMappingsEvent missingEvent = new FMLMissingMappingsEvent(missingMappings); + modController.propogateStateMessage(missingEvent); + + if (isLocalWorld) // local world, warn about entries still being set to the default action + { + boolean didWarn = false; + + for (MissingMapping mapping : missingMappings.values()) { - MissingMapping m = new MissingMapping(mapping.getValue(), remaps); - missingMappings.put(mapping.getKey(), m); - } - FMLMissingMappingsEvent missingEvent = new FMLMissingMappingsEvent(missingMappings); - modController.propogateStateMessage(missingEvent); - if (!missingMappings.isEmpty() && isLocalWorld) - { - FMLLog.severe("There are unidentified mappings in this world - we are going to attempt to process anyway"); - for (java.util.Map.Entry missed : missingMappings.entries()) + if (mapping.getAction() == FMLMissingMappingsEvent.Action.DEFAULT) { - remaps.add(missed.getValue()); + if (!didWarn) + { + FMLLog.severe("There are unidentified mappings in this world - we are going to attempt to process anyway"); + didWarn = true; + } + + FMLLog.severe("Unidentified %s: %s, id %d", mapping.type == Type.BLOCK ? "block" : "item", mapping.name, mapping.id); } } - else if (!missingMappings.isEmpty() && !isLocalWorld) + } + else // remote world, fail on entries with the default action + { + List missedMapping = new ArrayList(); + + for (MissingMapping mapping : missingMappings.values()) { - List missedMapping = Lists.newArrayList(); - for (java.util.Map.Entry missed : missingMappings.entries()) + if (mapping.getAction() == FMLMissingMappingsEvent.Action.DEFAULT) { - missedMapping.add(missed.getKey()+ ":" + missed.getValue().name); + missedMapping.add(mapping.name); } + } + + if (!missedMapping.isEmpty()) + { return ImmutableList.copyOf(missedMapping); } - return GameData.processIdRematches(remaps, isLocalWorld); } - return ImmutableList.of(); + + return GameData.processIdRematches(missingMappings.values(), isLocalWorld, gameData, remaps); } + public void fireRemapEvent(Map remaps) { if (remaps.isEmpty()) diff --git a/fml/src/main/java/cpw/mods/fml/common/StartupQuery.java b/fml/src/main/java/cpw/mods/fml/common/StartupQuery.java new file mode 100644 index 000000000..07d138983 --- /dev/null +++ b/fml/src/main/java/cpw/mods/fml/common/StartupQuery.java @@ -0,0 +1,160 @@ +package cpw.mods.fml.common; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; + +import net.minecraft.server.MinecraftServer; + +public class StartupQuery { + // internal class/functionality, do not use + + public static boolean confirm(String text) + { + StartupQuery query = new StartupQuery(text, new AtomicBoolean()); + query.execute(); + return query.getResult(); + } + + public static void notify(String text) + { + StartupQuery query = new StartupQuery(text, null); + query.execute(); + } + + public static void abort() + { + MinecraftServer server = FMLCommonHandler.instance().getMinecraftServerInstance(); + if (server != null) server.initiateShutdown(); + + aborted = true; // to abort loading and go back to the main menu + throw new AbortedException(); // to halt the server + } + + + public static void reset() + { + pending = null; + aborted = false; + } + + public static boolean check() + { + if (pending != null) + { + try + { + FMLCommonHandler.instance().queryUser(pending); + } + catch (InterruptedException e) + { + FMLLog.warning("query interrupted"); + abort(); + } + + pending = null; + } + + return !aborted; + } + + private static volatile StartupQuery pending; + private static volatile boolean aborted = false; + + + private StartupQuery(String text, AtomicBoolean result) + { + this.text = text; + this.result = result; + } + + public Boolean getResult() + { + return result == null ? null : result.get(); + } + + public void setResult(boolean result) + { + this.result.set(result); + } + + public String getText() + { + return text; + } + + public boolean isSynchronous() + { + return synchronous; + } + + public void finish() + { + signal.countDown(); + } + + private void execute() + { + String prop = System.getProperty("fml.queryResult"); + + if (result != null && prop != null) + { + FMLLog.info("Using fml.queryResult %s to answer the following query:\n%s", prop, text); + + if (prop.equalsIgnoreCase("confirm")) + { + setResult(true); + return; + } + else if (prop.equalsIgnoreCase("cancel")) + { + setResult(false); + return; + } + + FMLLog.warning("Invalid value for fml.queryResult: %s, expected confirm or cancel", prop); + } + + synchronous = false; + pending = this; // let the other thread start the query + + // from the integrated server thread: the client will eventually check pending and execute the query + // from the client thread: synchronous execution + // dedicated server: command handling in mc is synchronous, execute the server-side query directly + if (FMLCommonHandler.instance().getSide().isServer() || + FMLCommonHandler.instance().getEffectiveSide().isClient()) + { + synchronous = true; + check(); + } + + try + { + signal.await(); + reset(); + } + catch (InterruptedException e) + { + FMLLog.warning("query interrupted"); + abort(); + } + } + + private String text; + private AtomicBoolean result; + private CountDownLatch signal = new CountDownLatch(1); + private volatile boolean synchronous; + + + /** + * Exception not being caught by the crash report generation logic. + */ + public static class AbortedException extends RuntimeException + { + private static final long serialVersionUID = -5933665223696833921L; + + private AbortedException() + { + super(); + } + } +} diff --git a/fml/src/main/java/cpw/mods/fml/common/ZipperUtil.java b/fml/src/main/java/cpw/mods/fml/common/ZipperUtil.java index 79466db3c..0dfaefff8 100644 --- a/fml/src/main/java/cpw/mods/fml/common/ZipperUtil.java +++ b/fml/src/main/java/cpw/mods/fml/common/ZipperUtil.java @@ -10,8 +10,13 @@ import java.util.Deque; import java.util.LinkedList; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; + +import org.apache.logging.log4j.Level; + import com.google.common.io.Files; +import cpw.mods.fml.client.FMLClientHandler; + /** * Copied from http://stackoverflow.com/questions/1399126/java-util-zip-recreating-directory-structure * because the code looked very tidy and neat. Thanks, McDowell! @@ -55,4 +60,39 @@ public class ZipperUtil { res.close(); } } + + public static void backupWorld() throws IOException + { + String dirName = FMLCommonHandler.instance().getMinecraftServerInstance().getFolderName(); + String saveName; + + if (FMLCommonHandler.instance().getSide().isClient()) + { + saveName = FMLCommonHandler.instance().getMinecraftServerInstance().getWorldName(); + } + else + { + saveName = dirName; + } + + backupWorld(dirName, saveName); + } + + public static void backupWorld(String dirName, String saveName) throws IOException + { + File dstFolder = FMLCommonHandler.instance().getSavesDirectory(); + File zip = new File(dstFolder, String.format("%s-%2$tY%2$tm%2$td-%2$tH%2$tM%2$tS.zip", saveName, System.currentTimeMillis())); + + try + { + ZipperUtil.zip(new File(dstFolder, dirName), zip); + } + catch (IOException e) + { + FMLLog.log(Level.WARN, e, "World backup failed."); + throw e; + } + + FMLLog.info("World backup created at %s.", zip.getCanonicalPath()); + } } diff --git a/fml/src/main/java/cpw/mods/fml/common/event/FMLMissingMappingsEvent.java b/fml/src/main/java/cpw/mods/fml/common/event/FMLMissingMappingsEvent.java index 3045c38d1..fdaac1d9b 100644 --- a/fml/src/main/java/cpw/mods/fml/common/event/FMLMissingMappingsEvent.java +++ b/fml/src/main/java/cpw/mods/fml/common/event/FMLMissingMappingsEvent.java @@ -2,19 +2,23 @@ package cpw.mods.fml.common.event; import java.util.List; +import net.minecraft.block.Block; +import net.minecraft.item.Item; + import com.google.common.collect.ImmutableList; import com.google.common.collect.ListMultimap; -import cpw.mods.fml.common.FMLCommonHandler; import cpw.mods.fml.common.ModContainer; +import cpw.mods.fml.common.registry.GameData; import cpw.mods.fml.common.registry.GameRegistry; /** * This event is fired if a world is loaded that has block and item mappings referring the mod that are not * in existence. * These can be remapped to other existing objects, or simply discarded. + * Use get() and getAll() to process this event. * - * @author cpw + * @author cpw, Player * */ public class FMLMissingMappingsEvent extends FMLEvent { @@ -28,56 +32,143 @@ public class FMLMissingMappingsEvent extends FMLEvent { * @author cpw * */ - public static enum Action { IGNORE, WARN, FAIL } + public static enum Action { DEFAULT, IGNORE, WARN, FAIL, REMAP } public static class MissingMapping { public final GameRegistry.Type type; public final String name; - private Action action; - private List remaps; - public MissingMapping(String name, List remaps) + public final int id; + private Action action = Action.DEFAULT; + private Object target; + + public MissingMapping(String name, int id) { this.type = name.charAt(0) == '\u0001' ? GameRegistry.Type.BLOCK : GameRegistry.Type.ITEM; - this.name = name; - this.remaps = remaps; - this.action = FMLCommonHandler.instance().getDefaultMissingAction(); + this.name = name.substring(1); + this.id = id; } + /** + * @deprecated use ignore(), warn(), fail() or remap() instead + */ + @Deprecated public void setAction(Action target) { + if (target == Action.DEFAULT || target == Action.REMAP) throw new IllegalArgumentException(); + this.action = target; - remaps.add(this); } + /** + * Ignore the missing item. + */ + public void ignore() + { + action = Action.IGNORE; + } + + /** + * Warn the user about the missing item. + */ + public void warn() + { + action = Action.WARN; + } + + /** + * Prevent the world from loading due to the missing item. + */ + public void fail() + { + action = Action.FAIL; + } + + /** + * Remap the missing item to the specified Block. + * + * Use this if you have renamed a Block, don't forget to handle the ItemBlock. + * Existing references using the old name will point to the new one. + * + * @param target Block to remap to. + */ + public void remap(Block target) + { + if (type != GameRegistry.Type.BLOCK) throw new IllegalArgumentException("Can't remap an item to a block."); + if (target == null) throw new NullPointerException("remap target is null"); + if (GameData.getBlockRegistry().getId(target) < 0) throw new IllegalArgumentException(String.format("The specified block %s hasn't been registered at startup.", target)); + + action = Action.REMAP; + this.target = target; + } + + /** + * Remap the missing item to the specified Item. + * + * Use this if you have renamed an Item. + * Existing references using the old name will point to the new one. + * + * @param target Item to remap to. + */ + public void remap(Item target) + { + if (type != GameRegistry.Type.ITEM) throw new IllegalArgumentException("Can't remap a block to an item."); + if (target == null) throw new NullPointerException("remap target is null"); + if (GameData.getItemRegistry().getId(target) < 0) throw new IllegalArgumentException(String.format("The specified item %s hasn't been registered at startup.", target)); + + action = Action.REMAP; + this.target = target; + } + + // internal + public Action getAction() { return this.action; } + + public Object getTarget() + { + return target; + } } private ListMultimap missing; private ModContainer activeContainer; - private List currentList; public FMLMissingMappingsEvent(ListMultimap missingMappings) { this.missing = missingMappings; } + @Override public void applyModContainer(ModContainer activeContainer) { super.applyModContainer(activeContainer); this.activeContainer = activeContainer; - this.currentList = null; } /** * Get the list of missing mappings for the active mod. - * @return + * + * Process the list entries by calling ignore(), warn(), fail() or remap() on each entry. + * + * @return list of missing mappings */ public List get() { - if (currentList == null) - { - currentList = ImmutableList.copyOf(missing.removeAll(activeContainer.getModId())); - } - return currentList; + return ImmutableList.copyOf(missing.get(activeContainer.getModId())); + } + + /** + * Get the list of missing mappings for all mods. + * + * Only use this if you need to handle mod id changes, e.g. if you renamed your mod or + * split/merge into/from multiple mods. + * + * Process the list entries by calling ignore(), warn(), fail() or remap() on each entry you + * want to handle. + * + * @return list of missing mappings + */ + public List getAll() + { + return ImmutableList.copyOf(missing.values()); } } diff --git a/fml/src/main/java/cpw/mods/fml/common/network/internal/EntitySpawnHandler.java b/fml/src/main/java/cpw/mods/fml/common/network/internal/EntitySpawnHandler.java index 5faf82e27..450d1cf2a 100644 --- a/fml/src/main/java/cpw/mods/fml/common/network/internal/EntitySpawnHandler.java +++ b/fml/src/main/java/cpw/mods/fml/common/network/internal/EntitySpawnHandler.java @@ -57,7 +57,6 @@ public class EntitySpawnHandler extends SimpleChannelInboundHandler extends RegistryNamespaced { - static class FMLObjectIntIdentityMap extends ObjectIntIdentityMap { - private TIntIntHashMap frozenMap; - private TIntIntHashMap oldMap; - private TIntIntHashMap newMap; - private ArrayList frozenIndex; - private ArrayList oldIndex; - private ArrayList newIndex; - - public FMLObjectIntIdentityMap() - { - } - - boolean containsID(int id) - { - return func_148744_b(id); - } - - Object get(int id) - { - return func_148745_a(id); - } - - int get(Object obj) - { - return func_148747_b(obj); - } - - @SuppressWarnings("unchecked") - void beginSwap() - { - oldMap = field_148749_a; - newMap = new TIntIntHashMap(256, 0.5F, -1, -1); - oldIndex = (ArrayList) field_148748_b; - newIndex = new ArrayList(oldIndex.size()); - } - - @SuppressWarnings("unchecked") - void freezeMap() - { - frozenMap = new TIntIntHashMap(field_148749_a); - frozenIndex = new ArrayList(field_148748_b); - } - - void revertToFrozen() - { - field_148749_a = frozenMap; - field_148748_b = frozenIndex; - } - void completeSwap() - { - field_148749_a = newMap; - field_148748_b = newIndex; - oldIndex = newIndex = null; - oldMap = newMap = null; - } - - void revertSwap() - { - field_148749_a = oldMap; - field_148748_b = oldIndex; - oldIndex = newIndex = null; - oldMap = newMap = null; - } - - void putNew(int id, Object item) - { - field_148749_a = newMap; - field_148748_b = newIndex; - super.func_148746_a(item, id); - field_148749_a = oldMap; - field_148748_b = oldIndex; - } - - List usedIds() - { - return Ints.asList(field_148749_a.keys()); - } - - } - private final Class superType; private String optionalDefaultName; private I optionalDefaultObject; - - private BiMap namedIds = HashBiMap.create(); - private BiMap frozenIds; - private Map transactionalNamedIds; - private BitSet transactionalAvailabilityMap; - private BitSet availabilityMap; - int maxId; + private int maxId; private int minId; private char discriminator; + // aliases redirecting legacy names to the actual name, may need recursive application to find the final name. + // these need to be registry specific, it's possible to only have a loosely linked item for a block which may get renamed by itself. + private final Map aliases = new HashMap(); - public FMLControlledNamespacedRegistry(String optionalDefault, int maxIdValue, int minIdValue, Class type, char discriminator) + FMLControlledNamespacedRegistry(String optionalDefault, int maxIdValue, int minIdValue, Class type, char discriminator) { this.superType = type; this.discriminator = discriminator; this.optionalDefaultName = optionalDefault; - this.availabilityMap = new BitSet(maxIdValue); this.maxId = maxIdValue; this.minId = minIdValue; - this.underlyingIntegerMap = new FMLObjectIntIdentityMap(); } + @SuppressWarnings("unchecked") + void set(FMLControlledNamespacedRegistry registry) + { + if (this.superType != registry.superType) throw new IllegalArgumentException("incompatible registry"); + + this.discriminator = registry.discriminator; + this.optionalDefaultName = registry.optionalDefaultName; + this.maxId = registry.maxId; + this.minId = registry.minId; + this.aliases.clear(); + this.aliases.putAll(registry.aliases); + underlyingIntegerMap = new ObjectIntIdentityMap(); + registryObjects.clear(); + + for (I thing : (Iterable) registry) + { + addObjectRaw(registry.getId(thing), registry.getNameForObject(thing), thing); + } + } + + // public api + + /** + * Add an object to the registry, trying to use the specified id. + * + * @deprecated register through {@link GameRegistry} instead. + */ @Override + @Deprecated public void addObject(int id, String name, Object thing) { - FMLLog.finer("Add : %s %d %s", name, id, thing); - add(id, name, superType.cast(thing)); + GameData.getMain().register(thing, name, id); } - int swap(int id, String name, I thing) + /** + * DANGEROUS! EVIL! DO NOT USE! + * + * @deprecated register through {@link GameRegistry} instead. + */ + @Override + @Deprecated + public void putObject(Object objName, Object obj) { - FMLLog.fine("Swap : %s %d %s", name, id, thing); - BitSet temporary = availabilityMap; - availabilityMap = transactionalAvailabilityMap; + String name = (String) objName; + I thing = (I) obj; - int idToUse = id; - if (id == 0 || availabilityMap.get(id)) - { - idToUse = availabilityMap.nextClearBit(minId); - } - if (idToUse >= maxId) - { - throw new RuntimeException(String.format("Invalid id %s - not accepted",id)); - } + if (name == null) throw new NullPointerException("Can't use a null-name for the registry."); + if (name.isEmpty()) throw new IllegalArgumentException("Can't use an empty name for the registry."); + if (thing == null) throw new NullPointerException("Can't add null-object to the registry."); - namedIds.forcePut(ensureNamespaced(name),idToUse); - reassignMapping(name, idToUse); - useSlot(idToUse); - availabilityMap = temporary; - FMLLog.fine("Swap : %s %d %s", name, idToUse, thing); - return idToUse; + String existingName = getNameForObject(thing); + + if (existingName == null) + { + FMLLog.bigWarning("Ignoring putObject(%s, %s), not resolvable", name, thing); + } + else if (existingName.equals(name)) + { + FMLLog.bigWarning("Ignoring putObject(%s, %s), already added", name, thing); + } + else + { + FMLLog.bigWarning("Ignoring putObject(%s, %s), adding alias to %s instead", name, thing, existingName); + addAlias(name, existingName); + } } - public int add(int id, String name, I thing) + + /** + * Fetch the object identified by the specified name or the default object. + * + * For blocks the default object is the air block, for items it's null. + * + * @param name Unique name identifying the object. + * @return Registered object of the default object if it wasn't found- + */ + @Override + public I getObject(String name) { + I object = getRaw(name); + return object == null ? this.optionalDefaultObject : object; + } + + /** + * Fetch the object identified by the specified id or the default object. + * + * For blocks the default object is the air block, for items it's null. + * + * @param id ID identifying the object. + * @return Registered object of the default object if it wasn't found- + */ + @Override + public I getObjectById(int id) + { + I object = getRaw(id); + return object == null ? this.optionalDefaultObject : object; + } + + /** + * @deprecated use getObjectById instead + */ + @Deprecated + public I get(int id) + { + return getObjectById(id); + } + + /** + * @deprecated use getObject instead + */ + @Deprecated + public I get(String name) + { + return getObject(name); + } + + /** + * Get the id for the specified object. + * + * Don't hold onto the id across the world, it's being dynamically re-mapped as needed. + * + * Usually the name should be used instead of the id, if using the Block/Item object itself is + * not suitable for the task. + * + * @param thing Block/Item object. + * @return Block/Item id or -1 if it wasn't found. + */ + public int getId(I thing) + { + return getIDForObject(thing); + } + + /** + * Get the object identified by the specified id. + * + * @param id Block/Item id. + * @return Block/Item object or null if it wasn't found. + */ + public I getRaw(int id) + { + return superType.cast(super.getObjectById(id)); + } + + /** + * Get the object identified by the specified name. + * + * @param name Block/Item name. + * @return Block/Item object or null if it wasn't found. + */ + public I getRaw(String name) + { + I ret = superType.cast(super.getObject(name)); + + if (ret == null) // no match, try aliases recursively + { + name = aliases.get(name); + + if (name != null) return getRaw(name); + } + + return ret; + } + + /** + * Determine if the registry has an entry for the specified name. + * + * Aliased names will be resolved as well. + * + * @param name Object name to check. + * @return true if a matching entry was found. + */ + @Override + public boolean containsKey(String name) + { + boolean ret = super.containsKey(name); + + if (!ret) // no match, try aliases recursively + { + name = aliases.get(name); + + if (name != null) return containsKey(name); + } + + return ret; + } + + /** + * Get the id for the specified object. + * + * Don't hold onto the id across the world, it's being dynamically re-mapped as needed. + * + * Usually the name should be used instead of the id, if using the Block/Item object itself is + * not suitable for the task. + * + * @param itemName Block/Item registry name. + * @return Block/Item id or -1 if it wasn't found. + */ + public int getId(String itemName) + { + I obj = getRaw(itemName); + if (obj == null) return -1; + + return getId(obj); + } + + /** + * @deprecated use containsKey instead + */ + @Deprecated + public boolean contains(String itemName) + { + return containsKey(itemName); + } + + // internal + + @SuppressWarnings("unchecked") + public void serializeInto(Map idMapping) // for saving + { + for (I thing : (Iterable) this) + { + idMapping.put(discriminator+getNameForObject(thing), getId(thing)); + } + } + + public Map getAliases() // for saving + { + return ImmutableMap.copyOf(aliases); + } + + /** + * Add the specified object to the registry. + * + * @param id ID to use if available, auto-assigned otherwise. + * @param name Name to use, prefixed by the mod id. + * @param thing Object to add. + * @param availabilityMap Map marking available IDs for auto assignment. + * @return ID eventually allocated. + */ + int add(int id, String name, I thing, BitSet availabilityMap) + { + if (name == null) throw new NullPointerException("Can't use a null-name for the registry."); + if (name.isEmpty()) throw new IllegalArgumentException("Can't use an empty name for the registry."); + if (thing == null) throw new NullPointerException("Can't add null-object to the registry."); if (name.equals(optionalDefaultName)) { this.optionalDefaultObject = thing; @@ -164,9 +289,9 @@ public class FMLControlledNamespacedRegistry extends RegistryNamespaced { { idToUse = availabilityMap.nextClearBit(minId); } - if (idToUse >= maxId) + if (idToUse > maxId) { - throw new RuntimeException(String.format("Invalid id %s - not accepted",id)); + throw new RuntimeException(String.format("Invalid id %d - maximum id range exceeded.", id)); } ModContainer mc = Loader.instance().activeModContainer(); @@ -175,150 +300,79 @@ public class FMLControlledNamespacedRegistry extends RegistryNamespaced { String prefix = mc.getModId(); name = prefix + ":"+ name; } - namedIds.forcePut(ensureNamespaced(name),idToUse); - super.addObject(idToUse, name, thing); - useSlot(idToUse); - FMLLog.finer("Add : %s %d %s", name, idToUse, thing); + + if (getRaw(name) == thing) // already registered, return prev registration's id + { + FMLLog.bigWarning("The object %s has been registered twice for the same name %s.", thing, name); + return getId(thing); + } + if (getRaw(name) != null) // duplicate name, will crash later due to the BiMap + { + FMLLog.bigWarning("The name %s has been registered twice, for %s and %s.", name, getRaw(name), thing); + } + if (getId(thing) >= 0) // duplicate object, will crash later due to the BiMap + { + FMLLog.bigWarning("The object %s has been registered twice, using the names %s and %s.", thing, getNameForObject(thing), name); + } + if (GameData.isFrozen(this)) + { + FMLLog.bigWarning("The object %s (name %s) is being added too late.", thing, name); + } + + addObjectRaw(idToUse, name, thing); + + FMLLog.finer("Registry add: %s %d %s", name, idToUse, thing); return idToUse; } - @Override - public I getObject(String name) + void addAlias(String from, String to) { - I object = superType.cast(super.getObject(name)); - return object == null ? this.optionalDefaultObject : object; - } - - @Override - public I getObjectById(int id) - { - I object = superType.cast(super.getObjectById(id)); - return object == null ? this.optionalDefaultObject : object; - } - - - private FMLObjectIntIdentityMap idMap() - { - return (FMLObjectIntIdentityMap) underlyingIntegerMap; + aliases.put(from, to); + FMLLog.finer("Registry alias: %s -> %s", from, to); } @SuppressWarnings("unchecked") - private BiMap nameMap() + Map getEntriesNotIn(FMLControlledNamespacedRegistry registry) { - return (BiMap) registryObjects; - } + Map ret = new HashMap(); - void beginIdSwap() - { - idMap().beginSwap(); - transactionalNamedIds = Maps.newHashMap(); - transactionalAvailabilityMap = new BitSet(); - } - - void reassignMapping(String name, int newId) - { - Object item = nameMap().get(name); - idMap().putNew(newId, item); - transactionalNamedIds.put(name,newId); - transactionalAvailabilityMap.set(newId); - } - - void freezeMap() - { - if (frozenIds == null) + for (I thing : (Iterable) this) { - frozenIds = ImmutableBiMap.copyOf(namedIds); - idMap().freezeMap(); + if (!registry.field_148758_b.containsKey(thing)) ret.put(getNameForObject(thing), getId(thing)); } + + return ret; } - void revertToFrozen() - { - namedIds = HashBiMap.create(frozenIds); - idMap().revertToFrozen(); - } - - Map getMissingMappings() - { - return Maps.difference(frozenIds, transactionalNamedIds).entriesOnlyOnLeft(); - } - void completeIdSwap() - { - idMap().completeSwap(); - namedIds.clear(); - namedIds.putAll(transactionalNamedIds); - transactionalNamedIds = null; - } - - void revertSwap() - { - idMap().revertSwap(); - transactionalNamedIds = null; - } - - public I get(int id) - { - return getObjectById(id); - } - - public I get(String name) - { - return getObject(name); - } - - public int getId(I thing) - { - return getIDForObject(thing); - } - - public void serializeInto(Map idMapping) - { - for (Entry id: namedIds.entrySet()) - { - idMapping.put(discriminator+id.getKey(), id.getValue()); - } - } - - public void useSlot(int id) - { - if (id >= maxId) return; - availabilityMap.set(id); - } - - List usedIds() - { - return ((FMLObjectIntIdentityMap)underlyingIntegerMap).usedIds(); - } - - public int getId(String itemName) - { - if (namedIds.containsKey(itemName)) - { - return namedIds.get(itemName); - } - else - { - return -1; - } - } - - public boolean contains(String itemName) - { - return namedIds.containsKey(itemName); - } - + @SuppressWarnings("unchecked") void dump() { - for (Entry entry : namedIds.entrySet()) + List ids = new ArrayList(); + + for (I thing : (Iterable) this) { - String name = entry.getKey(); - Object thing = idMap().get(entry.getValue().intValue()); - FMLLog.finer("Registry : %s %d %s", name, entry.getValue(), thing); + ids.add(getId(thing)); + } + + // sort by id + Collections.sort(ids); + + for (int id : ids) + { + I thing = getRaw(id); + FMLLog.finer("Registry: %s %d %s", getNameForObject(thing), id, thing); } } - - BitSet slots() + + /** + * Version of addObject not using the API restricting overrides. + */ + private void addObjectRaw(int id, String name, I thing) { - return (BitSet) availabilityMap.clone(); + if (name == null) throw new NullPointerException(); + if (thing == null) throw new NullPointerException(); + + underlyingIntegerMap.func_148746_a(thing, id); // obj <-> id + super.putObject(ensureNamespaced(name), thing); // name <-> obj } } diff --git a/fml/src/main/java/cpw/mods/fml/common/registry/GameData.java b/fml/src/main/java/cpw/mods/fml/common/registry/GameData.java index 1682c2653..47ba3f537 100644 --- a/fml/src/main/java/cpw/mods/fml/common/registry/GameData.java +++ b/fml/src/main/java/cpw/mods/fml/common/registry/GameData.java @@ -14,21 +14,27 @@ package cpw.mods.fml.common.registry; import java.io.File; import java.io.IOException; +import java.util.Arrays; import java.util.BitSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; -import org.apache.logging.log4j.Level; +import java.util.Set; import net.minecraft.block.Block; import net.minecraft.item.Item; import net.minecraft.item.ItemBlock; import net.minecraft.item.ItemStack; +import org.apache.logging.log4j.Level; + import com.google.common.base.Charsets; import com.google.common.base.Joiner; import com.google.common.base.Joiner.MapJoiner; -import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.HashBasedTable; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableListMultimap; @@ -37,63 +43,97 @@ import com.google.common.collect.Maps; import com.google.common.collect.Table; import com.google.common.io.Files; +import cpw.mods.fml.client.FMLClientHandler; +import cpw.mods.fml.common.FMLCommonHandler; import cpw.mods.fml.common.FMLLog; import cpw.mods.fml.common.Loader; import cpw.mods.fml.common.ModContainer; +import cpw.mods.fml.common.StartupQuery; +import cpw.mods.fml.common.ZipperUtil; import cpw.mods.fml.common.event.FMLMissingMappingsEvent; import cpw.mods.fml.common.event.FMLMissingMappingsEvent.MissingMapping; +import cpw.mods.fml.common.registry.GameRegistry.Type; import cpw.mods.fml.common.registry.GameRegistry.UniqueIdentifier; public class GameData { - private static Table customItemStacks = HashBasedTable.create(); + private static final int MIN_BLOCK_ID = 0; + private static final int MAX_BLOCK_ID = 4095; + private static final int MIN_ITEM_ID = 4096; + private static final int MAX_ITEM_ID = 31999; - public static final FMLControlledNamespacedRegistry blockRegistry = new FMLControlledNamespacedRegistry("air", 4095, 0, Block.class,'\u0001'); - public static final FMLControlledNamespacedRegistry itemRegistry = new FMLControlledNamespacedRegistry(null, 32000, 4096, Item.class,'\u0002'); + private static final GameData mainData = new GameData(); + + /** + * @deprecated use {@link getBlockRegistry()} instead. + */ + @Deprecated + public static final FMLControlledNamespacedRegistry blockRegistry = getBlockRegistry(); + /** + * @deprecated use {@link getItemRegistry()} instead. + */ + @Deprecated + public static final FMLControlledNamespacedRegistry itemRegistry = getItemRegistry(); + + private static Table customItemStacks = HashBasedTable.create(); + private static Map customOwners = Maps.newHashMap(); + private static GameData frozen; + + // public api + + /** + * Get the currently active block registry. + * + * @return Block Registry. + */ + public static FMLControlledNamespacedRegistry getBlockRegistry() { + return getMain().iBlockRegistry; + } + + /** + * Get the currently active item registry. + * + * @return Item Registry. + */ + public static FMLControlledNamespacedRegistry getItemRegistry() { + return getMain().iItemRegistry; + } + + /** + * @deprecated no replacement planned + */ + @Deprecated + public static ModContainer findModOwner(String string) + { + UniqueIdentifier ui = new UniqueIdentifier(string); + if (customOwners.containsKey(ui)) + { + return customOwners.get(ui); + } + return Loader.instance().getIndexedModList().get(ui.modId); + } + + // internal from here public static Map buildItemDataList() { Map idMapping = Maps.newHashMap(); - blockRegistry.serializeInto(idMapping); - itemRegistry.serializeInto(idMapping); + getMain().iBlockRegistry.serializeInto(idMapping); + getMain().iItemRegistry.serializeInto(idMapping); return idMapping; } - static Item findItem(String modId, String name) + public static int[] getBlockedIds() { - return (Item) itemRegistry.getObject(modId + ":" + name); - } + int[] ret = new int[getMain().blockedIds.size()]; + int index = 0; - static Block findBlock(String modId, String name) - { - String key = modId + ":" + name; - return blockRegistry.contains(key) ? blockRegistry.getObject(key) : null; - } - - static ItemStack findItemStack(String modId, String name) - { - ItemStack is = customItemStacks.get(modId, name); - if (is == null) + for (int id : getMain().blockedIds) { - Item i = findItem(modId, name); - if (i != null) - { - is = new ItemStack(i, 0 ,0); - } + ret[index] = id; + index++; } - if (is == null) - { - Block b = findBlock(modId, name); - if (b != null) - { - is = new ItemStack(b, 0, Short.MAX_VALUE); - } - } - return is; - } - static void registerCustomItemStack(String name, ItemStack itemStack) - { - customItemStacks.put(Loader.instance().activeModContainer().getModId(), name, itemStack); + return ret; } public static void dumpRegistry(File minecraftDir) @@ -124,10 +164,48 @@ public class GameData { } } + static Item findItem(String modId, String name) + { + return (Item) getMain().iItemRegistry.getObject(modId + ":" + name); + } + + static Block findBlock(String modId, String name) + { + String key = modId + ":" + name; + return getMain().iBlockRegistry.containsKey(key) ? getMain().iBlockRegistry.getObject(key) : null; + } + + static ItemStack findItemStack(String modId, String name) + { + ItemStack is = customItemStacks.get(modId, name); + if (is == null) + { + Item i = findItem(modId, name); + if (i != null) + { + is = new ItemStack(i, 0 ,0); + } + } + if (is == null) + { + Block b = findBlock(modId, name); + if (b != null) + { + is = new ItemStack(b, 0, Short.MAX_VALUE); + } + } + return is; + } + + static void registerCustomItemStack(String name, ItemStack itemStack) + { + customItemStacks.put(Loader.instance().activeModContainer().getModId(), name, itemStack); + } + static UniqueIdentifier getUniqueName(Block block) { if (block == null) return null; - String name = blockRegistry.getNameForObject(block); + String name = getMain().iBlockRegistry.getNameForObject(block); UniqueIdentifier ui = new UniqueIdentifier(name); if (customItemStacks.contains(ui.modId, ui.name)) { @@ -140,7 +218,7 @@ public class GameData { static UniqueIdentifier getUniqueName(Item item) { if (item == null) return null; - String name = itemRegistry.getNameForObject(item); + String name = getMain().iItemRegistry.getNameForObject(item); UniqueIdentifier ui = new UniqueIdentifier(name); if (customItemStacks.contains(ui.modId, ui.name)) { @@ -150,203 +228,399 @@ public class GameData { return ui; } - private static Map customOwners = Maps.newHashMap(); - - static void registerBlockAndItem(ItemBlock item, Block block, String name, String modId) + /** + * Fix IDs improperly allocated by early versions of the registry, best-effort. + * + * Items sharing the same ID with a block, but not sharing the same registry name will be + * mapped to an unused id. Losing items instead of blocks should minimize the damage. + * + * @param dataList List containing the IDs to fix + */ + public static void fixBrokenIds(Map dataList, Set blockedIds) { - ModContainer mc = Loader.instance().activeModContainer(); - if (modId != null) + BitSet availabilityMap = new BitSet(MAX_ITEM_ID + 1); + + // reserve all ids occupied by blocks + for (Entry entry : dataList.entrySet()) { - customOwners.put(new UniqueIdentifier(modId, name), mc); - } - BitSet blockAvailability = blockRegistry.slots(); - BitSet itemAvailability = itemRegistry.slots(); - blockAvailability.or(itemAvailability); - int blockId = blockAvailability.nextClearBit(0); - if (blockId >= blockRegistry.maxId) - { - throw new RuntimeException(String.format("No more space for block allocations: used %d block ids", blockId -1)); - } - int actualBlockId = blockRegistry.add(blockId, name, block); - int itemId = itemRegistry.add(blockId, name, item); - if (blockId != actualBlockId || itemId != blockId) - { - throw new RuntimeException(String.format("There was a failure to allocate a matching block and item pair for %s: requested %d, got %d and %d", name, blockId, actualBlockId, itemId)); + String itemName = entry.getKey(); + String realName = itemName.substring(1); + + if (itemName.charAt(0) == '\u0001') // is a block + { + availabilityMap.set(entry.getValue()); + } } - } - static void registerItem(Item item, String name, String modId) - { - ModContainer mc = Loader.instance().activeModContainer(); - if (modId != null) - { - customOwners.put(new UniqueIdentifier(modId, name), mc); - } - if (item instanceof ItemBlock) - { - throw new RuntimeException("Cannot register an itemblock separately from it's block"); - } - int itemId = itemRegistry.add(0, name, item); - blockRegistry.useSlot(itemId); - } + Set newBlockedIds = new HashSet(); + Set itemsToRemove = new HashSet(); + Map itemsToRelocate = new HashMap(); - static void registerBlock(Block block, String name, String modId) - { - ModContainer mc = Loader.instance().activeModContainer(); - if (modId != null) + // check all ids occupied by items + for (Entry entry : dataList.entrySet()) { - customOwners.put(new UniqueIdentifier(modId, name), mc); - } - int blockId = blockRegistry.add(0, name, block); - itemRegistry.useSlot(blockId); - } + String itemName = entry.getKey(); - public static ModContainer findModOwner(String string) - { - UniqueIdentifier ui = new UniqueIdentifier(string); - if (customOwners.containsKey(ui)) - { - return customOwners.get(ui); - } - return Loader.instance().getIndexedModList().get(ui.modId); - } + if (itemName.charAt(0) != '\u0001') // is an item + { + int oldId = entry.getValue(); + String realName = itemName.substring(1); + String blockName = '\u0001' + realName; + Item item = getMain().iItemRegistry.getRaw(realName); + boolean blockThisId = false; // block oldId unless it's used by a block + if (item == null) // item no longer available + { + // can't fix items without reliably checking if they are ItemBlocks + FMLLog.warning("Item %s (old id %d) is no longer available and thus can't be fixed.", realName, oldId); + itemsToRemove.add(itemName); + blockThisId = true; + } + else if (item instanceof ItemBlock) + { + if (dataList.containsKey(blockName)) // the item was an ItemBlock before + { + int blockId = dataList.get(blockName); - public static void fixupRegistries() - { - for (Integer id : blockRegistry.usedIds()) - { - itemRegistry.useSlot(id); + if (blockId != oldId) // mis-located ItemBlock + { + // relocate to the matching block + FMLLog.warning("ItemBlock %s (old id %d) doesn't have the same id as its block (%d).", realName, oldId, blockId); + itemsToRelocate.put(entry.getKey(), blockId); + blockThisId = true; + } + else // intact ItemBlock + { + availabilityMap.set(oldId); // occupy id + } + } + else // the item hasn't been an ItemBlock before, but it's now + { + // can't fix these, drop them + FMLLog.warning("Item %s (old id %d) has been migrated to an ItemBlock and can't be fixed.", realName, oldId); + itemsToRemove.add(itemName); + blockThisId = true; + } + } + else if (availabilityMap.get(oldId)) // normal item, id is already occupied + { + // remove the item mapping + FMLLog.warning("Item %s (old id %d) is conflicting with another block/item and can't be fixed.", realName, oldId); + itemsToRemove.add(itemName); + } + else // intact Item + { + availabilityMap.set(oldId); // occupy id + } + + // handle blocking the id from future use if possible (i.e. not used by a conflicting block) + // blockThisId requests don't modify availabilityMap, it could only be set by a block (or another item, which isn't being handled) + if (blockThisId && !availabilityMap.get(oldId)) + { + // there's no block occupying this id, thus block the id from future use + // as there may still be ItemStacks in the world referencing it + newBlockedIds.add(oldId); + availabilityMap.set(oldId); + } + } } - for (Integer id : itemRegistry.usedIds()) + if (itemsToRemove.isEmpty() && itemsToRelocate.isEmpty()) return; // nothing to do + + // confirm + String text = "Forge Mod Loader detected that this save is damaged.\n\n" + + "It's likely that an automatic repair can successfully restore\n" + + "most of it, except some items which may get swapped with others.\n\n" + + "A world backup will be created as a zip file in your saves\n" + + "directory automatically.\n\n" + + itemsToRemove.size()+" items need to be removed.\n"+ + itemsToRelocate.size()+" items need to be relocated."; + + boolean confirmed = StartupQuery.confirm(text); + if (!confirmed) StartupQuery.abort(); + + // confirm missing mods causing item removal + Set modsMissing = new HashSet(); + + for (String itemName : itemsToRemove) { - blockRegistry.useSlot(id); + modsMissing.add(itemName.substring(1, itemName.indexOf(':'))); } + + for (Iterator it = modsMissing.iterator(); it.hasNext(); ) + { + String mod = it.next(); + + if (mod.equals("minecraft") || Loader.instance().isModLoaded(mod)) it.remove(); + } + + if (!modsMissing.isEmpty()) + { + text = "Forge Mod Loader detected that "+modsMissing.size()+" mods are missing.\n\n" + + "If you continue items previously provided by those mods will be\n" + + "removed while repairing this world save.\n\n" + + "Missing mods:\n"; + + for (String mod : modsMissing) text += mod+"\n"; + + confirmed = StartupQuery.confirm(text); + if (!confirmed) StartupQuery.abort(); + } + + // backup + try + { + ZipperUtil.backupWorld(); + } + catch (IOException e) + { + StartupQuery.notify("The world backup couldn't be created.\n\n"+e); + StartupQuery.abort(); + } + + // apply fix + for (String itemName : itemsToRemove) + { + int id = dataList.remove(itemName); + + FMLLog.warning("Removed Item %s, old id %d.", itemName.substring(1), id); + } + + for (Map.Entry entry : itemsToRelocate.entrySet()) + { + String itemName = entry.getKey(); + int newId = entry.getValue(); + + int oldId = dataList.put(itemName, newId); + + FMLLog.warning("Remapped Item %s to id %d, old id %d.", itemName.substring(1), newId, oldId); + } + + blockedIds.addAll(newBlockedIds); } public static List injectWorldIDMap(Map dataList, boolean injectFrozenData, boolean isLocalWorld) { + return injectWorldIDMap(dataList, new HashSet(), new HashMap(), new HashMap(), injectFrozenData, isLocalWorld); + } + + public static List injectWorldIDMap(Map dataList, Set blockedIds, Map blockAliases, Map itemAliases, boolean injectFrozenData, boolean isLocalWorld) + { + FMLLog.info("Injecting existing block and item data into this %s instance", FMLCommonHandler.instance().getEffectiveSide().isServer() ? "server" : "client"); Map remaps = Maps.newHashMap(); - ArrayListMultimap missing = ArrayListMultimap.create(); - blockRegistry.dump(); - itemRegistry.dump(); - blockRegistry.beginIdSwap(); - itemRegistry.beginIdSwap(); - for (Entry entry : dataList.entrySet()) - { - String itemName = entry.getKey(); - char discriminator = itemName.charAt(0); - itemName = itemName.substring(1); - Integer newId = entry.getValue(); - int currId; - boolean isBlock = discriminator == '\u0001'; - if (isBlock) - { - currId = blockRegistry.getId(itemName); - } - else - { - currId = itemRegistry.getId(itemName); - } + LinkedHashMap missingMappings = new LinkedHashMap(); + getMain().testConsistency(); + getMain().iBlockRegistry.dump(); + getMain().iItemRegistry.dump(); - if (currId == -1) - { - FMLLog.info("Found a missing id from the world %s", itemName); - missing.put(itemName.substring(0, itemName.indexOf(':')), itemName); - } - else if (currId != newId) - { - FMLLog.info("Found %s id mismatch %s : %d %d", isBlock ? "block" : "item", itemName, currId, newId); - remaps.put(itemName, new Integer[] { currId, newId }); - } + GameData newData = new GameData(); - if (isBlock) - { - blockRegistry.reassignMapping(itemName, newId); - } - else - { - itemRegistry.reassignMapping(itemName, newId); - } - } - List missedMappings = Loader.instance().fireMissingMappingEvent(missing, isLocalWorld); - if (!missedMappings.isEmpty()) + for (int id : blockedIds) { - blockRegistry.revertSwap(); - itemRegistry.revertSwap(); - return missedMappings; + newData.block(id); } - if (injectFrozenData) + for (Map.Entry entry : blockAliases.entrySet()) { - FMLLog.info("Injecting new block and item data into this server instance"); - Map missingBlocks = Maps.newHashMap(blockRegistry.getMissingMappings()); - Map missingItems = Maps.newHashMap(itemRegistry.getMissingMappings()); + newData.iBlockRegistry.addAlias(entry.getKey(), entry.getValue()); + } - for (Entry item: missingItems.entrySet()) + for (Map.Entry entry : itemAliases.entrySet()) + { + newData.iItemRegistry.addAlias(entry.getKey(), entry.getValue()); + } + + // process blocks and items in the world, blocks in the first pass, items in the second + // blocks need to be added first for proper ItemBlock handling + for (int pass = 0; pass < 2; pass++) + { + boolean isBlock = (pass == 0); + + for (Entry entry : dataList.entrySet()) { - String itemName = item.getKey(); - if (missingBlocks.containsKey(itemName)) + String itemName = entry.getKey(); + int newId = entry.getValue(); + + // names starting with 0x1 are blocks, skip if the type isn't handled by this pass + if ((itemName.charAt(0) == '\u0001') != isBlock) continue; + + itemName = itemName.substring(1); + int currId = isBlock ? getMain().iBlockRegistry.getId(itemName) : getMain().iItemRegistry.getId(itemName); + + if (currId == -1) { - int blockId = blockRegistry.swap(item.getValue(), itemName, blockRegistry.get(itemName)); - itemRegistry.swap(blockId, itemName, itemRegistry.get(itemName)); - FMLLog.info("Injecting new block/item %s : %d", itemName, blockId); - missingBlocks.remove(itemName); - if (Integer.valueOf(blockId) != item.getValue()) - { - remaps.put(itemName, new Integer[] { item.getValue(), blockId }); - } + FMLLog.info("Found a missing id from the world %s", itemName); + missingMappings.put(entry.getKey(), newId); + continue; // no block/item -> nothing to add } - else + else if (currId != newId) { - FMLLog.info("Injecting new item %s", itemName); - int itemId = itemRegistry.swap(item.getValue(), itemName, itemRegistry.get(itemName)); - if (Integer.valueOf(itemId) != item.getValue()) + FMLLog.fine("Found %s id mismatch %s : %d (was %d)", isBlock ? "block" : "item", itemName, currId, newId); + remaps.put(itemName, new Integer[] { currId, newId }); + } + + // register + FMLControlledNamespacedRegistry srcRegistry = isBlock ? getMain().iBlockRegistry : getMain().iItemRegistry; + currId = newData.register(srcRegistry.getRaw(itemName), itemName, newId); + + if (currId != newId) + { + throw new IllegalStateException(String.format("Can't map %s %s to id %d, already occupied by %s, blocked %b, ItemBlock %b", + isBlock ? "block" : "item", + itemName, + newId, + isBlock ? newData.iBlockRegistry.getRaw(newId) : newData.iItemRegistry.getRaw(newId), + newData.blockedIds.contains(newId), + isBlock ? false : (getMain().iItemRegistry.getRaw(currId) instanceof ItemBlock))); + } + } + } + + List missedMappings = Loader.instance().fireMissingMappingEvent(missingMappings, isLocalWorld, newData, remaps); + if (!missedMappings.isEmpty()) return missedMappings; + + if (injectFrozenData) // add blocks + items missing from the map + { + Map missingBlocks = frozen.iBlockRegistry.getEntriesNotIn(newData.iBlockRegistry); + Map missingItems = frozen.iItemRegistry.getEntriesNotIn(newData.iItemRegistry); + + if (!missingBlocks.isEmpty() || !missingItems.isEmpty()) + { + FMLLog.info("Injecting new block and item data into this server instance"); + + for (int pass = 0; pass < 2; pass++) + { + boolean isBlock = pass == 0; + Map missing = (pass == 0) ? missingBlocks : missingItems; + + for (Entry entry : missing.entrySet()) { - remaps.put(itemName, new Integer[] { item.getValue(), itemId }); + String itemName = entry.getKey(); + int currId = entry.getValue(); + int newId; + + if (isBlock) + { + newId = newData.registerBlock(frozen.iBlockRegistry.getRaw(itemName), itemName, null, currId); + } + else + { + newId = newData.registerItem(frozen.iItemRegistry.getRaw(itemName), itemName, null, currId); + } + + FMLLog.info("Injected new block/item %s : %d (was %d)", itemName, newId, currId); + + if (newId != currId) // a new id was assigned + { + remaps.put(itemName, new Integer[] { entry.getValue(), newId }); + } } } } - for (Entry block : missingBlocks.entrySet()) - { - FMLLog.info("Injecting new block %s", block.getKey()); - int blockId = blockRegistry.swap(block.getValue(), block.getKey(), blockRegistry.get(block.getKey())); - if (Integer.valueOf(blockId) != block.getValue()) - { - remaps.put(block.getKey(), new Integer[] { block.getValue(), blockId }); - } - } } - blockRegistry.completeIdSwap(); - itemRegistry.completeIdSwap(); - blockRegistry.dump(); - itemRegistry.dump(); + + newData.testConsistency(); + getMain().set(newData); + + getMain().iBlockRegistry.dump(); + getMain().iItemRegistry.dump(); Loader.instance().fireRemapEvent(remaps); return ImmutableList.of(); } - public static List processIdRematches(List remaps, boolean isLocalWorld) + + public static List processIdRematches(Iterable missedMappings, boolean isLocalWorld, GameData gameData, Map remaps) { List failed = Lists.newArrayList(); List ignored = Lists.newArrayList(); List warned = Lists.newArrayList(); + List defaulted = Lists.newArrayList(); - for (MissingMapping remap : remaps) + for (MissingMapping remap : missedMappings) { FMLMissingMappingsEvent.Action action = remap.getAction(); - if (action == FMLMissingMappingsEvent.Action.IGNORE) + + if (action == FMLMissingMappingsEvent.Action.REMAP) { - ignored.add(remap.name); - } - else if (action == FMLMissingMappingsEvent.Action.FAIL) - { - failed.add(remap.name); + // block/item re-mapped, finish the registration with the new name/object, but the old id + int currId, newId; + String newName; + + if (remap.type == Type.BLOCK) + { + currId = getMain().iBlockRegistry.getId((Block) remap.getTarget()); + newName = getMain().iBlockRegistry.getNameForObject(remap.getTarget()); + FMLLog.fine("The Block %s is being remapped to %s.", remap.name, newName); + + newId = gameData.registerBlock((Block) remap.getTarget(), newName, null, remap.id); + gameData.iBlockRegistry.addAlias(remap.name, newName); + } + else + { + currId = getMain().iItemRegistry.getId((Item) remap.getTarget()); + newName = getMain().iItemRegistry.getNameForObject(remap.getTarget()); + FMLLog.fine("The Item %s is being remapped to %s.", remap.name, newName); + + newId = gameData.registerItem((Item) remap.getTarget(), newName, null, remap.id); + gameData.iItemRegistry.addAlias(remap.name, newName); + } + + if (newId != remap.id) throw new IllegalStateException(); + + if (currId != newId) + { + FMLLog.info("Found %s id mismatch %s : %d (was %d)", remap.type == Type.BLOCK ? "block" : "item", newName, currId, newId); + remaps.put(newName, new Integer[] { currId, newId }); + } } else { - warned.add(remap.name); + // block item missing, warn as requested and block the id + if (action == FMLMissingMappingsEvent.Action.DEFAULT) + { + defaulted.add(remap.name); + } + else if (action == FMLMissingMappingsEvent.Action.IGNORE) + { + ignored.add(remap.name); + } + else if (action == FMLMissingMappingsEvent.Action.FAIL) + { + failed.add(remap.name); + } + else if (action == FMLMissingMappingsEvent.Action.WARN) + { + warned.add(remap.name); + } + + gameData.block(remap.id); // prevent the id from being reused later } } + + if (!defaulted.isEmpty()) + { + String text = "Forge Mod Loader detected missing blocks/items.\n\n" + + "There are "+defaulted.size()+" missing blocks and items in this save.\n" + + "If you continue the missing blocks/items will get removed.\n" + + "A world backup will be automatically created in your saves directory.\n\n" + + "Missing Blocks/Items:\n"; + + for (String s : defaulted) text += s + "\n"; + + boolean confirmed = StartupQuery.confirm(text); + if (!confirmed) StartupQuery.abort(); + + try + { + ZipperUtil.backupWorld(); + } + catch (IOException e) + { + StartupQuery.notify("The world backup couldn't be created.\n\n"+e); + StartupQuery.abort(); + } + + warned.addAll(defaulted); + } if (!failed.isEmpty()) { FMLLog.severe("This world contains blocks and items that refuse to be remapped. The world will not be loaded"); @@ -367,15 +641,281 @@ public class GameData { public static void freezeData() { FMLLog.fine("Freezing block and item id maps"); - blockRegistry.freezeMap(); - itemRegistry.freezeMap(); + + frozen = new GameData(getMain()); + frozen.testConsistency(); } public static void revertToFrozen() { - FMLLog.fine("Reverting to frozen data state"); - blockRegistry.revertToFrozen(); - itemRegistry.revertToFrozen(); + if (frozen == null) + { + FMLLog.warning("Can't revert to frozen GameData state without freezing first."); + } + else + { + FMLLog.fine("Reverting to frozen data state."); + + getMain().set(frozen); + } } + protected static boolean isFrozen(FMLControlledNamespacedRegistry registry) + { + return frozen != null && (getMain().iBlockRegistry == registry || getMain().iItemRegistry == registry); + } + + protected static GameData getMain() + { + return mainData; + } + + // internal registry objects + private final FMLControlledNamespacedRegistry iBlockRegistry; + private final FMLControlledNamespacedRegistry iItemRegistry; + // bit set marking ids as occupied + private final BitSet availabilityMap; + // IDs previously allocated in a world, but now unmapped/dangling; prevents the IDs from being reused + private final Set blockedIds; + + private GameData() + { + iBlockRegistry = new FMLControlledNamespacedRegistry("air", MAX_BLOCK_ID, MIN_BLOCK_ID, Block.class,'\u0001'); + iItemRegistry = new FMLControlledNamespacedRegistry(null, MAX_ITEM_ID, MIN_ITEM_ID, Item.class,'\u0002'); + availabilityMap = new BitSet(MAX_ITEM_ID + 1); + blockedIds = new HashSet(); + } + + private GameData(GameData data) + { + this(); + set(data); + } + + private void set(GameData data) + { + iBlockRegistry.set(data.iBlockRegistry); + iItemRegistry.set(data.iItemRegistry); + availabilityMap.clear(); + availabilityMap.or(data.availabilityMap); + blockedIds.clear(); + blockedIds.addAll(data.blockedIds); + } + + int register(Object obj, String name, int idHint) + { + if (obj instanceof Block) + { + return registerBlock((Block) obj, name, null, idHint); + } + else if (obj instanceof Item) + { + return registerItem((Item) obj, name, null, idHint); + } + else + { + throw new IllegalArgumentException("An invalid registry object is to be added, only instances of Block or Item are allowed."); + } + } + + int registerItem(Item item, String name, String modId) + { + return registerItem(item, name, modId, 0); + } + + int registerItem(Item item, String name, String modId, int idHint) + { + if (modId != null) + { + ModContainer mc = Loader.instance().activeModContainer(); + customOwners.put(new UniqueIdentifier(modId, name), mc); + } + if (item instanceof ItemBlock) // ItemBlock, adjust id and clear the slot already occupied by the corresponding block + { + Block block = ((ItemBlock) item).field_150939_a; + idHint = iBlockRegistry.getId(block); + + if (idHint == -1) // ItemBlock before its Block + { + idHint = availabilityMap.nextClearBit(MIN_BLOCK_ID); // find suitable id here, iItemRegistry would search from MIN_ITEM_ID + if (idHint > MAX_BLOCK_ID) throw new RuntimeException(String.format("Invalid id %d - maximum id range exceeded.", idHint)); + } + else // ItemBlock after its Block + { + FMLLog.fine("Found matching Block %s for ItemBlock %s at id %d", block, item, idHint); + freeSlot(idHint, item); // temporarily free the slot occupied by the Block for the item registration + } + } + + int itemId = iItemRegistry.add(idHint, name, item, availabilityMap); + + if (item instanceof ItemBlock) // verify + { + if (itemId != idHint) throw new IllegalStateException("Block -> ItemBlock insertion failed."); + verifyItemBlockName((ItemBlock) item); + } + + // block the Block Registry slot with the same id + useSlot(itemId); + + return itemId; + } + + int registerBlock(Block block, String name, String modId) + { + return registerBlock(block, name, modId, 0); + } + + int registerBlock(Block block, String name, String modId, int idHint) + { + if (modId != null) + { + ModContainer mc = Loader.instance().activeModContainer(); + customOwners.put(new UniqueIdentifier(modId, name), mc); + } + + // handle ItemBlock-before-Block registrations + ItemBlock itemBlock = null; + + for (Item item : (Iterable) iItemRegistry) // find matching ItemBlock + { + if (item instanceof ItemBlock && ((ItemBlock) item).field_150939_a == block) + { + itemBlock = (ItemBlock) item; + break; + } + } + + if (itemBlock != null) // has ItemBlock, adjust id and clear the slot already occupied by the corresponding item + { + idHint = iItemRegistry.getId(itemBlock); + FMLLog.fine("Found matching ItemBlock %s for Block %s at id %d", itemBlock, block, idHint); + freeSlot(idHint, block); // temporarily free the slot occupied by the Item for the block registration + } + + // add + int blockId = iBlockRegistry.add(idHint, name, block, availabilityMap); + + if (itemBlock != null) // verify + { + if (blockId != idHint) throw new IllegalStateException("ItemBlock -> Block insertion failed."); + verifyItemBlockName(itemBlock); + } + + useSlot(blockId); + + return blockId; + } + + /** + * Block the specified id from being reused. + */ + private void block(int id) + { + blockedIds.add(id); + useSlot(id); + } + + private void useSlot(int id) + { + availabilityMap.set(id); + } + + /** + * Free the specified slot. + * + * The slot must not be occupied by something else than the specified object within the same type. + * The same object is permitted for handling duplicate registrations. + * + * @param id id to free + * @param obj object allowed besides different types (block vs item) + */ + private void freeSlot(int id, Object obj) + { + FMLControlledNamespacedRegistry registry = (obj instanceof Block) ? iBlockRegistry : iItemRegistry; + Object thing = registry.getRaw(id); + + if (thing != null && thing != obj) + { + throw new IllegalStateException(String.format("Can't free registry slot %d occupied by %s", id, thing)); + } + + availabilityMap.clear(id); + } + + private void verifyItemBlockName(ItemBlock item) + { + String blockName = iBlockRegistry.getNameForObject(item.field_150939_a); + String itemName = iItemRegistry.getNameForObject(item); + + if (blockName != null && !blockName.equals(itemName)) + { + FMLLog.bigWarning("Block <-> ItemBlock name mismatch, block name %s, item name %s", blockName, itemName); + } + } + + @SuppressWarnings("unchecked") + private void testConsistency() { + // test if there's an entry for every set bit in availabilityMap + for (int i = availabilityMap.nextSetBit(0); i >= 0; i = availabilityMap.nextSetBit(i+1)) + { + if (iBlockRegistry.getRaw(i) == null && iItemRegistry.getRaw(i) == null && !blockedIds.contains(i)) + { + throw new IllegalStateException(String.format("availabilityMap references empty entries for id %d.", i)); + } + } + + // test if there's a bit in availabilityMap set for every entry in the block registry, make sure it's not a blocked id + for (Block block : (Iterable) iBlockRegistry) + { + int id = iBlockRegistry.getId(block); + String name = iBlockRegistry.getNameForObject(block); + + if (id < 0) { + throw new IllegalStateException(String.format("Registry entry for block %s, name %s, doesn't yield an id", block, name)); + } + if (name == null) { + throw new IllegalStateException(String.format("Registry entry for block %s, id %d, doesn't yield a name", block, id)); + } + if (!availabilityMap.get(id)) { + throw new IllegalStateException(String.format("Registry entry for block %s, id %d, name %s, marked as empty.", block, id, name)); + } + if (blockedIds.contains(id)) { + throw new IllegalStateException(String.format("Registry entry for block %s, id %d, name %s, marked as dangling.", block, id, name)); + } + } + + // test if there's a bit in availabilityMap set for every entry in the item registry, make sure it's not a blocked id, + // check if ItemBlocks have blocks with matching ids in the block registry + for (Item item : (Iterable) iItemRegistry) + { + int id = iItemRegistry.getId(item); + String name = iItemRegistry.getNameForObject(item); + + if (id < 0) { + throw new IllegalStateException(String.format("Registry entry for item %s, name %s, doesn't yield an id", item, name)); + } + if (name == null) { + throw new IllegalStateException(String.format("Registry entry for item %s, id %d, doesn't yield a name", item, id)); + } + if (!availabilityMap.get(id)) { + throw new IllegalStateException(String.format("Registry entry for item %s, id %d, name %s, marked as empty.", item, id, name)); + } + if (blockedIds.contains(id)) { + throw new IllegalStateException(String.format("Registry entry for item %s, id %d, name %s, marked as dangling.", item, id, name)); + } + + if (item instanceof ItemBlock) + { + Block block = ((ItemBlock) item).field_150939_a; + + if (iBlockRegistry.getId(block) != id) + { + throw new IllegalStateException(String.format("Registry entry for ItemBlock %s, id %d, is missing or uses the non-matching id %d.", item, id, iBlockRegistry.getId(block))); + } + } + } + + FMLLog.fine("Registry consistency check successful"); + } } diff --git a/fml/src/main/java/cpw/mods/fml/common/registry/GameRegistry.java b/fml/src/main/java/cpw/mods/fml/common/registry/GameRegistry.java index b2a752997..909d867a3 100644 --- a/fml/src/main/java/cpw/mods/fml/common/registry/GameRegistry.java +++ b/fml/src/main/java/cpw/mods/fml/common/registry/GameRegistry.java @@ -136,7 +136,7 @@ public class GameRegistry */ public static Item registerItem(Item item, String name, String modId) { - GameData.registerItem(item, name, modId); + GameData.getMain().registerItem(item, name, modId); return item; } @@ -192,13 +192,11 @@ public class GameRegistry Constructor itemCtor = itemclass.getConstructor(ctorArgClasses); i = itemCtor.newInstance(ObjectArrays.concat(block, itemCtorArgs)); } + // block registration has to happen first + GameData.getMain().registerBlock(block, name, modId); if (i != null) { - GameData.registerBlockAndItem(i, block, name, modId); - } - else - { - GameData.registerBlock(block, name, modId); + GameData.getMain().registerItem(i, name, modId); } return block; } diff --git a/fml/src/main/java/cpw/mods/fml/common/registry/GameRegistryException.java b/fml/src/main/java/cpw/mods/fml/common/registry/GameRegistryException.java deleted file mode 100644 index 9b02969d9..000000000 --- a/fml/src/main/java/cpw/mods/fml/common/registry/GameRegistryException.java +++ /dev/null @@ -1,19 +0,0 @@ -package cpw.mods.fml.common.registry; - -import java.util.List; - -public class GameRegistryException extends RuntimeException { - private static final long serialVersionUID = 1L; - private List items; - - public GameRegistryException(String message, List items) - { - super(message); - this.items = items; - } - - public List getItems() - { - return this.items; - } -} diff --git a/fml/src/main/java/cpw/mods/fml/server/FMLServerHandler.java b/fml/src/main/java/cpw/mods/fml/server/FMLServerHandler.java index 92712eb23..726d7b379 100644 --- a/fml/src/main/java/cpw/mods/fml/server/FMLServerHandler.java +++ b/fml/src/main/java/cpw/mods/fml/server/FMLServerHandler.java @@ -12,20 +12,27 @@ */ package cpw.mods.fml.server; +import java.io.File; +import java.util.Iterator; import java.util.List; import java.util.Set; + +import net.minecraft.command.ServerCommand; import net.minecraft.network.INetHandler; import net.minecraft.network.NetHandlerPlayServer; import net.minecraft.network.NetworkManager; import net.minecraft.server.MinecraftServer; import net.minecraft.server.dedicated.DedicatedServer; +import net.minecraft.world.storage.SaveFormatOld; + import com.google.common.collect.ImmutableList; + import cpw.mods.fml.common.FMLCommonHandler; +import cpw.mods.fml.common.FMLLog; import cpw.mods.fml.common.IFMLSidedHandler; import cpw.mods.fml.common.Loader; import cpw.mods.fml.common.ModContainer; -import cpw.mods.fml.common.WorldAccessContainer; -import cpw.mods.fml.common.event.FMLMissingMappingsEvent; +import cpw.mods.fml.common.StartupQuery; import cpw.mods.fml.common.eventhandler.EventBus; import cpw.mods.fml.common.network.FMLNetworkEvent; import cpw.mods.fml.common.registry.LanguageRegistry; @@ -94,6 +101,12 @@ public class FMLServerHandler implements IFMLSidedHandler throw new RuntimeException(message, exception); } + @Override + public File getSavesDirectory() + { + return ((SaveFormatOld) server.getActiveAnvilConverter()).savesDirectory; + } + /** * Get the server instance */ @@ -134,6 +147,67 @@ public class FMLServerHandler implements IFMLSidedHandler { } + + @Override + public void queryUser(StartupQuery query) throws InterruptedException + { + if (query.getResult() == null) + { + FMLLog.warning("%s", query.getText()); + query.finish(); + } + else + { + String text = query.getText() + + "\n\nRun the command /fml confirm or or /fml cancel to proceed." + + "\nAlternatively start the server with -Dfml.queryResult=confirm or -Dfml.queryResult=cancel to preselect the answer."; + FMLLog.warning("%s", text); + + if (!query.isSynchronous()) return; // no-op until mc does commands in another thread (if ever) + + boolean done = false; + + while (!done && server.isServerRunning()) + { + if (Thread.interrupted()) throw new InterruptedException(); + + DedicatedServer dedServer = (DedicatedServer) server; + + // rudimentary command processing, check for fml confirm/cancel and stop commands + synchronized (dedServer.pendingCommandList) + { + for (Iterator it = dedServer.pendingCommandList.iterator(); it.hasNext(); ) + { + String cmd = it.next().command.trim().toLowerCase(); + + if (cmd.equals("/fml confirm")) + { + FMLLog.info("confirmed"); + query.setResult(true); + done = true; + it.remove(); + } + else if (cmd.equals("/fml cancel")) + { + FMLLog.info("cancelled"); + query.setResult(false); + done = true; + it.remove(); + } + else if (cmd.equals("/stop")) + { + StartupQuery.abort(); + } + } + } + + Thread.sleep(10L); + } + + query.finish(); + } + } + @Override public boolean shouldServerShouldBeKilledQuietly() { @@ -182,21 +256,7 @@ public class FMLServerHandler implements IFMLSidedHandler { bus.post(new FMLNetworkEvent.CustomPacketRegistrationEvent(manager, channelSet, channel, side, NetHandlerPlayServer.class)); } - @Override - public FMLMissingMappingsEvent.Action getDefaultMissingAction() - { - return FMLMissingMappingsEvent.Action.valueOf(System.getProperty("fml.missingBlockAction", "FAIL")); - } - @Override - public void serverLoadedSuccessfully() - { - } - @Override - public void failedServerLoading(RuntimeException ex, WorldAccessContainer wac) - { - - } @Override public boolean shouldAllowPlayerLogins() { diff --git a/fml/src/main/resources/fml_at.cfg b/fml/src/main/resources/fml_at.cfg index 3e86a81a8..8b54a7fdc 100644 --- a/fml/src/main/resources/fml_at.cfg +++ b/fml/src/main/resources/fml_at.cfg @@ -74,6 +74,12 @@ public net.minecraft.client.gui.GuiScreen field_146297_k # minecraft instance - # Minecraft #public atv.D #FD:Minecraft/field_71425_J #running public net.minecraft.client.Minecraft field_71446_o # textureManager +## ItemBlock +public net.minecraft.item.ItemBlock field_150939_a +## DedicatedServer +public net.minecraft.server.dedicated.DedicatedServer field_71341_l # pendingCommandList +## SaveFormatOld +public net.minecraft.world.storage.SaveFormatOld field_75808_a # savesDirectory protected net.minecraft.util.ObjectIntIdentityMap field_148749_a # internal map protected net.minecraft.util.ObjectIntIdentityMap field_148748_b # internal index list