diff --git a/src/main/java/codechicken/nei/IMCForNEI.java b/src/main/java/codechicken/nei/IMCForNEI.java new file mode 100644 index 00000000..7ad96a31 --- /dev/null +++ b/src/main/java/codechicken/nei/IMCForNEI.java @@ -0,0 +1,147 @@ +package codechicken.nei; + +import net.minecraft.client.gui.GuiRepair; +import net.minecraft.client.gui.inventory.GuiBrewingStand; +import net.minecraft.client.gui.inventory.GuiCrafting; +import net.minecraft.client.gui.inventory.GuiFurnace; +import net.minecraft.client.gui.inventory.GuiInventory; +import net.minecraft.nbt.NBTTagCompound; + +import codechicken.nei.api.API; +import codechicken.nei.recipe.BrewingOverlayHandler; +import codechicken.nei.recipe.BrewingRecipeHandler; +import codechicken.nei.recipe.DefaultOverlayHandler; +import codechicken.nei.recipe.FireworkRecipeHandler; +import codechicken.nei.recipe.FuelRecipeHandler; +import codechicken.nei.recipe.FurnaceRecipeHandler; +import codechicken.nei.recipe.InformationHandler; +import codechicken.nei.recipe.ProfilerRecipeHandler; +import codechicken.nei.recipe.RepairOverlayHandler; +import codechicken.nei.recipe.RepairRecipeHandler; +import codechicken.nei.recipe.ShapedRecipeHandler; +import codechicken.nei.recipe.ShapelessRecipeHandler; +import cpw.mods.fml.common.event.FMLInterModComms; + +public class IMCForNEI { + + public static void IMCSender() { + + API.registerRecipeHandler(new ShapedRecipeHandler()); + API.registerUsageHandler(new ShapedRecipeHandler()); + sendHandler("codechicken.nei.recipe.ShapedRecipeHandler", "minecraft:crafting_table"); + + API.registerRecipeHandler(new ShapelessRecipeHandler()); + API.registerUsageHandler(new ShapelessRecipeHandler()); + sendHandler("codechicken.nei.recipe.ShapelessRecipeHandler", "minecraft:crafting_table"); + + API.registerRecipeHandler(new FireworkRecipeHandler()); + API.registerUsageHandler(new FireworkRecipeHandler()); + sendHandler("codechicken.nei.recipe.FireworkRecipeHandler", "minecraft:fireworks"); + + API.registerRecipeHandler(new FurnaceRecipeHandler()); + API.registerUsageHandler(new FurnaceRecipeHandler()); + sendHandler("codechicken.nei.recipe.FurnaceRecipeHandler", "minecraft:furnace"); + + API.registerRecipeHandler(new BrewingRecipeHandler()); + API.registerUsageHandler(new BrewingRecipeHandler()); + sendHandler("codechicken.nei.recipe.BrewingRecipeHandler", "minecraft:brewing_stand"); + + API.registerRecipeHandler(new FuelRecipeHandler()); + API.registerUsageHandler(new FuelRecipeHandler()); + sendFuelRecipeHandler(); + + API.registerRecipeHandler(new InformationHandler()); + API.registerUsageHandler(new InformationHandler()); + sendInformationHandler(); + + API.registerRecipeHandler(new RepairRecipeHandler()); + API.registerUsageHandler(new RepairRecipeHandler()); + sendRepairHandler(); + sendCatalyst("repair", "minecraft:anvil"); + + API.registerGuiOverlay(GuiCrafting.class, "crafting"); + API.registerGuiOverlay(GuiInventory.class, "crafting2x2", 63, 20); + API.registerGuiOverlay(GuiFurnace.class, "smelting"); + API.registerGuiOverlay(GuiFurnace.class, "fuel"); + API.registerGuiOverlay(GuiBrewingStand.class, "brewing"); + API.registerGuiOverlay(GuiRepair.class, "repair", 2, 37); + + API.registerGuiOverlayHandler(GuiCrafting.class, new DefaultOverlayHandler(), "crafting"); + API.registerGuiOverlayHandler(GuiInventory.class, new DefaultOverlayHandler(63, 20), "crafting2x2"); + API.registerGuiOverlayHandler(GuiBrewingStand.class, new BrewingOverlayHandler(), "brewing"); + API.registerGuiOverlayHandler(GuiRepair.class, new RepairOverlayHandler(), "repair"); + + API.registerRecipeHandler(new ProfilerRecipeHandler(true)); + API.registerUsageHandler(new ProfilerRecipeHandler(false)); + } + + private static void sendHandler(String aName, String aBlock) { + NBTTagCompound aNBT = new NBTTagCompound(); + aNBT.setString("handler", aName); + aNBT.setString("modName", "Minecraft"); + aNBT.setString("modId", "minecraft"); + aNBT.setString("itemName", aBlock); + aNBT.setInteger("handlerWidth", 166); + aNBT.setInteger("handlerHeight", 65); + aNBT.setBoolean("multipleWidgetsAllowed", true); + aNBT.setInteger("yShift", 0); + FMLInterModComms.sendMessage("NotEnoughItems", "registerHandlerInfo", aNBT); + } + + private static void sendRepairHandler() { + NBTTagCompound aNBT = new NBTTagCompound(); + aNBT.setString("handler", "codechicken.nei.recipe.RepairRecipeHandler"); + aNBT.setString("modName", "Minecraft"); + aNBT.setString("modId", "minecraft"); + aNBT.setString("itemName", "minecraft:anvil"); + aNBT.setInteger("handlerHeight", 39); + aNBT.setInteger("handlerWidth", 166); + aNBT.setBoolean("multipleWidgetsAllowed", true); + aNBT.setBoolean("showFavoritesButton", false); + aNBT.setInteger("yShift", 0); + FMLInterModComms.sendMessage("NotEnoughItems", "registerHandlerInfo", aNBT); + } + + private static void sendFuelRecipeHandler() { + NBTTagCompound aNBT = new NBTTagCompound(); + aNBT.setString("handler", "codechicken.nei.recipe.FuelRecipeHandler"); + aNBT.setString("modName", "Minecraft"); + aNBT.setString("modId", "minecraft"); + aNBT.setString("imageResource", "nei:textures/nei_tabbed_sprites.png"); + aNBT.setInteger("imageX", 80); + aNBT.setInteger("imageY", 0); + aNBT.setInteger("imageWidth", 14); + aNBT.setInteger("imageHeight", 14); + aNBT.setInteger("handlerHeight", 65); + aNBT.setInteger("handlerWidth", 166); + aNBT.setBoolean("multipleWidgetsAllowed", true); + aNBT.setInteger("yShift", 0); + FMLInterModComms.sendMessage("NotEnoughItems", "registerHandlerInfo", aNBT); + } + + private static void sendInformationHandler() { + NBTTagCompound aNBT = new NBTTagCompound(); + aNBT.setString("handler", "codechicken.nei.recipe.InformationHandler"); + aNBT.setString("modName", "Not Enough Items"); + aNBT.setString("modId", "nei"); + aNBT.setString("imageResource", "nei:textures/nei_tabbed_sprites.png"); + aNBT.setInteger("imageX", 96); + aNBT.setInteger("imageY", 0); + aNBT.setInteger("imageWidth", 16); + aNBT.setInteger("imageHeight", 16); + aNBT.setInteger("handlerHeight", 65); + aNBT.setInteger("handlerWidth", 166); + aNBT.setBoolean("multipleWidgetsAllowed", true); + aNBT.setInteger("yShift", 0); + FMLInterModComms.sendMessage("NotEnoughItems", "registerHandlerInfo", aNBT); + } + + private static void sendCatalyst(String aName, String aStack) { + NBTTagCompound aNBT = new NBTTagCompound(); + aNBT.setString("handlerID", aName); + aNBT.setString("itemName", aStack); + aNBT.setInteger("priority", 0); + FMLInterModComms.sendMessage("NotEnoughItems", "registerCatalystInfo", aNBT); + } + +} diff --git a/src/main/java/codechicken/nei/ItemList.java b/src/main/java/codechicken/nei/ItemList.java index dea01fe4..4d9a0170 100644 --- a/src/main/java/codechicken/nei/ItemList.java +++ b/src/main/java/codechicken/nei/ItemList.java @@ -32,6 +32,7 @@ import codechicken.nei.api.ItemFilter.ItemFilterProvider; import codechicken.nei.api.ItemInfo; import codechicken.nei.recipe.InformationHandler; import codechicken.nei.recipe.RecipeHandlerRef; +import codechicken.nei.recipe.RepairRecipeHandler; import codechicken.nei.search.TooltipFilter; import codechicken.nei.util.ItemUntranslator; import cpw.mods.fml.common.FMLLog; @@ -433,6 +434,7 @@ public class ItemList { loadFinished = true; + RepairRecipeHandler.findRepairRecipesOnceParallel(); RecipeHandlerRef.clearCache(); FavoriteRecipes.reload(); SubsetWidget.updateHiddenItems(); diff --git a/src/main/java/codechicken/nei/NEIClientConfig.java b/src/main/java/codechicken/nei/NEIClientConfig.java index 1213089b..0f3e26c3 100644 --- a/src/main/java/codechicken/nei/NEIClientConfig.java +++ b/src/main/java/codechicken/nei/NEIClientConfig.java @@ -61,7 +61,6 @@ import codechicken.nei.recipe.GuiRecipeTab; import codechicken.nei.recipe.IRecipeHandler; import codechicken.nei.recipe.InformationHandler; import codechicken.nei.recipe.RecipeCatalysts; -import codechicken.nei.recipe.RecipeInfo; import codechicken.nei.util.ItemUntranslator; import codechicken.nei.util.NEIKeyboardUtils; import codechicken.obfuscator.ObfuscationRun; @@ -149,6 +148,7 @@ public class NEIClientConfig { API.addOption(new OptionToggleButton("inventory.widgetsenabled")); tag.getTag("findFuelsParallel").getBooleanValue(true); + tag.getTag("showRepairOnlyForDamageableItems").getBooleanValue(true); tag.getTag("inventory.autocrafting").getBooleanValue(true); tag.getTag("inventory.dynamicFontSize").getBooleanValue(true); @@ -978,7 +978,6 @@ public class NEIClientConfig { // main NEI config loading ItemInfo.load(); GuiInfo.load(); - RecipeInfo.load(); HeldItemHandler.load(); LayoutManager.load(); NEIController.load(); diff --git a/src/main/java/codechicken/nei/NEIModContainer.java b/src/main/java/codechicken/nei/NEIModContainer.java index c509b406..e1ee7615 100644 --- a/src/main/java/codechicken/nei/NEIModContainer.java +++ b/src/main/java/codechicken/nei/NEIModContainer.java @@ -104,6 +104,7 @@ public class NEIModContainer extends DummyModContainer { public void init(FMLInitializationEvent event) { if (CommonUtils.isClient()) { ClientHandler.load(); + IMCForNEI.IMCSender(); } ServerHandler.load(); } diff --git a/src/main/java/codechicken/nei/recipe/RecipeInfo.java b/src/main/java/codechicken/nei/recipe/RecipeInfo.java index 1515ae17..6c74edb9 100644 --- a/src/main/java/codechicken/nei/recipe/RecipeInfo.java +++ b/src/main/java/codechicken/nei/recipe/RecipeInfo.java @@ -4,16 +4,11 @@ import java.util.HashMap; import java.util.List; import java.util.stream.Collectors; -import net.minecraft.client.gui.inventory.GuiBrewingStand; import net.minecraft.client.gui.inventory.GuiContainer; -import net.minecraft.client.gui.inventory.GuiCrafting; -import net.minecraft.client.gui.inventory.GuiFurnace; -import net.minecraft.client.gui.inventory.GuiInventory; import com.google.common.base.Objects; import codechicken.nei.OffsetPositioner; -import codechicken.nei.api.API; import codechicken.nei.api.IOverlayHandler; import codechicken.nei.api.IStackPositioner; @@ -96,33 +91,4 @@ public class RecipeInfo { return offset == null ? new int[] { 5, 11 } : offset; } - public static void load() { - API.registerRecipeHandler(new ShapedRecipeHandler()); - API.registerUsageHandler(new ShapedRecipeHandler()); - API.registerRecipeHandler(new ShapelessRecipeHandler()); - API.registerUsageHandler(new ShapelessRecipeHandler()); - API.registerRecipeHandler(new FireworkRecipeHandler()); - API.registerUsageHandler(new FireworkRecipeHandler()); - API.registerRecipeHandler(new FurnaceRecipeHandler()); - API.registerUsageHandler(new FurnaceRecipeHandler()); - API.registerRecipeHandler(new BrewingRecipeHandler()); - API.registerUsageHandler(new BrewingRecipeHandler()); - API.registerRecipeHandler(new FuelRecipeHandler()); - API.registerUsageHandler(new FuelRecipeHandler()); - API.registerRecipeHandler(new InformationHandler()); - API.registerUsageHandler(new InformationHandler()); - - API.registerGuiOverlay(GuiCrafting.class, "crafting"); - API.registerGuiOverlay(GuiInventory.class, "crafting2x2", 63, 20); - API.registerGuiOverlay(GuiFurnace.class, "smelting"); - API.registerGuiOverlay(GuiFurnace.class, "fuel"); - API.registerGuiOverlay(GuiBrewingStand.class, "brewing"); - - API.registerGuiOverlayHandler(GuiCrafting.class, new DefaultOverlayHandler(), "crafting"); - API.registerGuiOverlayHandler(GuiInventory.class, new DefaultOverlayHandler(63, 20), "crafting2x2"); - API.registerGuiOverlayHandler(GuiBrewingStand.class, new BrewingOverlayHandler(), "brewing"); - - API.registerRecipeHandler(new ProfilerRecipeHandler(true)); - API.registerUsageHandler(new ProfilerRecipeHandler(false)); - } } diff --git a/src/main/java/codechicken/nei/recipe/RepairOverlayHandler.java b/src/main/java/codechicken/nei/recipe/RepairOverlayHandler.java new file mode 100644 index 00000000..af2477e1 --- /dev/null +++ b/src/main/java/codechicken/nei/recipe/RepairOverlayHandler.java @@ -0,0 +1,24 @@ +package codechicken.nei.recipe; + +import java.util.List; + +import net.minecraft.client.gui.inventory.GuiContainer; +import net.minecraft.inventory.Slot; + +import codechicken.nei.PositionedStack; + +public class RepairOverlayHandler extends DefaultOverlayHandler { + + @Override + public boolean canCraft(GuiContainer firstGui, IRecipeHandler handler, int recipeIndex) { + return false; + } + + @Override + public Slot[][] mapIngredSlots(GuiContainer gui, List ingredients) { + final List slots = gui.inventorySlots.inventorySlots; + + return new Slot[][] { { slots.get(0) }, { slots.get(1) } }; + } + +} diff --git a/src/main/java/codechicken/nei/recipe/RepairRecipeHandler.java b/src/main/java/codechicken/nei/recipe/RepairRecipeHandler.java new file mode 100644 index 00000000..de84c26a --- /dev/null +++ b/src/main/java/codechicken/nei/recipe/RepairRecipeHandler.java @@ -0,0 +1,233 @@ +package codechicken.nei.recipe; + +import java.awt.Rectangle; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import net.minecraft.client.gui.GuiRepair; +import net.minecraft.client.gui.inventory.GuiContainer; +import net.minecraft.item.ItemStack; + +import codechicken.nei.ItemList; +import codechicken.nei.NEIClientConfig; +import codechicken.nei.NEIClientUtils; +import codechicken.nei.PositionedStack; +import codechicken.nei.RestartableTask; +import codechicken.nei.drawable.DrawableBuilder; +import codechicken.nei.drawable.DrawableResource; + +public class RepairRecipeHandler extends TemplateRecipeHandler { + + private final DrawableResource BG_TEXTURE = new DrawableBuilder("nei:textures/gui/repair.png", 0, 0, 168, 36) + .setTextureSize(168, 36).build(); + + private static final RestartableTask reloadRepairRecipesTask = new RestartableTask("NEI Repair Recipe Reloading") { + + @Override + public void execute() { + new RepairRecipeHandler().buildCache(); + } + }; + + private static class ItemStackKey { + + private final ItemStack stack; + + public ItemStackKey(ItemStack stack) { + this.stack = stack; + } + + @Override + public int hashCode() { + return stack.getItem().hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + + if (obj instanceof ItemStackKey objeKey) { + return NEIClientUtils.areStacksSameTypeWithNBT(stack, objeKey.stack); + } + + return false; + } + + } + + private static class DamagablePositionedStack extends PositionedStack { + + public DamagablePositionedStack(Object item, int x, int y) { + super(item, x, y); + } + + @Override + public boolean contains(ItemStack ingredient) { + return ingredient.getItemDamage() > 0 && NEIClientUtils.areStacksSameTypeWithNBT(this.item, ingredient); + } + + } + + private static List cachedRecipes; + + private static final int LEFT_X = 25; + private static final int RIGHT_X = 74; + private static final int OUTPUT_X = 132; + private static final int SLOT_Y = 10; + + public class RepairPair extends CachedRecipe { + + final List ingredients = new ArrayList<>(2); + final PositionedStack result; + + RepairPair(ItemStack left, Object right, ItemStack result) { + ingredients.add(new DamagablePositionedStack(left, LEFT_X, SLOT_Y)); + ingredients.add(new PositionedStack(right, RIGHT_X, SLOT_Y)); + this.result = new PositionedStack(result, OUTPUT_X, SLOT_Y); + } + + @Override + public List getIngredients() { + return this.ingredients; + } + + @Override + public PositionedStack getResult() { + return this.result; + } + } + + public static void findRepairRecipesOnceParallel() { + reloadRepairRecipesTask.restart(); + } + + private void buildCache() { + final Map recipes = new HashMap<>(); + + for (ItemStack itemstack1 : ItemList.items) { + recipes.computeIfAbsent(new ItemStackKey(itemstack1), key -> this.getRepairRecipe(key.stack)); + } + + cachedRecipes = recipes.values().stream().filter(recipe -> recipe != null).collect(Collectors.toList()); + } + + private RepairPair getRepairRecipe(ItemStack itemstack1) { + if (!itemstack1.isItemStackDamageable()) return null; + + final List permutations = new ArrayList<>(); + + for (ItemStack itemstack2 : ItemList.items) { + if (itemstack1.getItem().getIsRepairable(itemstack1, itemstack2)) { + final ItemStack right = itemstack2.copy(); + right.stackSize = 4; + permutations.add(right); + } + } + + if (permutations.isEmpty()) return null; + + final int maxDamage = itemstack1.getMaxDamage(); + final ItemStack result = itemstack1.copy(); + final ItemStack left = itemstack1.copy(); + + left.setItemDamage(maxDamage - 1); + result.setItemDamage(left.getItemDamageForDisplay() - (maxDamage / 4) * 4); + + return new RepairPair(left, permutations, result); + } + + @Override + public TemplateRecipeHandler newInstance() { + + if (cachedRecipes == null) { + buildCache(); + } + + return super.newInstance(); + } + + @Override + public void loadCraftingRecipes(String outputId, Object... results) { + if (outputId.equals(getOverlayIdentifier()) && getClass() == RepairRecipeHandler.class) { + arecipes.addAll(cachedRecipes); + } else if (!NEIClientConfig.getBooleanSetting("showRepairOnlyForDamageableItems")) { + super.loadCraftingRecipes(outputId, results); + } + } + + @Override + public void loadCraftingRecipes(ItemStack result) { + for (RepairPair recipe : cachedRecipes) { + if (recipe.getResult().contains(result)) arecipes.add(recipe); + } + } + + @Override + public void loadUsageRecipes(String inputId, Object... ingredients) { + if (inputId.equals(getOverlayIdentifier()) && getClass() == RepairRecipeHandler.class) { + loadCraftingRecipes(getOverlayIdentifier()); + } else { + super.loadUsageRecipes(inputId, ingredients); + } + } + + @Override + public void loadUsageRecipes(ItemStack ingredient) { + final boolean showRepairOnlyForDamageableItems = NEIClientConfig + .getBooleanSetting("showRepairOnlyForDamageableItems"); + + for (RepairPair recipe : cachedRecipes) { + final List ingredients = recipe.getIngredients(); + + if (ingredients.get(0).contains(ingredient)) { + arecipes.add(recipe); + } else if (!showRepairOnlyForDamageableItems && ingredients.get(1).contains(ingredient)) { + arecipes.add(new RepairPair(ingredients.get(0).item, ingredient, recipe.getResult().item)); + } + } + + if (arecipes.isEmpty()) { + final RepairPair recipe = getRepairRecipe(ingredient); + if (recipe != null) { + arecipes.add(recipe); + } + } + } + + @Override + public void loadTransferRects() { + transferRects.add(new RecipeTransferRect(new Rectangle(100, 11, 22, 15), getOverlayIdentifier())); + } + + @Override + public String getRecipeName() { + return NEIClientUtils.translate("recipe.repair"); + } + + @Override + public String getGuiTexture() { + return "nei:textures/gui/repair.png"; + } + + @Override + public String getOverlayIdentifier() { + return "repair"; + } + + @Override + public Class getGuiClass() { + return GuiRepair.class; + } + + @Override + public void drawBackground(int recipe) { + BG_TEXTURE.draw(0, 0); + } + + @Override + public void drawForeground(int recipe) {} + +} diff --git a/src/main/resources/assets/nei/csv/handlers.csv b/src/main/resources/assets/nei/csv/handlers.csv index 9ce4537f..4a17d169 100644 --- a/src/main/resources/assets/nei/csv/handlers.csv +++ b/src/main/resources/assets/nei/csv/handlers.csv @@ -1,11 +1,4 @@ handler,modName,itemName,nbtInfo,modId,modRequired,excludedModId,yShift,handlerHeight,handlerWidth,maxRecipesPerPage,imageResource,imageX,imageY,imageWidth,imageHeight,itemNotes,useCustomScroll -codechicken.nei.recipe.BrewingRecipeHandler,Minecraft,minecraft:brewing_stand,,minecraft,FALSE,,,65,166,5,,,,,,, -codechicken.nei.recipe.FireworkRecipeHandler,Minecraft,minecraft:fireworks,,minecraft,FALSE,,,65,166,5,,,,,,, -codechicken.nei.recipe.FurnaceRecipeHandler,Minecraft,minecraft:furnace,,minecraft,FALSE,,,65,166,5,,,,,,, -codechicken.nei.recipe.ShapedRecipeHandler,Minecraft,minecraft:crafting_table,,minecraft,FALSE,,,65,166,5,,,,,,, -codechicken.nei.recipe.ShapelessRecipeHandler,Minecraft,minecraft:crafting_table,,minecraft,FALSE,,,65,166,5,,,,,,, -codechicken.nei.recipe.FuelRecipeHandler,Minecraft,,,minecraft,FALSE,,,65,166,5,nei:textures/nei_tabbed_sprites.png,80,0,14,14,, -codechicken.nei.recipe.InformationHandler,Not Enough Items,,,nei,FALSE,,,65,166,1,nei:textures/nei_tabbed_sprites.png,96,0,16,16,, advsolar.client.nei.MTRecipeHandler,Advanced Solar Panels,AdvancedSolarPanel:BlockMolecularTransformer,,AdvancedSolarPanel,TRUE,,,68,,5,,,,,,, appeng.integration.modules.NEIHelpers.NEIAEShapedRecipeHandler,Applied Energistics 2,appliedenergistics2:item.ItemMultiPart:380,,appliedenergistics2,TRUE,,,65,,5,,,,,,, appeng.integration.modules.NEIHelpers.NEIAEShapelessRecipeHandler,Applied Energistics 2,appliedenergistics2:item.ItemMultiPart:380,,appliedenergistics2,TRUE,,,65,,5,,,,,,, diff --git a/src/main/resources/assets/nei/lang/en_US.lang b/src/main/resources/assets/nei/lang/en_US.lang index 286c784e..e991c6c3 100644 --- a/src/main/resources/assets/nei/lang/en_US.lang +++ b/src/main/resources/assets/nei/lang/en_US.lang @@ -556,6 +556,7 @@ nei.recipe.firework=Fireworks nei.recipe.firework.tooltip0=Dyes add colours to the charges nei.recipe.firework.tooltip1=Recolouring adds fade colours to the charge nei.recipe.firework.tooltip2=Multiple charges can be spawned from one rocket +nei.recipe.repair=Repair nei.recipe.brewing=Brewing nei.recipe.profiler.crafting=Crafting Profiling nei.recipe.profiler.usage=Usage Profiling diff --git a/src/main/resources/assets/nei/textures/gui/repair.png b/src/main/resources/assets/nei/textures/gui/repair.png new file mode 100644 index 00000000..342a1e3e Binary files /dev/null and b/src/main/resources/assets/nei/textures/gui/repair.png differ