Créer une API (ajout de recette)


  • Rédacteurs

    Sommaire

    • Introduction

    • Pré-requis

    • Code

      Introduction

      Aujourd'hui nous allons apprendre à créer une API (Application Programming Interface) ou Interface de programmation en français (source).
      La problématique est la suivante, imaginez que vous aillez un mod, il contient une machine quelconque possédant des recettes que vous avez préalablement établie dans
      votre code source. Mais vous aimeriez qu'un autre moddeur puisse ajouter lui aussi des recettes à votre machine sans modifier votre mod.
      La solution, vous la devinez et de créer une API afin qu'il puisse ajouter des recettes.

      Pré-requis

      Code

      Création de toute les packages et toutes les classes

      Dans un premier temps nous allons créer un enum nommée TutoApi qui se trouvera dans le dossier src/api/java
      Pour ce faire sélectionnez votre projet Eclipse contenant votre mod, clique droit -> New -> Source Folder
      Dans folder name tapez : src/api/java, validez. Créez maintenant un package avec le nom que vous voulez.
      Clique droit -> New -> Package et entrez le nom, validez.
      Dans ce package créez un enum appelé TutoApi. Dans le même package créez une interface que vous appelerez ITutoApi.
      Créez un sous-package nommé "features" dans lequel vous créez 3 interfaces :

      • IMachineTutoRecipe
      • IMachineTutoRegistry
      • IRegistryRecipes

      Ensuite dans le package "common" de votre mod (dans le dossier "src/main/java") créez un package "core".
      Dans "core" créez une classe 'Api' et un package 'features'.
      Dans "features" créez une classe 'MachineTutoRecipe' et un package 'registries'
      Dans "registries" créez une classe 'RegistryRecipes' et une classe 'RegistryMachineTuto'

      Vous devriez avoir créé 9 fichier java.

      L'enum TutoApi

      Nom du fichier : TutoApi
      Ce fichier dans le dossier : src/api/java/
      Dans le package : modid.api
      Nous avons créer un enum car nous n'avons pas besoin d'instance.

      
      public enum TutoApi {
      
      	; //Aucun champs, il FAUT mettre le point-virgule
      
      	/** Chemin vers la classe Api */
      	private static final String API_CLASS_PATH = "prefix.common.core.Api";
      	/** Nom du champ contenant l'instance de la classe Api */
      	private static final String INSTANCE_FIELD = "INSTANCE";
      	/** //Instance de la classe Api */
      	private static ITutoApi API;
      
      	/**
      	*	Utilisation de la réflection pour récupérer l'instance de la classe Api
      	*/
      	static {
      		try {
      			final Class apiClass = Class.forName(API_CLASS_PATH);
      			final Field instanceField = apiClass.getDeclaredField(INSTANCE_FIELD);
      			API = (ITutoApi)(instanceField.get(apiClass));
      		} catch (final ClassNotFoundException e) {
      			e.printStackTrace();
      		} catch (final NoSuchFieldException e) {
      			e.printStackTrace();
      		} catch (final IllegalAccessException e) {
      			e.printStackTrace();
      		}
      	}
      
      	/**
      	*	@return L'instance de la classe Api
      	*/
      	public static ITutoApi instance() {
      		return API;
      	}
      }
      
      

      Voilà, il n'y a pas grand-chose à expliquer, on utilise la réflection pour obtenir l'instance de la classe Api

      Les interfaces de l'Api

      ITutoApi
      Dossier : src/api/java
      Package : modid.api

      Cette interface permet de récupérer le registre contenant toutes les recettes que nous mettons à disposition via l'API

      
      public interface ITutoApi {
      
      	/**
      	 * @return Le registre contenant toute les recettes
      	 */
      	public IRegistryRecipes recipes();
      
      }
      
      

      IRegistryRecipes
      Dossier : src/api/java
      Package : modid.api.features

      Cette interface permet de récupérer le registre contenant toute les recettes d'une certaines machine

      
      public interface IRegistryRecipes {
      
      	/**
      	* @return Le registre des recettes du bloc MachineTuto
      	*/
      	public IMachineTutoRegistry machineTuto();
      
      }
      
      

      IMachineTutoRegistry
      Dossier : src/api/java
      Package : modid.api.features

      Cette interface permet de récupérer de manipuler les recettes et le carburant du bloc MachineTuto

      
      public interface IMachineTutoRegistry {
      
      	/**
      	 * @return Une liste contenant les recettes du bloc MachineTuto
      	 */
      	public List <imachinetutorecipe>getRecipes();
      
      	/**
      	 * Permet d'ajouter une recette à la liste de recette
      	 * @param ItemStacks d'inputs
      	 * @param ItemStacks d'output
      	 */
      	public void addRecipe(ItemStack[] in, ItemStack[] out);
      
      	/**
      	 * Permet de récupérer la recette pour un input donné
      	 * @param ItemStacks d'input de la recette cherchée
      	 * @return
      	 */
      	public IMachineTutoRecipe getRecipeForInput(ItemStack[] in);
      
      	/**
      	 * Permet d'ajouter un carburant à la machine
      	 * @param Le carburant
      	 * @param La durée du carburant
      	 */
      	public void addFuel(ItemStack stack, Integer duration);
      
      	/**
      	 * @param Un carburant
      	 * @return La durée du carburant
      	 */
      	public int getFuelDuration(ItemStack stack);
      
      }
      
      

      IMachineTutoRecipe
      Dossier : src/api/java
      Package : modid.api.features

      Cette interface représente une recette du bloc MachineTuto

      
      public interface IMachineTutoRecipe {
      
      	/**
      	 * @return ItemStacks d'input de la recette
      	 */
      	public ItemStack[] getInput();
      
      	/**
      	 * Permet de modifier les ItemStacks d'input de la recette
      	 * @param ItemStacks d'input de la recette
      	 */
      	public void setInput(ItemStack[] input);
      
      	/**
      	 * @return ItemStacks d'output de la recette
      	 */
      	public ItemStack[] getOutput();
      
      	/**
      	 * Permet de modifier les ItemStacks d'output de la recette
      	 * @param ItemStacks d'output de la recette
      	 */
      	public void setOutput(ItemStack[] output);
      
      }
      
      

      Comme vous pouvez le voir grâce à ces interfaces le moddeur peut facilement ajouter ou modifier une recette sans réécrire le mod.

      Les classes de l'Api

      Api
      Dossier : src/main/java
      Package : prefix.common.core

      Cette classe est celle dont on a spécifié le chemin dans l'interface TutoApi, elle implémente l'interface ITutoApi

      
      public class Api implements ITutoApi {
      
      	/**
      	 * Instance de l'Api qui est récupéré par réflexion par l'enum TutoApi
      	 * "INSTANCE" est le champs qui est renseigné dans le second champs de la classe TutoApi
      	 */
      	public static final Api INSTANCE = new Api();
      
      	/**
      	 * Instance privé de l'interface IRegistryRecipes
      	 */
      	private IRegistryRecipes registryRecipes;
      
      	/**
      	 * Instanciation de l'interface IRegistryRecipes via la classe RegistryRecipes
      	 */
      	private Api() {
      		registryRecipes = new RegistryRecipes();
      	}
      
      	@Override
      	public IRegistryRecipes recipes() {
      		return registryRecipes;
      	}
      
      }
      
      

      RegistryRecipes
      Dossier : src/main/java
      Package : prefix.common.core.features.registries

      Cette classe implemente l'interface IRegistryRecipes

      
      public class RegistryRecipes implements IRegistryRecipes {
      
      	/**
      	 * Instance du registre du bloc MachineTuto
      	 */
      	private IMachineTutoRegistry machineTuto;;
      
      	/**
      	* Instanciation de la variable dans le constructeur
      	*/
      	public RegistryRecipes() {
      		machineTuto = new RegistryMachineTuto();
      	}
      
      	@Override
      	public IMachineTutoRegistry machineTuto() {
      		return machineTuto;
      	}
      
      }
      
      

      RegistryMachineTuto
      Dossier : src/main/java
      Package : prefix.common.core.features.registries

      Cette classe contient les recettes du bloc MachineTuto et la liste des fuels, elle implémente l'interface IMachineTutoRegistry

      
      public class RegistryMachineTuto implements IMachineTutoRegistry {
      
      	/** Mapping des carburant avec leur durée */
      	private Map <itemstack, integer="">fuelToTime = new HashMap<itemstack, integer="">();
      	/** List de recettes */
      	private List <imachinetutorecipe>recipes;
      
      	/**
      	 * Ajout des recettes et des carburants de base
      	 */
      	public RegistryMachineTuto()
      	{
      		this.recipes = new ArrayList<imachinetutorecipe>();
      		this.addFuel(new ItemStack(Items.blaze_powder), 5000);
      		this.addRecipe(new ItemStack[] {
      				new ItemStack(Items.apple),
      				new ItemStack(Items.apple),
      				new ItemStack(Items.apple),
      				new ItemStack(Items.apple)
      		}, new ItemStack[] {
      				new ItemStack(Items.apple),
      				new ItemStack(Items.apple)
      		});
      	}
      
      	@Override
      	public int getFuelDuration(ItemStack fuel)
      	{
      		Iterator<entry<itemstack, integer="">> iterator = this.fuelToTime.entrySet().iterator();
      		Entry <itemstack, integer="">entry;
      		do
      		{
      			if(!iterator.hasNext())
      			{
      				return 0;
      			}
      			entry = iterator.next();
      		}
      		while(fuel.getItem() != entry.getKey().getItem() || fuel.getItemDamage() != entry.getKey().getItemDamage() );
      
      		return entry.getValue();
      	}
      
      	@Override
      	public void addFuel(ItemStack fuel, Integer time)
      	{
      		fuelToTime.put(fuel, time);
      	}
      
      	@Override
      	public void addRecipe(ItemStack[] input, ItemStack[] output)
      	{
      		if(this.getRecipeForInput(input) == null);
      		recipes.add(new MachineTutoRecipe(input, output));
      	}
      
      	@Override
      	public List <imachinetutorecipe>getRecipes() {
      		return recipes;
      	}
      
      	@Override
      	public IMachineTutoRecipe getRecipeForInput(ItemStack[] in) {
      		if(in == null) return null;
      		if(in.length != TileEntityMachineTuto.INPUTS_SLOTS) return null;
      		for(IMachineTutoRecipe recipe : recipes) {
      			boolean recipeOK = true;
      			ItemStack[] inputs = recipe.getInput();
      			for(int i = 0; i < in.length; i++) {
      				if(!ItemStack.areItemsEqual(in*, inputs*)) recipeOK = false;
      			}
      			if(recipeOK) return recipe;
      		}
      		return null;
      	}
      }
      
      

      MachineTutoRecipe
      Dossier : src/main/java
      Package : prefix.common.core.features

      Cette classe est la classe d'une recette elle implémente l'interface IMachineTutoRecipe

      
      public class MachineTutoRecipe implements IMachineTutoRecipe {
      
      	/** ItemStacks d'input */
      	private ItemStack[] inputs;
      	/** ItemStacks d'ouput */
      	private ItemStack[] outputs;
      
      	public MachineTutoRecipe(ItemStack[] in, ItemStack[] out) {
      		inputs = in;
      		outputs = out;
      	}
      
      	@Override
      	public ItemStack[] getInput() {
      		return inputs;
      	}
      
      	@Override
      	public void setInput(ItemStack[] input) {
      		inputs = input;
      	}
      
      	@Override
      	public ItemStack[] getOutput() {
      		return outputs;
      	}
      
      	@Override
      	public void setOutput(ItemStack[] output) {
      		outputs = output;
      	}
      
      }
      
      

      Et voilà, c'est tout.

      Utilisation de l'Api

      Maintenant que tout est bon, il ne reste plus qu'à savoir s'en servir.
      Premièrement, lorsqu'un mod veut utiliser votre API il lui suffit d'ajouter à son buildpath tout ce qui se trouve dans src/api/java
      Ensuite quand il veut utiliser votre API il faut qu'il vérifie si votre mod est bien load

      
      Loader.isModLoaded(modid de votre mod) {
      	//Utilisation de votre API
      }
      
      

      Exemple de d'utilisation :

      
      Loader.isModLoaded(modid de votre mod) {
      	TutoApi.instance().recipes().machineTuto().addFuel(new ItemStack(Items.bone), 50);
      }
      
      

      Ce code ajoute comme carburant une os avec comme durée 50.
      C'est aussi simple que cela.



  • Créer une API est une bonne idée pour organiser son mod !

    En revanche précise que c'est une API pour les recettes car une API c'est très général.


  • Rédacteurs

    Ici s'en est une pour les recettes parce qu'il fallait un exemple mais si comme tu le dit c'est général une API si on a compris le principe c'est simple de faire autre chose que rajouter des recettes. J'avoue que je ne suis pas encore super bon pour les API j'ai fait ça aujourd'hui en essayant de voir comment ça marche avec Applied donc si il y a des erreurs où autres n'hésite vraiment pas



  • A vrai dire, une API est différente selon les personnes, chez certains c'est plus complet, chez d'autres c'est plus simple, mais le principal c'est de tout expliquer pour que tout le monde le change un peu à sa manière et de ce que je vois (j'ai pas tout lu) c'est bien commenté


  • Rédacteurs

    Ce qui fait bizarre, selon moi c'est que je me suis inspiré de l'API d'Applied qui est complète alors que dans le tutoriel je ne rajoute que le fait d'ajouter des recettes à un bloc ce qui fait qu'on a l'impression de brasser de l'air pour pas grand-chose



  • L'organisation c'est bien, dans mon mod CSP j'ai fait une bonne API pour mes blocks ce qui me permet de créer un block avec model et système d'énergie integré avec seulement quelques lignes : https://github.com/SCAREXgaming/CSP/blob/master/src/main/java/fr/scarex/csp/block/SolarPanelFrame.java