public List<Plugin> loadPlugins(List<Class<?>> plugins, BiConsumer<Integer, Integer> onPluginLoaded) throws PluginInstantiationException
	{
		MutableGraph<Class<? extends Plugin>> graph = GraphBuilder
			.directed()
			.build();

		for (Class<?> clazz : plugins)
		{
			PluginDescriptor pluginDescriptor = clazz.getAnnotation(PluginDescriptor.class);

			if (pluginDescriptor == null)
			{
				if (clazz.getSuperclass() == Plugin.class)
				{
					log.error("Class {} is a plugin, but has no plugin descriptor", clazz);
				}
				continue;
			}

			if (clazz.getSuperclass() != Plugin.class)
			{
				log.error("Class {} has plugin descriptor, but is not a plugin", clazz);
				continue;
			}

			if (!pluginDescriptor.loadWhenOutdated() && isOutdated)
			{
				continue;
			}

			if (pluginDescriptor.developerPlugin() && !developerMode)
			{
				continue;
			}

			if (safeMode && !pluginDescriptor.loadInSafeMode())
			{
				log.debug("Disabling {} due to safe mode", clazz);
				// also disable the plugin from autostarting later
				configManager.unsetConfiguration(RuneLiteConfig.GROUP_NAME,
					(Strings.isNullOrEmpty(pluginDescriptor.configName()) ? clazz.getSimpleName() : pluginDescriptor.configName()).toLowerCase());
				continue;
			}

			graph.addNode((Class<Plugin>) clazz);
		}

		// Build plugin graph
		for (Class<? extends Plugin> pluginClazz : graph.nodes())
		{
			PluginDependency[] pluginDependencies = pluginClazz.getAnnotationsByType(PluginDependency.class);

			for (PluginDependency pluginDependency : pluginDependencies)
			{
				if (graph.nodes().contains(pluginDependency.value()))
				{
					graph.putEdge(pluginDependency.value(), pluginClazz);
				}
			}
		}

		if (Graphs.hasCycle(graph))
		{
			throw new PluginInstantiationException("Plugin dependency graph contains a cycle!");
		}

		List<Class<? extends Plugin>> sortedPlugins = topologicalSort(graph);

		int loaded = 0;
		List<Plugin> newPlugins = new ArrayList<>();
		for (Class<? extends Plugin> pluginClazz : sortedPlugins)
		{
			Plugin plugin;
			try
			{
				plugin = instantiate(this.plugins, (Class<Plugin>) pluginClazz);
				newPlugins.add(plugin);
				this.plugins.add(plugin);
			}
			catch (PluginInstantiationException ex)
			{
				log.error("Error instantiating plugin!", ex);
			}

			loaded++;
			if (onPluginLoaded != null)
			{
				onPluginLoaded.accept(loaded, sortedPlugins.size());
			}
		}

		return newPlugins;
	}
@Test
	public void testLoadPlugins() throws Exception
	{
		PluginManager pluginManager = new PluginManager(false, false, null, null, null, null);
		pluginManager.setOutdated(true);
		pluginManager.loadCorePlugins();
		Collection<Plugin> plugins = pluginManager.getPlugins();
		long expected = pluginClasses.stream()
			.map(cl -> cl.getAnnotation(PluginDescriptor.class))
			.filter(Objects::nonNull)
			.filter(PluginDescriptor::loadWhenOutdated)
			.count();
		assertEquals(expected, plugins.size());

		pluginManager = new PluginManager(false, false, null, null, null, null);
		pluginManager.loadCorePlugins();
		plugins = pluginManager.getPlugins();

		// Check that the plugins register with the eventbus without errors
		EventBus eventBus = new EventBus();
		plugins.forEach(eventBus::register);

		expected = pluginClasses.stream()
			.map(cl -> cl.getAnnotation(PluginDescriptor.class))
			.filter(Objects::nonNull)
			.filter(pd -> !pd.developerPlugin())
			.count();
		assertEquals(expected, plugins.size());
	}