/* * Copyright (c) 2015 NOVA, All rights reserved. * This library is free software, licensed under GNU Lesser General Public License version 3 * * This file is part of NOVA. * * NOVA is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * NOVA is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with NOVA. If not, see <http://www.gnu.org/licenses/>. */ package nova.core.wrapper.mc.forge.v18.wrapper.item; import com.google.common.collect.HashBiMap; import net.minecraft.item.ItemStack; import net.minecraft.util.ResourceLocation; import net.minecraftforge.fml.common.FMLCommonHandler; import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; import net.minecraftforge.fml.common.registry.GameRegistry; import nova.core.block.BlockFactory; import nova.core.component.Category; import nova.core.event.ItemEvent; import nova.core.item.Item; import nova.core.item.ItemBlock; import nova.core.item.ItemFactory; import nova.core.loader.Mod; import nova.core.nativewrapper.NativeConverter; import nova.core.retention.Data; import nova.core.wrapper.mc.forge.v18.launcher.ForgeLoadable; import nova.core.wrapper.mc.forge.v18.launcher.NovaMinecraft; import nova.core.wrapper.mc.forge.v18.util.WrapUtility; import nova.core.wrapper.mc.forge.v18.util.WrapperEvent; import nova.core.wrapper.mc.forge.v18.wrapper.CategoryConverter; import nova.core.wrapper.mc.forge.v18.wrapper.block.BlockConverter; import nova.core.wrapper.mc.forge.v18.wrapper.data.DataConverter; import nova.internal.core.Game; import nova.internal.core.launch.InitializationException; import nova.internal.core.launch.NovaLauncher; import java.util.Set; /** * The main class responsible for wrapping items. * @author Calclavia, Stan Hebben */ public class ItemConverter implements NativeConverter<Item, ItemStack>, ForgeLoadable { /** * A map of all items registered */ private final HashBiMap<ItemFactory, MinecraftItemMapping> map = HashBiMap.create(); public static ItemConverter instance() { return Game.natives().getNative(Item.class, ItemStack.class); } @Override public Class<Item> getNovaSide() { return Item.class; } @Override public Class<ItemStack> getNativeSide() { return ItemStack.class; } @Override public Item toNova(ItemStack itemStack) { return getNovaItem(itemStack).setCount(itemStack.stackSize); } //TODO: Why is this method separate? public Item getNovaItem(ItemStack itemStack) { if (itemStack.getItemDamage() == net.minecraftforge.oredict.OreDictionary.WILDCARD_VALUE) { // TODO: Deal withPriority wildcard meta values - important for the ore dictionary return getNovaItem(new ItemStack(itemStack.getItem(), 1, 0)); } if (itemStack.getTagCompound() != null && itemStack.getTagCompound() instanceof FWNBTTagCompound) { return ((FWNBTTagCompound) itemStack.getTagCompound()).getItem(); } else { ItemFactory itemFactory = registerMinecraftMapping(itemStack.getItem(), itemStack.getItemDamage()); Data data = itemStack.getTagCompound() != null ? DataConverter.instance().toNova(itemStack.getTagCompound()) : new Data(); if (!itemStack.getHasSubtypes() && itemStack.getItemDamage() > 0) { data.put("damage", itemStack.getItemDamage()); } return itemFactory.build(data); } } @Override public ItemStack toNative(Item item) { if (item == null) { return null; } //Prevent recusive wrapping if (item instanceof BWItem) { return ((BWItem) item).makeItemStack(item.count()); } else { ItemFactory itemFactory = Game.items().get(item.getID()).get(); FWNBTTagCompound tag = new FWNBTTagCompound(item); MinecraftItemMapping mapping = get(itemFactory); if (mapping == null) { throw new InitializationException("Missing mapping for " + itemFactory.getID()); } ItemStack result = new ItemStack(mapping.item, item.count(), mapping.meta); result.setTagCompound(tag); return result; } } public ItemStack toNative(ItemFactory itemFactory) { FWNBTTagCompound tag = new FWNBTTagCompound(itemFactory.build()); MinecraftItemMapping mapping = get(itemFactory); if (mapping == null) { throw new InitializationException("Missing mapping for " + itemFactory.getID()); } ItemStack result = new ItemStack(mapping.item, 1, mapping.meta); result.setTagCompound(tag); return result; } public ItemStack toNative(String id) { return toNative(Game.items().get(id).get().build().setCount(1)); } public MinecraftItemMapping get(ItemFactory item) { return map.get(item); } public ItemFactory get(MinecraftItemMapping minecraftItem) { return map.inverse().get(minecraftItem); } /** * Saves NOVA item into a Minecraft ItemStack. */ public ItemStack updateMCItemStack(ItemStack itemStack, Item item) { itemStack.stackSize = item.count(); if (itemStack.stackSize <= 0) { return null; } itemStack.setTagCompound(DataConverter.instance().toNative(new FWNBTTagCompound(item), item.getFactory().save(item))); WrapperEvent.UpdateItemEvent event = new WrapperEvent.UpdateItemEvent(item, itemStack); Game.events().publish(event); return itemStack; } /** * Register all Nova blocks */ @Override public void preInit(FMLPreInitializationEvent evt) { registerNOVAItemsToMinecraft(); registerMinecraftItemsToNOVA(); registerSubtypeResolution(); } private void registerNOVAItemsToMinecraft() { //There should be no items registered during Native Converter preInit() // item.registry.forEach(this::registerNOVAItem); Game.events().on(ItemEvent.Register.class).bind(this::onItemRegistered); } private void onItemRegistered(ItemEvent.Register event) { registerNOVAItem(event.itemFactory); } private void registerNOVAItem(ItemFactory itemFactory) { if (map.containsKey(itemFactory)) { // just a safeguard - don't map stuff twice return; } net.minecraft.item.Item itemWrapper; Item dummy = itemFactory.build(); if (dummy instanceof ItemBlock) { BlockFactory blockFactory = ((ItemBlock) dummy).blockFactory; net.minecraft.block.Block mcBlock = BlockConverter.instance().toNative(blockFactory); itemWrapper = net.minecraft.item.Item.getItemFromBlock(mcBlock); if (itemWrapper == null) { throw new InitializationException("ItemConverter: Missing block: " + itemFactory.getID()); } } else { itemWrapper = new FWItem(itemFactory); } MinecraftItemMapping minecraftItemMapping = new MinecraftItemMapping(itemWrapper, 0); map.put(itemFactory, minecraftItemMapping); // Don't register ItemBlocks twice if (!(dummy instanceof ItemBlock)) { NovaMinecraft.proxy.registerItem((FWItem) itemWrapper); String itemId = itemFactory.getID(); if (!itemId.contains(":")) itemId = NovaLauncher.instance().flatMap(NovaLauncher::getCurrentMod).map(Mod::id).orElse("nova") + ':' + itemId; GameRegistry.registerItem(itemWrapper, itemId); if (dummy.components.has(Category.class) && FMLCommonHandler.instance().getSide().isClient()) { //Add into creative tab Category category = dummy.components.get(Category.class); itemWrapper.setCreativeTab(CategoryConverter.instance().toNative(category, itemWrapper)); } Game.logger().info("Registered item: {}", itemFactory.getID()); } } private void registerMinecraftItemsToNOVA() { @SuppressWarnings("unchecked") Set<ResourceLocation> itemIDs = net.minecraft.item.Item.itemRegistry.getKeys(); itemIDs.forEach(itemID -> { net.minecraft.item.Item item = (net.minecraft.item.Item) net.minecraft.item.Item.itemRegistry.getObject(itemID); registerMinecraftMapping(item, 0); }); } private void registerSubtypeResolution() { Game.events().on(ItemEvent.IDNotFound.class).bind(this::onIDNotFound); } private void onIDNotFound(ItemEvent.IDNotFound event) { // if item minecraft:planks:2 is detected, this code will register minecraft:planks:2 dynamically // we cannot do this up front since there is **NO** reliable way to get the sub-items of an item int lastColon = event.id.lastIndexOf(':'); if (lastColon < 0) { return; } try { int meta = Integer.parseInt(event.id.substring(lastColon + 1)); String itemID = event.id.substring(0, lastColon); net.minecraft.item.Item item = (net.minecraft.item.Item) net.minecraft.item.Item.itemRegistry.getObject(itemID); if (item == null || !item.getHasSubtypes()) { return; } event.setRemappedFactory(registerMinecraftMapping(item, meta)); } catch (NumberFormatException ex) { } } private ItemFactory registerMinecraftMapping(net.minecraft.item.Item item, int meta) { MinecraftItemMapping mapping = new MinecraftItemMapping(item, meta); if (map.inverse().containsKey(mapping)) { // don't register twice, return the factory instead return map.inverse().get(mapping); } BWItemFactory itemFactory = new BWItemFactory(item, meta); map.put(itemFactory, mapping); Game.items().register(itemFactory); return itemFactory; } /** * Used to map MC items and their meta to nova item factories. */ public final class MinecraftItemMapping { public final net.minecraft.item.Item item; public final int meta; public MinecraftItemMapping(net.minecraft.item.Item item, int meta) { this.item = item; this.meta = item.getHasSubtypes() ? meta : 0; } public MinecraftItemMapping(ItemStack itemStack) { this.item = itemStack.getItem(); this.meta = itemStack.getHasSubtypes() ? itemStack.getItemDamage() : 0; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } MinecraftItemMapping that = (MinecraftItemMapping) o; if (meta != that.meta) { return false; } if (!item.equals(that.item)) { return false; } return true; } @Override public int hashCode() { int result = item.hashCode(); result = 31 * result + meta; return result; } @Override public String toString() { return WrapUtility.getItemID(item, meta); } } }