diff --git a/src/main/java/gregtech/GT_Mod.java b/src/main/java/gregtech/GT_Mod.java index 14670008779..e8f9bd712b2 100644 --- a/src/main/java/gregtech/GT_Mod.java +++ b/src/main/java/gregtech/GT_Mod.java @@ -30,6 +30,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import com.cleanroommc.modularui.factory.GuiManager; import com.google.common.base.Stopwatch; import com.google.common.collect.SetMultimap; import com.gtnewhorizon.gtnhlib.config.ConfigException; @@ -77,6 +78,7 @@ import gregtech.api.util.GT_SpawnEventHandler; import gregtech.api.util.GT_Utility; import gregtech.api.util.item.ItemHolder; +import gregtech.client.GT_KeyBindings; import gregtech.common.GT_DummyWorld; import gregtech.common.GT_Network; import gregtech.common.GT_Proxy; @@ -104,6 +106,8 @@ import gregtech.common.misc.spaceprojects.commands.SPM_Command; import gregtech.common.misc.spaceprojects.commands.SP_Command; import gregtech.common.misc.spaceprojects.commands.SpaceProject_Command; +import gregtech.common.misc.techtree.gui.GuiOpenEventHandler; +import gregtech.common.misc.techtree.gui.TechTreeGuiFactory; import gregtech.common.tileentities.machines.GT_MetaTileEntity_Hatch_CraftingInput_ME; import gregtech.common.tileentities.storage.GT_MetaTileEntity_DigitalChestBase; import gregtech.crossmod.holoinventory.HoloInventory; @@ -137,6 +141,7 @@ import gregtech.loaders.preload.GT_Loader_MultiTileEntities; import gregtech.loaders.preload.GT_Loader_OreDictionary; import gregtech.loaders.preload.GT_Loader_OreProcessing; +import gregtech.loaders.preload.GT_Loader_Technologies; import gregtech.loaders.preload.GT_PreLoad; import ic2.api.recipe.IRecipeInput; import ic2.api.recipe.RecipeOutput; @@ -315,11 +320,20 @@ public void onPreLoad(FMLPreInitializationEvent aEvent) { .getParentFile()); GT_PreLoad.adjustScrap(); + if (isClientSide()) { + GT_KeyBindings.registerBindings(); + } + AEApi.instance() .registries() .interfaceTerminal() .register(GT_MetaTileEntity_Hatch_CraftingInput_ME.class); + GuiManager.registerFactory(TechTreeGuiFactory.INSTANCE); + FMLCommonHandler.instance() + .bus() + .register(GuiOpenEventHandler.INSTANCE); + GT_PreLoad.runMineTweakerCompat(); new GT_Loader_OreProcessing().run(); @@ -335,6 +349,7 @@ public void onPreLoad(FMLPreInitializationEvent aEvent) { new GT_CoverBehaviorLoader().run(); new GT_SonictronLoader().run(); new GT_SpawnEventHandler(); + new GT_Loader_Technologies().run(); // populate itemstack instance for NBT check in GT_Recipe setItemStacks(); diff --git a/src/main/java/gregtech/api/enums/ItemList.java b/src/main/java/gregtech/api/enums/ItemList.java index 74f169681cc..bb8d900a111 100644 --- a/src/main/java/gregtech/api/enums/ItemList.java +++ b/src/main/java/gregtech/api/enums/ItemList.java @@ -2104,6 +2104,7 @@ public enum ItemList implements IItemContainer { BlockQuarkPipe, BlockQuarkReleaseChamber, BlockQuarkContainmentCasing, + TechTreeAccessor, LargeFluidExtractor, AcceleratorLV, AcceleratorMV, diff --git a/src/main/java/gregtech/api/gui/modularui2/UITextureAdapter.java b/src/main/java/gregtech/api/gui/modularui2/UITextureAdapter.java new file mode 100644 index 00000000000..8aee9955000 --- /dev/null +++ b/src/main/java/gregtech/api/gui/modularui2/UITextureAdapter.java @@ -0,0 +1,8 @@ +package gregtech.api.gui.modularui2; + +public class UITextureAdapter extends com.cleanroommc.modularui.drawable.UITexture { + + public UITextureAdapter(com.gtnewhorizons.modularui.api.drawable.UITexture texture) { + super(texture.location, texture.u0, texture.v0, texture.u1, texture.v1, false); + } +} diff --git a/src/main/java/gregtech/api/gui/modularui2/UITextures.java b/src/main/java/gregtech/api/gui/modularui2/UITextures.java new file mode 100644 index 00000000000..bc632b4c701 --- /dev/null +++ b/src/main/java/gregtech/api/gui/modularui2/UITextures.java @@ -0,0 +1,21 @@ +package gregtech.api.gui.modularui2; + +import com.cleanroommc.modularui.drawable.AdaptableUITexture; +import com.cleanroommc.modularui.drawable.UITexture; + +import gregtech.api.enums.Mods; +import gregtech.api.gui.modularui.GT_UITextures; + +public class UITextures { + + public static final UITexture BACKGROUND_SCREEN_BLUE = AdaptableUITexture.builder() + .location(Mods.TecTech.ID, "gui/background/screen_blue") + .adaptable(2) + .imageSize(90, 72) + .build(); + + public static final UITexture BUTTON_STANDARD_TOGGLE_UP = new UITextureAdapter( + GT_UITextures.BUTTON_STANDARD_TOGGLE.getSubArea(0f, 0f, 0.5f, 0.5f)); + public static final UITexture BUTTON_STANDARD_TOGGLE_DOWN = new UITextureAdapter( + GT_UITextures.BUTTON_STANDARD_TOGGLE.getSubArea(0.5f, 0.5f, 1f, 1f)); +} diff --git a/src/main/java/gregtech/api/net/PacketOpenTechTree.java b/src/main/java/gregtech/api/net/PacketOpenTechTree.java new file mode 100644 index 00000000000..420488fd203 --- /dev/null +++ b/src/main/java/gregtech/api/net/PacketOpenTechTree.java @@ -0,0 +1,29 @@ +package gregtech.api.net; + +import net.minecraft.entity.player.EntityPlayerMP; + +import cpw.mods.fml.common.network.simpleimpl.IMessage; +import cpw.mods.fml.common.network.simpleimpl.IMessageHandler; +import cpw.mods.fml.common.network.simpleimpl.MessageContext; +import gregtech.common.misc.techtree.gui.TechTreeGuiFactory; +import io.netty.buffer.ByteBuf; + +public class PacketOpenTechTree implements IMessage, IMessageHandler { + + @Override + public void fromBytes(ByteBuf buf) { + // No need to do anything here + } + + @Override + public void toBytes(ByteBuf buf) { + // No need to do anything here + } + + @Override + public IMessage onMessage(PacketOpenTechTree message, MessageContext ctx) { + EntityPlayerMP player = ctx.getServerHandler().playerEntity; + TechTreeGuiFactory.open(player, player.serverPosX, player.serverPosY, player.serverPosZ); + return null; + } +} diff --git a/src/main/java/gregtech/client/GT_KeyBindings.java b/src/main/java/gregtech/client/GT_KeyBindings.java new file mode 100644 index 00000000000..2da350e6971 --- /dev/null +++ b/src/main/java/gregtech/client/GT_KeyBindings.java @@ -0,0 +1,19 @@ +package gregtech.client; + +import static gregtech.api.enums.Mods.GregTech; + +import net.minecraft.client.settings.KeyBinding; + +import org.lwjgl.input.Keyboard; + +import cpw.mods.fml.client.registry.ClientRegistry; + +public class GT_KeyBindings { + + public static KeyBinding openTechTree; + + public static void registerBindings() { + openTechTree = new KeyBinding("Open Tech Tree", Keyboard.KEY_GRAVE, GregTech.name()); + ClientRegistry.registerKeyBinding(openTechTree); + } +} diff --git a/src/main/java/gregtech/common/items/ItemTechAccessor.java b/src/main/java/gregtech/common/items/ItemTechAccessor.java new file mode 100644 index 00000000000..ae2ac6212c4 --- /dev/null +++ b/src/main/java/gregtech/common/items/ItemTechAccessor.java @@ -0,0 +1,45 @@ +package gregtech.common.items; + +import static gregtech.api.enums.Mods.GregTech; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.world.World; + +import com.cleanroommc.modularui.api.IGuiHolder; +import com.cleanroommc.modularui.screen.ModularPanel; +import com.cleanroommc.modularui.screen.ModularScreen; +import com.cleanroommc.modularui.value.sync.PanelSyncManager; + +import cpw.mods.fml.common.registry.GameRegistry; +import gregtech.api.util.GT_LanguageManager; +import gregtech.common.misc.techtree.gui.TechTreeGui; +import gregtech.common.misc.techtree.gui.TechTreeGuiData; +import gregtech.common.misc.techtree.gui.TechTreeGuiFactory; + +public class ItemTechAccessor extends Item implements IGuiHolder { + + public ItemTechAccessor() { + super(); + setUnlocalizedName("gt.techaccessor"); + setMaxStackSize(1); + GameRegistry.registerItem(this, this.getUnlocalizedName(), GregTech.ID); + GT_LanguageManager.addStringLocalization("item.gt.techaccessor.name", "Tech Tree Accessor"); + } + + @Override + public ModularScreen createScreen(TechTreeGuiData data, ModularPanel mainPanel) { + return new ModularScreen(mainPanel); + } + + public ModularPanel buildUI(TechTreeGuiData data, PanelSyncManager syncManager) { + return TechTreeGui.buildUI(data, syncManager); + } + + @Override + public ItemStack onItemRightClick(ItemStack itemStackIn, World worldIn, EntityPlayer player) { + TechTreeGuiFactory.open(player, player.serverPosX, player.serverPosY, player.serverPosZ); + return itemStackIn; + } +} diff --git a/src/main/java/gregtech/common/misc/techtree/TechList.java b/src/main/java/gregtech/common/misc/techtree/TechList.java new file mode 100644 index 00000000000..d8f78ee2a2e --- /dev/null +++ b/src/main/java/gregtech/common/misc/techtree/TechList.java @@ -0,0 +1,16 @@ +package gregtech.common.misc.techtree; + +import gregtech.common.misc.techtree.base.Technology; + +public class TechList { + + // If a technology has no requirements, this will be used as a prerequisite instead. + public static Technology HiddenRoot; + public static Technology TechA; + public static Technology TechB; + public static Technology TechC; + public static Technology TechD; + public static Technology TechE; + public static Technology TechF; + public static Technology TechG; +} diff --git a/src/main/java/gregtech/common/misc/techtree/TechnologyRegistry.java b/src/main/java/gregtech/common/misc/techtree/TechnologyRegistry.java new file mode 100644 index 00000000000..7563425f6cf --- /dev/null +++ b/src/main/java/gregtech/common/misc/techtree/TechnologyRegistry.java @@ -0,0 +1,49 @@ +package gregtech.common.misc.techtree; + +import java.util.Collection; +import java.util.HashMap; + +import gregtech.common.misc.techtree.interfaces.ITechnology; + +public class TechnologyRegistry { + + private static final HashMap registeredTechs = new HashMap<>(); + + /** + * Check if a technology exists + * + * @param name The name of the technology to look up + * @return True if the technology exists and is registered, false otherwise + */ + public static boolean technologyExists(String name) { + return registeredTechs.containsKey(name); + } + + /** + * Register a new technology into the system + * + * @param tech The technology to register. + * @throws RuntimeException if there already is a technology with the same internal name + */ + public static void register(ITechnology tech) { + if (technologyExists(tech.getInternalName())) { + throw new RuntimeException("Technology with internal name" + tech.getInternalName() + " already exists."); + } + registeredTechs.put(tech.getInternalName(), tech); + } + + /** + * @return A list of all registered technologies + */ + public static Collection getTechnologies() { + return registeredTechs.values(); + } + + public static ITechnology findTechnology(String internalName) { + return registeredTechs.get(internalName); + } + + public static int numTechnologies() { + return registeredTechs.size(); + } +} diff --git a/src/main/java/gregtech/common/misc/techtree/base/Technology.java b/src/main/java/gregtech/common/misc/techtree/base/Technology.java new file mode 100644 index 00000000000..d432ca1657f --- /dev/null +++ b/src/main/java/gregtech/common/misc/techtree/base/Technology.java @@ -0,0 +1,114 @@ +package gregtech.common.misc.techtree.base; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.UUID; + +import net.minecraft.util.StatCollector; + +import gregtech.common.misc.techtree.interfaces.IPrerequisite; +import gregtech.common.misc.techtree.interfaces.ITechnology; + +/** + * @author NotAPenguin0 + */ +public class Technology implements ITechnology, IPrerequisite { + + /** + * Internal name of the technology. + */ + private final String internalName; + /** + * Unlocalized name of the technology + */ + private final String unlocalizedName; + + /** + * Prerequisite technologies required to research this technology. + */ + private final IPrerequisite[] prerequisites; + + /** + * Depth of the technology + */ + private int depth = 0; + + /** + * Technologies of which this technology is a prerequisite + */ + private final ArrayList children = new ArrayList<>(); + + /** + * Create a new researchable technology + * + * @param name The internal name of the technology + * @param unlocalizedName The unlocalized name of the technology + * @param prerequisites The technologies required to unlock this technology. This cannot be changed afterwards. + */ + public Technology(String name, String unlocalizedName, IPrerequisite[] prerequisites) { + this.internalName = name; + this.unlocalizedName = unlocalizedName; + this.prerequisites = prerequisites; + + // Calculate depth based on depth of prerequisite technologies. + // The depth into the tree is one more than the depth of the deepest prerequisite technology. + // Note that we can do this, because you cannot add prerequisite technologies after construction, + // so the depth of all prerequisite techs is final once we reach this constructor. + for (IPrerequisite prereq : prerequisites) { + // If this prerequisite is a technology, modify depth and children for it. + ITechnology tech = prereq.getTechnology(); + if (tech != null) { + depth = Math.max(depth, tech.getDepth() + 1); + // Also store this technology as a child tech of each prerequisite, since this makes graph traversal + // a lot easier + tech.addChildTechnology(this); + } + } + } + + @Override + public void addChildTechnology(ITechnology tech) { + this.children.add(tech); + } + + @Override + public String getInternalName() { + return internalName; + } + + @Override + public String getUnlocalizedName() { + return unlocalizedName; + } + + @Override + public String getLocalizedName() { + return StatCollector.translateToLocal(getUnlocalizedName()); + } + + @Override + public int getDepth() { + return depth; + } + + @Override + public Collection getPrerequisites() { + return Arrays.asList(this.prerequisites); + } + + @Override + public Collection getChildren() { + return children; + } + + @Override + public boolean isSatisfied(UUID playerUuid) { + return false; + } + + @Override + public ITechnology getTechnology() { + return this; + } +} diff --git a/src/main/java/gregtech/common/misc/techtree/base/TechnologyBuilder.java b/src/main/java/gregtech/common/misc/techtree/base/TechnologyBuilder.java new file mode 100644 index 00000000000..bfc7f9d6044 --- /dev/null +++ b/src/main/java/gregtech/common/misc/techtree/base/TechnologyBuilder.java @@ -0,0 +1,69 @@ +package gregtech.common.misc.techtree.base; + +import java.util.ArrayList; +import java.util.Arrays; + +import gregtech.common.misc.techtree.TechList; +import gregtech.common.misc.techtree.TechnologyRegistry; +import gregtech.common.misc.techtree.interfaces.IPrerequisite; + +/** + * Helper class to more easily construct researchable technologies and add them to the registry + * + * @author NotAPenguin0 + */ +public class TechnologyBuilder { + + private String internalName; + private String unlocalizedname = ""; + private IPrerequisite[] prereqs = new IPrerequisite[] {}; + + /** + * Start constructing a new researchable technology with the given internal name + * + * @param internalName Internal name for this technology. This must be globally unique + */ + public TechnologyBuilder(String internalName) { + this.internalName = internalName; + } + + public TechnologyBuilder unlocalizedName(String name) { + this.unlocalizedname = name; + return this; + } + + /** + * Set prerequisite technologies for this technology + * + * @param prereqs List of prerequisites, required before researching this technology + */ + public TechnologyBuilder prerequisites(IPrerequisite... prereqs) { + this.prereqs = prereqs; + return this; + } + + /** + * Builds the technology object and registers it to the global technology registry. + * + * @return The researchable tech, so you can store it for easy referencing + */ + public Technology buildAndRegister() { + // If no prerequisite technology exists, add the hidden root tech as a prerequisite + boolean foundTech = false; + for (IPrerequisite prereq : this.prereqs) { + if (prereq.getTechnology() != null) { + foundTech = true; + break; + } + } + if (!foundTech) { + // Add root tech to the list + ArrayList prereqs = new ArrayList<>(Arrays.asList(this.prereqs)); + prereqs.add(TechList.HiddenRoot); + this.prereqs = prereqs.toArray(new IPrerequisite[] {}); + } + Technology tech = new Technology(this.internalName, this.unlocalizedname, this.prereqs); + TechnologyRegistry.register(tech); + return tech; + } +} diff --git a/src/main/java/gregtech/common/misc/techtree/gui/GuiOpenEventHandler.java b/src/main/java/gregtech/common/misc/techtree/gui/GuiOpenEventHandler.java new file mode 100644 index 00000000000..6746c021fae --- /dev/null +++ b/src/main/java/gregtech/common/misc/techtree/gui/GuiOpenEventHandler.java @@ -0,0 +1,23 @@ +package gregtech.common.misc.techtree.gui; + +import cpw.mods.fml.common.eventhandler.SubscribeEvent; +import cpw.mods.fml.common.gameevent.InputEvent; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import gregtech.api.net.PacketOpenTechTree; +import gregtech.client.GT_KeyBindings; +import gtPlusPlus.core.handler.PacketHandler; + +public class GuiOpenEventHandler { + + public static final GuiOpenEventHandler INSTANCE = new GuiOpenEventHandler(); + + @SubscribeEvent + @SideOnly(Side.CLIENT) + public void onKey(InputEvent.KeyInputEvent event) { + if (GT_KeyBindings.openTechTree.isPressed()) { + // Yeah, this is the GT++ packet handler + PacketHandler.sendToServer(new PacketOpenTechTree()); + } + } +} diff --git a/src/main/java/gregtech/common/misc/techtree/gui/TechLayer.java b/src/main/java/gregtech/common/misc/techtree/gui/TechLayer.java new file mode 100644 index 00000000000..b515ba9073c --- /dev/null +++ b/src/main/java/gregtech/common/misc/techtree/gui/TechLayer.java @@ -0,0 +1,22 @@ +package gregtech.common.misc.techtree.gui; + +import java.util.ArrayList; + +import com.cleanroommc.modularui.api.widget.IWidget; +import com.cleanroommc.modularui.widgets.layout.Column; + +import gregtech.common.misc.techtree.interfaces.ITechnology; + +public class TechLayer extends Column { + + private final ArrayList containedTechs = new ArrayList<>(); + + public void addTechnology(ITechnology tech, IWidget widget) { + containedTechs.add(tech); + child(widget); + } + + public ArrayList getTechnologies() { + return containedTechs; + } +} diff --git a/src/main/java/gregtech/common/misc/techtree/gui/TechSelectorButton.java b/src/main/java/gregtech/common/misc/techtree/gui/TechSelectorButton.java new file mode 100644 index 00000000000..b393e183dc6 --- /dev/null +++ b/src/main/java/gregtech/common/misc/techtree/gui/TechSelectorButton.java @@ -0,0 +1,13 @@ +package gregtech.common.misc.techtree.gui; + +import com.cleanroommc.modularui.api.ITheme; +import com.cleanroommc.modularui.theme.WidgetTheme; +import com.cleanroommc.modularui.widgets.ButtonWidget; + +public class TechSelectorButton> extends ButtonWidget { + + @Override + public WidgetTheme getWidgetTheme(ITheme theme) { + return theme.getFallback(); + } +} diff --git a/src/main/java/gregtech/common/misc/techtree/gui/TechTreeGui.java b/src/main/java/gregtech/common/misc/techtree/gui/TechTreeGui.java new file mode 100644 index 00000000000..973f5f72150 --- /dev/null +++ b/src/main/java/gregtech/common/misc/techtree/gui/TechTreeGui.java @@ -0,0 +1,121 @@ +package gregtech.common.misc.techtree.gui; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; + +import com.cleanroommc.modularui.api.drawable.IKey; +import com.cleanroommc.modularui.api.widget.IWidget; +import com.cleanroommc.modularui.drawable.DynamicDrawable; +import com.cleanroommc.modularui.screen.ModularPanel; +import com.cleanroommc.modularui.utils.Alignment; +import com.cleanroommc.modularui.value.sync.PanelSyncManager; +import com.cleanroommc.modularui.widget.ScrollWidget; +import com.cleanroommc.modularui.widget.scroll.HorizontalScrollData; +import com.cleanroommc.modularui.widgets.TextWidget; +import com.cleanroommc.modularui.widgets.layout.Flow; +import com.cleanroommc.modularui.widgets.layout.Row; + +import gregtech.api.gui.modularui2.UITextures; +import gregtech.common.misc.techtree.TechnologyRegistry; +import gregtech.common.misc.techtree.interfaces.ITechnology; + +public class TechTreeGui { + + public static TreePanel buildMainPanel(ArrayList containers, HashMap widgetForTech) { + return (TreePanel) TreePanel.defaultPanel("Technology Tree", containers, widgetForTech) + .full() + .background(UITextures.BACKGROUND_SCREEN_BLUE); + } + + public static TextWidget buildTitle() { + return IKey.lang("gt.gui.techtree.title") + .alignment(Alignment.Center) + .color(0xffffff) + .shadow(true) + .asWidget() + .scale(1.2f) + .align(Alignment.TopCenter) + .top(5); + } + + public static IWidget buildTechWidget(TechTreeGuiData data, ITechnology tech) { + return new TechSelectorButton<>().size(60, 16) + .overlay(IKey.lang(tech.getUnlocalizedName())) + .onMousePressed(mouseButton -> { + // Make this tech the selected technology + if (mouseButton == 0) { + data.setSelectedTechnology(tech); + } + return true; + }) + // Dynamically set background of this button based on the currently selected tech + .background(new DynamicDrawable(() -> { + if (data.getSelectedTechnology() == tech) { + return UITextures.BUTTON_STANDARD_TOGGLE_DOWN; + } + return UITextures.BUTTON_STANDARD_TOGGLE_UP; + })); + } + + public static TechLayer makeTechContainer(int layer) { + return (TechLayer) new TechLayer().marginRight(40) + .coverChildrenWidth(); + } + + public static TechLayer getTechContainer(int layer, ArrayList containers) { + // If this container already exists, simply return it + if (layer < containers.size()) return containers.get(layer); + // If it doesn't extend the list with new container objects until the needed layer is reached + int n = containers.size(); + for (int i = n; i <= layer; ++i) { + containers.add(i, makeTechContainer(i)); + } + // Then return the container + return containers.get(layer); + } + + public static ModularPanel buildUI(TechTreeGuiData data, PanelSyncManager syncManager) { + data.getNEISettings() + .disableNEI(); + + TechTreeLayout layout = TechTreeLayout.constructOrGet(); + // Index into the list specifies the layer of the technology + ArrayList techContainers = new ArrayList<>(); + HashMap widgetForTech = new HashMap<>(); + + Collection techs = TechnologyRegistry.getTechnologies(); + for (ITechnology tech : techs) { + IWidget techWidget = buildTechWidget(data, tech); + TechLayer container = getTechContainer(layout.layerInfo.getDisplayLayer(tech), techContainers); + container.addTechnology(tech, techWidget); + widgetForTech.put(tech.getInternalName(), techWidget); + } + + TreePanel mainPanel = buildMainPanel(techContainers, widgetForTech); + mainPanel.child(buildTitle()); + + Flow treeParent = new Row().align(Alignment.TopLeft) + // Cover all columns with width (at least) + .coverChildrenWidth(); + + // Now add all containers as children of the main row + for (TechLayer container : techContainers) { + treeParent.child(container); + } + + // Put the row into a horizontally scrollable region + IWidget scroll = new ScrollWidget<>(new HorizontalScrollData()).align(Alignment.TopLeft) + .marginLeft(20) + .marginRight(20) + .topRel(0.2f) + .widthRel(1.0f) + .heightRel(0.6f) + .child(treeParent); + + // Then add main row to the main panel + mainPanel.child(scroll); + + return mainPanel; + } +} diff --git a/src/main/java/gregtech/common/misc/techtree/gui/TechTreeGuiData.java b/src/main/java/gregtech/common/misc/techtree/gui/TechTreeGuiData.java new file mode 100644 index 00000000000..c4cf4225c74 --- /dev/null +++ b/src/main/java/gregtech/common/misc/techtree/gui/TechTreeGuiData.java @@ -0,0 +1,26 @@ +package gregtech.common.misc.techtree.gui; + +import net.minecraft.entity.player.EntityPlayer; + +import com.cleanroommc.modularui.factory.GuiData; + +import gregtech.common.misc.techtree.interfaces.ITechnology; + +public class TechTreeGuiData extends GuiData { + + private ITechnology selectedTechnology; + + public TechTreeGuiData(EntityPlayer player, ITechnology selectedTechnology) { + super(player); + this.selectedTechnology = selectedTechnology; + } + + public void setSelectedTechnology(ITechnology tech) { + this.selectedTechnology = tech; + } + + public ITechnology getSelectedTechnology() { + return this.selectedTechnology; + } + +} diff --git a/src/main/java/gregtech/common/misc/techtree/gui/TechTreeGuiFactory.java b/src/main/java/gregtech/common/misc/techtree/gui/TechTreeGuiFactory.java new file mode 100644 index 00000000000..4d2a241ad29 --- /dev/null +++ b/src/main/java/gregtech/common/misc/techtree/gui/TechTreeGuiFactory.java @@ -0,0 +1,76 @@ +package gregtech.common.misc.techtree.gui; + +import java.io.IOException; + +import javax.annotation.Nonnull; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.network.PacketBuffer; + +import org.jetbrains.annotations.NotNull; + +import com.cleanroommc.modularui.factory.AbstractUIFactory; +import com.cleanroommc.modularui.factory.GuiManager; + +import gregtech.api.enums.ItemList; +import gregtech.common.items.ItemTechAccessor; +import gregtech.common.misc.techtree.TechnologyRegistry; +import gregtech.common.misc.techtree.interfaces.ITechnology; + +public class TechTreeGuiFactory extends AbstractUIFactory { + + public static final TechTreeGuiFactory INSTANCE = new TechTreeGuiFactory(); + + private TechTreeGuiFactory() { + super("gregtech:techtree:gui"); + } + + public static void open(@Nonnull EntityPlayer player, int x, int y, int z) { + // Fuck you galacticraft + if (player instanceof EntityPlayerMP) { + TechTreeGuiData data = new TechTreeGuiData(player, null); + GuiManager.open(INSTANCE, data, (EntityPlayerMP) player); + } + } + + @Override + public @NotNull ItemTechAccessor getGuiHolder(TechTreeGuiData data) { + return (ItemTechAccessor) ItemList.TechTreeAccessor.getItem(); + } + + @Override + public void writeGuiData(TechTreeGuiData guiData, PacketBuffer buffer) { + // Write data to buffer + NBTTagCompound dataNBT = new NBTTagCompound(); + if (guiData.getSelectedTechnology() != null) { + dataNBT.setString( + "selectedTechnology", + guiData.getSelectedTechnology() + .getInternalName()); + } + + try { + buffer.writeNBTTagCompoundToBuffer(dataNBT); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @NotNull + @Override + public TechTreeGuiData readGuiData(EntityPlayer player, PacketBuffer buffer) { + try { + NBTTagCompound data = buffer.readNBTTagCompoundFromBuffer(); + // Read currently selected technology from nbt data buffer + ITechnology selectedTechnology = null; + if (data.hasKey("selectedTechnology")) { + selectedTechnology = TechnologyRegistry.findTechnology(data.getString("selectedTechnology")); + } + return new TechTreeGuiData(player, selectedTechnology); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/gregtech/common/misc/techtree/gui/TechTreeLayout.java b/src/main/java/gregtech/common/misc/techtree/gui/TechTreeLayout.java new file mode 100644 index 00000000000..70aaa7ac267 --- /dev/null +++ b/src/main/java/gregtech/common/misc/techtree/gui/TechTreeLayout.java @@ -0,0 +1,244 @@ +package gregtech.common.misc.techtree.gui; + +// Paper on layered DAG layouting, this algorithm will be used to draw the technology tree! +// https://web.archive.org/web/20180728233917/https://www.it.usyd.edu.au/~shhong/fab.pdf +// Some notes: +// 1. Since our technology tree has no cycles, we can omit the first step of the algorithm on cycle removal. +// Note that the acyclic-ness of the tree is enforced by the use of the technology builder. After construction, +// a technology cannot have prerequisites added to it, so it's impossible to construct a cyclic graph. +// 2. A major assumption the technology system will make is that the entire tech graph is connected. +// This simplifies some code greatly. +// 3. To traverse the technology tree more easily, we will implicitly define a root technology in the tree. +// This node will be hidden in the GUI and is only for internal purposes. +// Note that this implicit definition also enforces the connected-ness of the tree. +// 4. For layering, we will use Coffman-Graham layering. The reason for this is that we can have a hard limit on the +// width +// of each layer using this algorithm. The other algorithms focus on minimizing height or edge span, both of which are +// not the biggest issue in our case because we will need a large scroll region for the technology tree anyway. + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import gregtech.common.misc.techtree.TechnologyRegistry; +import gregtech.common.misc.techtree.interfaces.IPrerequisite; +import gregtech.common.misc.techtree.interfaces.ITechnology; + +public class TechTreeLayout { + + private static TechTreeLayout INSTANCE = null; + + public static class Layering { + + private final Map layerForTech = new HashMap<>(); + private final Map> layers = new HashMap<>(); + + public Set getLayer(int layer) { + return layers.computeIfAbsent(layer, k -> new HashSet<>()); + } + + public void setLayerForTech(ITechnology tech, int layer) { + Set layerSet = getLayer(layer); + layerForTech.put(tech.getInternalName(), layer); + layerSet.add(tech); + } + + public int getDisplayLayer(ITechnology tech) { + // Layers are assigned with sinks having a lower number, so for display purposes + // simply invert it based on the number of layers + int numLayers = layers.size(); + return numLayers - getInternalLayer(tech); + } + + public int getInternalLayer(ITechnology tech) { + return layerForTech.get(tech.getInternalName()); + } + } + + public final Layering layerInfo; + + private TechTreeLayout(Layering layers) { + this.layerInfo = layers; + } + + private static boolean isOrderUnassigned(HashMap order, ITechnology tech, int numNodes) { + return order.get(tech.getInternalName()) == numNodes + 1; + } + + private static Set computeOrderSet(HashMap order, ITechnology tech) { + // The set of ordering numbers for this technology is the set of orders of its prerequisites + Set orderSet = new HashSet<>(); + for (IPrerequisite parent : tech.getPrerequisites()) { + // If this parent is a technology, add its ordering number to the order set + ITechnology parentTech = parent.getTechnology(); + if (parentTech != null) orderSet.add(order.get(parentTech.getInternalName())); + } + return orderSet; + } + + // Returns true if s < t. + // Note that this function MODIFIES the input sets, but since we do not need them later this is fine + private static boolean compareOrderSets(Set s, Set t) { + // s < t if s is empty and t is not + if (s.isEmpty() && !t.isEmpty()) return true; + // If neither is empty, do a proper comparison + if (!s.isEmpty() && !t.isEmpty()) { + Integer maxS = s.stream() + .max(Integer::compare) + .get(); + Integer maxT = t.stream() + .max(Integer::compare) + .get(); + // If the largest value in s is smaller than that in t, s < t + if (maxS < maxT) return true; + // If they are equal, remove the maximum values and keep comparing + else if (maxS.equals(maxT)) { + // Note that this modifies the sets, but because they are not used after this call it's fine to do so + // (until we start caching) + s.remove(maxS); + t.remove(maxT); + return compareOrderSets(s, t); + } + } + // In any other case, t > s + return false; + } + + private static HashMap orderNodes() { + HashMap nodeOrdering = new HashMap<>(); + + // First initialize all values in the order to the maximum value. + // If we have n nodes, the maximum order value is n + 1. + int numNodes = TechnologyRegistry.numTechnologies(); + for (ITechnology tech : TechnologyRegistry.getTechnologies()) { + String id = tech.getInternalName(); + nodeOrdering.put(id, numNodes + 1); + } + + // Now we loop over all possible ordering values and assign them + for (int i = 1; i <= numNodes; ++i) { + // Among the set of unordered nodes (this is the nodes with ordering n + 1), we find the node + // with the minimum order value and assign it to this. Note that currently, this algorithm is *quite* slow. + // I'm sure there is a better way to construct this, but for now we will go with a simple brute force + // version to demonstrate a proof of concept and find something that works. + ITechnology chosenTech = null; + for (ITechnology tech : TechnologyRegistry.getTechnologies()) { + // If this node is unassigned, consider it for our next candidate + if (isOrderUnassigned(nodeOrdering, tech, numNodes)) { + // If we don't have anything to compare it to yet, simply pick this one for now. + if (chosenTech == null) { + chosenTech = tech; + continue; + } + // If we do have something to compare it to, we need to do the comparison. + // Note that while it is quite inefficient to recompute this ordering every time, since the map + // of orders can change the simplest way for now is to recompute it, since caching it is not + // correct. + Set candidateOrder = computeOrderSet(nodeOrdering, tech); + Set knownOrder = computeOrderSet(nodeOrdering, chosenTech); + // If the new candidate has a smaller ordering, assign it as our new chosen technology. + if (compareOrderSets(candidateOrder, knownOrder)) { + chosenTech = tech; + } + } + } + if (chosenTech == null) throw new NullPointerException("Previous loop should never produce a null value"); + nodeOrdering.put(chosenTech.getInternalName(), i); + } + + return nodeOrdering; + } + + private static boolean canLayerNode(ITechnology node, Set nodesToLayer) { + // A node can be assigned a layer if all of its descendants have been layered + for (ITechnology descendant : node.getChildren()) { + if (nodesToLayer.contains(descendant)) return false; + } + return true; + } + + private static boolean mayAssignToLayer(ITechnology node, int layer, Layering layering) { + // If all outgoing nodes are contained in lower layers than this layer, we can add this node to this layer + for (ITechnology descendant : node.getChildren()) { + // We know that this node must have a layer assigned, + // based on the precondition in canLayerNode + int descLayer = layering.getInternalLayer(descendant); + // If this layer is not strictly smaller than the layer we are trying to assign this node to, + // forbid it + if (descLayer >= layer) { + return false; + } + } + return true; + } + + private static Layering calculateLayering(int maxWidth) { + // Implements the Coffman-Graham layering algorithm to assign a layer to each node in the graph + Layering layering = new Layering(); + + // Phase 1: Construct an ordering of all nodes in the tree + HashMap order = orderNodes(); + // Phase 2: Assign a layer to each node + + // Set of all nodes without an assigned layer. Initially this is obviously all nodes. + Set nodesToLayer = new HashSet<>(TechnologyRegistry.getTechnologies()); + + // Keep repeating as long as there are nodes without an unassigned layer + int currentLayer = 1; + while (!nodesToLayer.isEmpty()) { + // Find the next node to layer. We do this by finding the node with minimal order that can be layered + int minOrder = Integer.MAX_VALUE; + ITechnology minNode = null; + for (ITechnology node : nodesToLayer) { + // If we haven't found a node to layer yet, assign one + if (canLayerNode(node, nodesToLayer)) { + // Compare the order of this node with our current minimum. + int nodeOrder = order.get(node.getInternalName()); + if (nodeOrder < minOrder) { + minOrder = nodeOrder; + minNode = node; + } + } + } + if (minNode == null) throw new NullPointerException("This loop should never produce a null value"); + // Found a minimal order, layerable node, now assign it a layer + Set layerSet = layering.getLayer(currentLayer); + // If this layer is not exceeding the max width, and we can assign this node to it, do so + if (layerSet.size() < maxWidth && mayAssignToLayer(minNode, currentLayer, layering)) { + layering.setLayerForTech(minNode, currentLayer); + } else { + // Otherwise assign it to a new layer + currentLayer += 1; + layering.setLayerForTech(minNode, currentLayer); + } + nodesToLayer.remove(minNode); + } + + return layering; + } + + private static TechTreeLayout construct() { + // Construct layout information of the technology tree based on the current technology registry. + + // Step 1 in constructing a layout is dividing all the nodes in the tree into layers. We can give this + // procedure a maximum amount of nodes per layer, to limit the width of each layer. + + // TODO: Parameter to configure this somehow, or decide based on gui layout + final int maxWidth = 3; + Layering layers = calculateLayering(maxWidth); + + // Step 2 is re-ordering the nodes inside each layer to minimize the number of crossings between edges + // in the newly layered graph. This is a NP-hard problem, so the solution will not be efficient either way. + // While complex hybrid algorithms are possible, this doesn't seem to be worth it, so we will be going with a + // common and relatively simple heuristic-based algorithm instead. + + return new TechTreeLayout(layers); + } + + public static TechTreeLayout constructOrGet() { + if (INSTANCE != null) return INSTANCE; + INSTANCE = construct(); + return INSTANCE; + } +} diff --git a/src/main/java/gregtech/common/misc/techtree/gui/TreePanel.java b/src/main/java/gregtech/common/misc/techtree/gui/TreePanel.java new file mode 100644 index 00000000000..2bfbab79da1 --- /dev/null +++ b/src/main/java/gregtech/common/misc/techtree/gui/TreePanel.java @@ -0,0 +1,231 @@ +package gregtech.common.misc.techtree.gui; + +import static gregtech.api.enums.Mods.GregTech; + +import java.nio.FloatBuffer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import org.jetbrains.annotations.NotNull; +import org.lwjgl.BufferUtils; +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL15; +import org.lwjgl.opengl.GL20; + +import com.cleanroommc.modularui.api.widget.IWidget; +import com.cleanroommc.modularui.screen.ModularPanel; +import com.cleanroommc.modularui.screen.viewport.GuiContext; +import com.cleanroommc.modularui.theme.WidgetTheme; +import com.cleanroommc.modularui.widget.sizer.Area; +import com.gtnewhorizon.gtnhlib.client.renderer.shader.ShaderProgram; +import com.gtnewhorizons.modularui.api.math.Pos2d; + +import gregtech.common.misc.techtree.interfaces.ITechnology; + +public class TreePanel extends ModularPanel { + + private final ArrayList containers; + private final HashMap widgetForTech; + + private ShaderProgram arrowShader; + private boolean initialized = false; + private int vbo_pID = -1; + private int vbo_vID = -1; + private int a_p0 = -1, a_p1 = -1, a_p2 = -1; + private int u_dist = -1; + private int u_ScreenSize = -1; + + public TreePanel(@NotNull String name, ArrayList containers, HashMap widgetForTech) { + super(name); + this.containers = containers; + this.widgetForTech = widgetForTech; + } + + public static TreePanel defaultPanel(@NotNull String name, ArrayList containers, + HashMap widgetForTech) { + return defaultPanel(name, 176, 166, containers, widgetForTech); + } + + public static TreePanel defaultPanel(@NotNull String name, int width, int height, ArrayList containers, + HashMap widgetForTech) { + + return (TreePanel) new TreePanel(name, containers, widgetForTech).size(width, height); + } + + Pos2d getWidgetCenterRight(IWidget widget, int thickness) { + Area area = widget.getArea(); + return new Pos2d(area.x + area.width - thickness, area.y + area.height / 2); + } + + Pos2d getWidgetCenterLeft(IWidget widget, int thickness) { + Area area = widget.getArea(); + return new Pos2d(area.x + thickness, area.y + area.height / 2); + } + + private void init() { + + try { + arrowShader = new ShaderProgram( + GregTech.resourceDomain, + "shaders/techTreeArrow.vert.glsl", + "shaders/techTreeArrow.frag.glsl"); + + a_p0 = arrowShader.getAttribLocation("a_p0"); + a_p1 = arrowShader.getAttribLocation("a_p1"); + a_p2 = arrowShader.getAttribLocation("a_p2"); + u_dist = arrowShader.getUniformLocation("u_dist"); + u_ScreenSize = arrowShader.getUniformLocation("u_ScreenSize"); + } catch (Exception e) { + System.out.println(e.getMessage()); + return; + } + GL20.glUniform1f(u_dist, 1); + + vbo_pID = GL15.glGenBuffers(); + vbo_vID = GL15.glGenBuffers(); + initialized = true; + } + + @Override + public void draw(GuiContext context, WidgetTheme widgetTheme) { + super.draw(context, widgetTheme); + + if (!initialized) { + init(); + if (!initialized) return; + } + + int vertexCount = regenerateBuffers(); + + arrowShader.use(); + + GL11.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS); + GL11.glDisable(GL11.GL_CULL_FACE); + GL11.glEnable(GL11.GL_BLEND); + GL20.glEnableVertexAttribArray(a_p0); + GL20.glEnableVertexAttribArray(a_p1); + GL20.glEnableVertexAttribArray(a_p2); + + GL20.glUniform2f(u_ScreenSize, context.mc.displayWidth, context.mc.displayHeight); + GL20.glUniform1f(u_dist, 2); + + GL11.glEnableClientState(GL11.GL_VERTEX_ARRAY); + GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo_pID); + int stride = 2 * 3 * Float.BYTES; // 3 vec2 attributes + + GL20.glVertexAttribPointer(a_p0, 2, GL11.GL_FLOAT, false, stride, 0); + GL20.glVertexAttribPointer(a_p1, 2, GL11.GL_FLOAT, false, stride, 2 * Float.BYTES); + GL20.glVertexAttribPointer(a_p2, 2, GL11.GL_FLOAT, false, stride, 4 * Float.BYTES); + + GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo_vID); + GL11.glVertexPointer(3, GL11.GL_FLOAT, 0, 0); + + GL11.glDrawArrays(GL11.GL_TRIANGLES, 0, vertexCount); + GL11.glDisableClientState(GL11.GL_VERTEX_ARRAY); + + GL20.glDisableVertexAttribArray(a_p0); + GL20.glDisableVertexAttribArray(a_p1); + GL20.glDisableVertexAttribArray(a_p2); + GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0); + + ShaderProgram.clear(); + + GL11.glPopAttrib(); + } + + // Collects all bezier curves needed to be drawn in a buffers + // in order to minimize the amount of draw calls done + private int regenerateBuffers() { + int lineCount = 0; + // Collect draw count + for (TechLayer container : containers) { + ArrayList techs = container.getTechnologies(); + for (ITechnology tech : techs) { + lineCount += tech.getChildren() + .size(); + } + } + + // Each line requires a quad which means 6 vertices, which means 6 vec3's, which means 18 floats per face + int vertexCount = lineCount * 6; + FloatBuffer vertexBuffer = BufferUtils.createFloatBuffer(vertexCount * 3); + + // Each vertex requires 3 control points, which is a vec2, which results in 36(!!) floats per face + // Compress floats? + FloatBuffer bezierBuffer = BufferUtils.createFloatBuffer(vertexCount * 6); + + for (TechLayer container : containers) { + ArrayList techs = container.getTechnologies(); + List children = container.getChildren(); + for (int i = 0; i < techs.size(); ++i) { + ITechnology tech = techs.get(i); + IWidget child = children.get(i); + for (ITechnology depTech : tech.getChildren()) { + IWidget depWidget = widgetForTech.get(depTech.getInternalName()); + bufferLineData(child, depWidget, vertexBuffer, bezierBuffer); + } + } + } + + vertexBuffer.flip(); + bezierBuffer.flip(); + + GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo_vID); + GL15.glBufferData(GL15.GL_ARRAY_BUFFER, vertexBuffer, GL15.GL_DYNAMIC_DRAW); + + GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo_pID); + GL15.glBufferData(GL15.GL_ARRAY_BUFFER, bezierBuffer, GL15.GL_DYNAMIC_DRAW); + GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0); + + return vertexCount; + } + + private void bufferLineData(IWidget child, IWidget depWidget, FloatBuffer vertexBuffer, FloatBuffer bezierPoints) { + Pos2d start = getWidgetCenterRight(child, 2); + Pos2d end = getWidgetCenterLeft(depWidget, 2); + + float[] quad = getQuad(start, end); + + vertexBuffer.put(quad); // Adds 18 + + float[] vertexBezier1 = { start.x, start.y, (start.x * 3 + end.x) / 4f, start.y, // a_p1 + (start.x + end.x) / 2f, (start.y + end.y) / 2f }; + bezierPoints.put(vertexBezier1); + bezierPoints.put(vertexBezier1); + bezierPoints.put(vertexBezier1); + + float[] vertexBezier2 = { (start.x + end.x) / 2f, (start.y + end.y) / 2f, (start.x + end.x * 3) / 4f, end.y, + end.x, end.y, }; + bezierPoints.put(vertexBezier2); + bezierPoints.put(vertexBezier2); + bezierPoints.put(vertexBezier2); + } + + private static float @NotNull [] getQuad(Pos2d start, Pos2d end) { + float dx = 4; + float dy = 4; + + float p0x = start.x - dx; + float p0y = Math.min(start.y, end.y) - dy; + float p1x = end.x + dx; + float p1y = Math.min(start.y, end.y) - dy; + float p2x = end.x + dx; + float p2y = Math.max(start.y, end.y) + dy; + float p3x = start.x - dx; + float p3y = Math.max(start.y, end.y) + dy; + + float[] quad; + if (start.y > end.y) { + quad = new float[] { p0x, p0y, 0, p2x, p2y, 0, p3x, p3y, 0, // Top left tri + p0x, p0y, 0, p1x, p1y, 0, p2x, p2y, 0, // Bottom right tri + }; + } else { + quad = new float[] { p0x, p0y, 0, p1x, p1y, 0, p3x, p3y, 0, // Bottom left tri + p1x, p1y, 0, p2x, p2y, 0, p3x, p3y, 0, // Top right tri + }; + } + return quad; + } + +} diff --git a/src/main/java/gregtech/common/misc/techtree/interfaces/IPrerequisite.java b/src/main/java/gregtech/common/misc/techtree/interfaces/IPrerequisite.java new file mode 100644 index 00000000000..a77e0a95cd4 --- /dev/null +++ b/src/main/java/gregtech/common/misc/techtree/interfaces/IPrerequisite.java @@ -0,0 +1,27 @@ +package gregtech.common.misc.techtree.interfaces; + +import java.util.UUID; + +/** + * Represents a prerequisite for a technology to be researchable. Typically, this is another technology, + * but it could also be something completely different such as a space project being built. + */ +public interface IPrerequisite { + + /** + * Check if this prerequisite is satisfied for the player with given UUID + * + * @return True if it was satisfied, false otherwise. + */ + boolean isSatisfied(UUID playerUuid); + + /** + * Get the technology associated with this prerequisite technology. If this is not null, this technology + * will be used to draw dependencies in the technology tree. + * + * @return If this prerequisite is linked to a technology, return it. Otherwise, return null. + */ + default ITechnology getTechnology() { + return null; + } +} diff --git a/src/main/java/gregtech/common/misc/techtree/interfaces/ITechnology.java b/src/main/java/gregtech/common/misc/techtree/interfaces/ITechnology.java new file mode 100644 index 00000000000..ac5ac1dc9ba --- /dev/null +++ b/src/main/java/gregtech/common/misc/techtree/interfaces/ITechnology.java @@ -0,0 +1,51 @@ +package gregtech.common.misc.techtree.interfaces; + +import java.util.Collection; + +/** + * Represents + * + * @author NotAPenguin0 + */ +public interface ITechnology { + + /** + * @return The internal name of the technology + */ + String getInternalName(); + + /** + * @return The unlocalized name of the technology + */ + String getUnlocalizedName(); + + /** + * @return The localized name of the technology + */ + String getLocalizedName(); + + /** + * Adds a technology as a child technology of this technology. Note that this should NEVER be called without making + * sure the reverse relation is also satisfied. Essentially, do not call this outside of the Technology constructor. + * + * @param tech The technology to add as a child + */ + void addChildTechnology(ITechnology tech); + + /** + * @return The depth of this technology in the technology tree. + * Zero means this technology has no ancestors. + */ + int getDepth(); + + /** + * @return A collection of required prerequisites for this technology to be researchable + */ + Collection getPrerequisites(); + + /** + * @return A collection of child technologies of this technology. All these child technologies + * require this technology (and possibly more) to unlock. + */ + Collection getChildren(); +} diff --git a/src/main/java/gregtech/loaders/preload/GT_Loader_Item_Block_And_Fluid.java b/src/main/java/gregtech/loaders/preload/GT_Loader_Item_Block_And_Fluid.java index f4c682d8481..d3b61f82321 100644 --- a/src/main/java/gregtech/loaders/preload/GT_Loader_Item_Block_And_Fluid.java +++ b/src/main/java/gregtech/loaders/preload/GT_Loader_Item_Block_And_Fluid.java @@ -89,6 +89,7 @@ import gregtech.common.items.GT_TierDrone; import gregtech.common.items.GT_VolumetricFlask; import gregtech.common.items.GT_WirelessHeadphones; +import gregtech.common.items.ItemTechAccessor; import gregtech.common.tileentities.render.TileDrone; import gregtech.common.tileentities.render.TileLaser; import gregtech.common.tileentities.render.TileWormhole; @@ -537,6 +538,8 @@ public void run() { GT_ModHandler.getIC2Item("reactorDepletedMOXQuad", 1), true)); + ItemList.TechTreeAccessor.set(new ItemTechAccessor()); + GT_Log.out.println("GT_Mod: Adding Blocks."); GregTech_API.sBlockMachines = new GT_Block_Machines(); GregTech_API.sBlockCasings1 = new GT_Block_Casings1(); diff --git a/src/main/java/gregtech/loaders/preload/GT_Loader_Technologies.java b/src/main/java/gregtech/loaders/preload/GT_Loader_Technologies.java new file mode 100644 index 00000000000..3ccae31649e --- /dev/null +++ b/src/main/java/gregtech/loaders/preload/GT_Loader_Technologies.java @@ -0,0 +1,52 @@ +package gregtech.loaders.preload; + +import gregtech.GT_Mod; +import gregtech.common.misc.techtree.TechList; +import gregtech.common.misc.techtree.TechnologyRegistry; +import gregtech.common.misc.techtree.base.Technology; +import gregtech.common.misc.techtree.base.TechnologyBuilder; +import gregtech.common.misc.techtree.gui.TechTreeLayout; +import gregtech.common.misc.techtree.interfaces.IPrerequisite; + +public class GT_Loader_Technologies implements Runnable { + + @Override + public void run() { + GT_Mod.GT_FML_LOGGER.info("Loading researchable technologies"); + + // Construct with constructor manually because we don't want to assign itself as a prerequisite and mess + // everything up + TechList.HiddenRoot = new Technology("Root", "gt.tech.root.name", new IPrerequisite[] {}); + TechnologyRegistry.register(TechList.HiddenRoot); + + TechList.TechA = new TechnologyBuilder("TechA").unlocalizedName("gt.tech.a.name") + .buildAndRegister(); + + TechList.TechB = new TechnologyBuilder("TechB").unlocalizedName("gt.tech.b.name") + .buildAndRegister(); + + TechList.TechC = new TechnologyBuilder("TechC").unlocalizedName("gt.tech.c.name") + .buildAndRegister(); + TechList.TechD = new TechnologyBuilder("TechD").unlocalizedName("gt.tech.d.name") + .buildAndRegister(); + TechList.TechE = new TechnologyBuilder("TechE").unlocalizedName("gt.tech.e.name") + .prerequisites(TechList.TechB, TechList.TechC) + .buildAndRegister(); + /* + * TechList.TechF = new TechnologyBuilder("TechF").unlocalizedName("gt.tech.f.name") + * .prerequisites(TechList.TechA, TechList.TechD) + * .buildAndRegister(); + */ + TechList.TechG = new TechnologyBuilder("TechG").unlocalizedName("gt.tech.g.name") + .prerequisites(TechList.TechC) + .buildAndRegister(); + + int numTechnologies = TechnologyRegistry.numTechnologies(); + GT_Mod.GT_FML_LOGGER.info("Constructing tech tree layout"); + long start = System.currentTimeMillis(); + TechTreeLayout.constructOrGet(); + long end = System.currentTimeMillis(); + GT_Mod.GT_FML_LOGGER.info(String.format("Loaded %d technologies.", numTechnologies)); + GT_Mod.GT_FML_LOGGER.info(String.format("Constructing tech tree layout took %d ms.", end - start)); + } +} diff --git a/src/main/java/gtPlusPlus/core/handler/PacketHandler.java b/src/main/java/gtPlusPlus/core/handler/PacketHandler.java index af6a376f1a7..6d49a2d514a 100644 --- a/src/main/java/gtPlusPlus/core/handler/PacketHandler.java +++ b/src/main/java/gtPlusPlus/core/handler/PacketHandler.java @@ -9,6 +9,7 @@ import cpw.mods.fml.common.network.simpleimpl.IMessage; import cpw.mods.fml.common.network.simpleimpl.SimpleNetworkWrapper; import cpw.mods.fml.relauncher.Side; +import gregtech.api.net.PacketOpenTechTree; import gtPlusPlus.api.objects.Logger; import gtPlusPlus.core.network.handler.AbstractClientMessageHandler; import gtPlusPlus.core.network.packet.AbstractPacket; @@ -25,6 +26,8 @@ public class PacketHandler { public static final void init() { registerMessage(Packet_VolumetricFlaskGui.class, Packet_VolumetricFlaskGui.class); registerMessage(Packet_VolumetricFlaskGui2.class, Packet_VolumetricFlaskGui2.class); + // GT message because yes + registerMessage(PacketOpenTechTree.class, PacketOpenTechTree.class); } /** diff --git a/src/main/resources/assets/gregtech/lang/en_US.lang b/src/main/resources/assets/gregtech/lang/en_US.lang index c981c982bb4..da23f214143 100644 --- a/src/main/resources/assets/gregtech/lang/en_US.lang +++ b/src/main/resources/assets/gregtech/lang/en_US.lang @@ -1602,6 +1602,17 @@ gt.time.second.plural=seconds #Tool modes gt.wrench.mode.1=Line Mode +#Researchable technologies +gt.tech.a.name=Tech A +gt.tech.b.name=Tech B +gt.tech.c.name=Tech C +gt.tech.d.name=Tech D +gt.tech.e.name=Tech E +gt.tech.f.name=Tech F +gt.tech.g.name=Tech G + +#Tech tree gui +gt.gui.techtree.title=Technology Tree # GT++ # Shit I stole from GTNH~ diff --git a/src/main/resources/assets/gregtech/shaders/techTreeArrow.frag.glsl b/src/main/resources/assets/gregtech/shaders/techTreeArrow.frag.glsl new file mode 100644 index 00000000000..a6811922cd7 --- /dev/null +++ b/src/main/resources/assets/gregtech/shaders/techTreeArrow.frag.glsl @@ -0,0 +1,80 @@ +#version 120 + +varying vec2 v_p0; +varying vec2 v_p1; +varying vec2 v_p2; +uniform float u_dist; + +vec2 quadraticBezier(float t) { + return (1.0 - t) * (1.0 - t) * v_p0 + + 2.0 * (1.0 - t) * t * v_p1 + + t * t * v_p2; +} +vec2 quadraticBezierDerivative(float t){ + return 2*(v_p1-v_p0)+2*(v_p0-2*v_p1+v_p2)*t; +} + +vec2 quadraticBezierSecondDerivative(float t){ + return 2*(v_p0-2*v_p1+v_p2); +} + +float distanceToBezier(vec2 p, float t) { + vec2 bezierPoint = quadraticBezier(t); + return length(bezierPoint - p); +} + +float distanceToBezierSquared(float t,vec2 p){ + vec2 B = quadraticBezier(t); + vec2 pB2 = B-p; + return dot(pB2,pB2); +} + +float distanceToBezierSquaredDerivative(float t,vec2 p){ + return 2*dot(quadraticBezierDerivative(t),(quadraticBezier(t) - p)); +} + +float distanceToBezierSquaredSecondDerivative(float t, vec2 p){ + vec2 d1t = quadraticBezierDerivative(t); + vec2 d2t = quadraticBezierSecondDerivative(t); + return 2*(dot(d2t,(quadraticBezier(t) - p)) + dot(d1t,d1t)); +} + +float findClosestT(vec2 p){ + float eps = .001; + int iteration = 0; + int maxIterations = 4; //Two looks presentable, 3 seems good, 4 for good measure + float t = 0.5; //Initial guess + + // Newton-Raphson Method + // Minimizes the distance squared insteadof the distance for numerical stability + while (iteration < maxIterations) { + float dt = distanceToBezierSquaredDerivative(t, p); + float dtt = distanceToBezierSquaredSecondDerivative(t, p); + + if (abs(dt) < eps) { + break; + } + + float deltaT = dt / dtt; + t -= deltaT; + + if (abs(deltaT) < eps) { + break; + } + iteration++; + } + return t; +} + +void main() { + vec2 p = gl_FragCoord.xy; + float t = findClosestT(p); + float dist = distanceToBezier(p, t); + + float lazyAlias = smoothstep(0, 1, (u_dist-dist+1)/(u_dist+1)); + + if (dist < u_dist) + gl_FragColor = vec4(1,1,1,lazyAlias); + else + gl_FragColor = vec4(0,0,0,0); +} diff --git a/src/main/resources/assets/gregtech/shaders/techTreeArrow.vert.glsl b/src/main/resources/assets/gregtech/shaders/techTreeArrow.vert.glsl new file mode 100644 index 00000000000..16aac3611a6 --- /dev/null +++ b/src/main/resources/assets/gregtech/shaders/techTreeArrow.vert.glsl @@ -0,0 +1,31 @@ +#version 120 + +uniform vec2 u_ScreenSize; + +//Control pointers for the bezier curve +attribute vec2 a_p0; +attribute vec2 a_p1; +attribute vec2 a_p2; + +uniform vec2 u_p0; + +varying vec2 v_p0; +varying vec2 v_p1; +varying vec2 v_p2; + + +vec2 fragmentCoord(vec2 v){ + vec4 cs = gl_ModelViewProjectionMatrix * vec4(v, 0, 1); + vec2 ncd = cs.xy/cs.w; + vec2 ws = (ncd * 0.5 + 0.5) * u_ScreenSize; + return ws; +} + + +void main() { + v_p0 = fragmentCoord(a_p0); + v_p1 = fragmentCoord(a_p1); + v_p2 = fragmentCoord(a_p2); + + gl_Position = gl_ModelViewProjectionMatrix*gl_Vertex; +}