Skip to content

Commit 26c92c3

Browse files
authored
Page sur les Capabilities (Les-Moddeurs-Francais#73)
* Capabilities page(may need a rework) * Fixing some typos * PlayerEntity -> Player * Meilleure explication du système de "clé" * Transformation des noms de classe en `code` pour une meilleure visibilité * Oups j'en avais oublié * CapabilityManager#get -> `CapabilityManager#get`
1 parent a906a6f commit 26c92c3

File tree

1 file changed

+291
-0
lines changed

1 file changed

+291
-0
lines changed

docs/advanced/capabilities.md

+291
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
---
2+
sidebar_position: 3
3+
title: Capabilities
4+
description: Comment utiliser les capabilities ?
5+
tags: [avancé]
6+
---
7+
8+
Les `capabilities` sont un système mis à disposition par Forge permettant de stocker des données sur des **BlockEntities**(TileEntities), des **Entities**, des **ItemStacks**, des **Levels**(Worlds) et des **LevelChunks**(Chunks).
9+
10+
## Utiliser une Capability
11+
12+
Forge fournit par défaut trois capabilities : `IItemHandler`, qui permet de stocker des items, `IFluidHandler`, qui permet de stocker des liquides et enfin `IEnergyStorage`, qui permet de stocker de l'énergie.
13+
14+
Une capability possède au minimum normalement trois classes : l'interface(Exemple : `IItemHandler`), l'(les) implémentation(s) par défaut de la capability(Exemple : `ItemStackHandler`) et enfin la classe qui contient l'instance de la capability et qui sert à l'enregistrer(Exemple : `CapabilityItemHandler`).
15+
16+
Pour les utiliser, il faut d'abord les attacher à la `BlockEntity`/`Entity`/`ItemStack`/`Level`/`LevelChunk` de votre choix.
17+
### Attacher une Capability
18+
19+
#### Récupérer l'instance d'une capability
20+
21+
Pour attacher une capability, il faut déjà posséder son unique instance. Pour cela, vous pouvez l'obtenir soit dans la classe qui la contient par défaut, soit en obtenant une autre référence de la même instance en utilisant `CapabilityManager#get` comme ceci:
22+
```java
23+
static Capability<VotreInterface> VOTRE_CAPABILITY = CapabilityManager.get(new CapabilityToken<>(){});
24+
```
25+
Où VotreInterface est l'interface de votre capability et VOTRE_CAPABILITY est le nom que vous voulez donner à votre variable(appelez-la comme vous voulez)
26+
27+
Exemple :
28+
```java
29+
static Capability<IEnergyStorage> ENERGY_STORAGE = CapabilityManager.get(new CapabilityToken<>(){});
30+
```
31+
#### Attacher une Capability
32+
33+
Pour attacher une Capability, il faut passer par l'évènement `AttachCapabilitiesEvent`:
34+
* `AttachCapabilitiesEvent<Entity>` pour les `Entity`
35+
* `AttachCapabilitiesEvent<BlockEntity>` pour les `BlockEntity`
36+
* `AttachCapabilitiesEvent<ItemStack>` pour les `ItemStack`
37+
* `AttachCapabilitiesEvent<Level>` pour les `Level`
38+
* `AttachCapabilitiesEvent<LevelChunk>` pour les `LevelChunk`
39+
40+
:::caution
41+
Il n'existe d'évènement que pour ces cinq-là. Par exemple, si vous voulez attacher une Capability à un joueur spécifiquement, `AttachCapabilitiesEvent<Player>` ne marchera pas. À la place, il faut utiliser `AttachCapabilitiesEvent<Entity>` et vérifier si `AttachCapabilitiesEvent#getObject`(l'entité) est une instance de Player.
42+
:::caution
43+
Vous devrez avoir une implémentation de votre capability(utilisez celle par défaut ou créez la vôtre, voir [ici](#les-implémentations-de-linterface-de-votre-capability)).
44+
45+
Il vous faudra également une `ResourceLocation` qui sera la "clé" de votre capability et qui sera utilisée pour éviter que la même capability soit ajoutée deux fois ou que d'autres erreurs du style se produisent.
46+
47+
:::tip
48+
Votre clé peut être n'importe quelle `ResourceLocation`, mais elle doit être unique.
49+
50+
Vous pouvez, par exemple, créer une `ResourceLocation` à partir de votre modid et du nom de la Capability que vous ajoutez comme ceci :
51+
52+
```java
53+
ResourceLocation VOTRE_CLE = new ResourceLocation(VOTRE_MODID, NOM_DE_LA_CAPABILITY)
54+
```
55+
:::tip
56+
57+
Pour finir, il vous faudra une implémentation de `ICapabilityProvider` qui retourne avec la fonction `getCapability` un `LazyOptional` de la capability(un Provider)
58+
59+
Exemple :
60+
61+
```java
62+
public class EnergyStorageProvider implements ICapabilityProvider {
63+
private final LazyOptional<IEnergyStorage> energyStorageOptional;
64+
65+
public EnergyStorageProvider(){
66+
this.energyStorageOptional = LazyOptional.of(() -> new EnergyStorage(10000)); // Remplacez le new EnergyStorage() par votre implémentation de l'interface de la capability
67+
}
68+
69+
@Nonnull
70+
@Override
71+
public <T> LazyOptional<T> getCapability(@Nonnull Capability<T> cap, @Nullable Direction side) {
72+
return CapabilityEnergy.ENERGY.orEmpty(cap, this.energyStorageOptional);
73+
}
74+
75+
}
76+
```
77+
Pensez bien à remplacer `IEnergyStorage` par l'interface de votre capability, `new EnergyStorage()` par l'implémentation de votre capability et `CapabilityEnergy.ENERGY` par l'unique instance de la capability(regardez [ici](#récupérer-linstance-dune-capability) pour savoir comment l'obtenir)
78+
79+
Exemples :
80+
81+
Attacher la Capability `IEnergyStorage` avec le Provider fait plus haut à tous les `LevelChunk` :
82+
```java
83+
@SubscribeEvent
84+
public static void attachToChunks(AttachCapabilitiesEvent<LevelChunks> event)
85+
{
86+
event.addCapability(VOTRE_CLE, new EnergyStorageProvider());
87+
}
88+
```
89+
Attacher la Capability `IEnergyStorage` avec le Provider fait plus haut à des `Player` :
90+
```java
91+
@SubscribeEvent
92+
public static void attachToEntities(AttachCapabilitiesEvent<Entity> event)
93+
{
94+
if(event.getObject() instanceof Player){
95+
event.addCapability(VOTRE_CLE, new EnergyStorageProvider());
96+
}
97+
}
98+
```
99+
(pensez bien à remplacer VOTRE_CLE par la `ResourceLocation` servant de clé que vous avez créée plus haut et `new EnergyStorageProvider()` par votre provider)
100+
101+
:::warning
102+
Attacher les Capabilities par défaut de Forge(voir [ici](#utiliser-une-capability)) à des classes vanilla peut causer certains problèmes. Par exemple, attacher un `IItemHandler` à un joueur ne marchera pas, car si vous essayez de le récupérer en passant par le joueur, vous obtiendrez un `IItemHandler` qui correspond à l'inventaire du même joueur. Si vous souhaitez tout de même utiliser les Capabilities de Forge, il faut alors créer une nouvelle Capability qui extend celle que vous souhaitez attacher(voir [ici](#créer-une-capability)).
103+
:::
104+
105+
### Récupérer la Capability
106+
107+
Une fois que la Capability est bien attachée, pour l'utiliser, il faut la récupérer ! Pour cela, reprenons l'exemple de la Capability `IEnergyStorage` attachée à un joueur :
108+
109+
Pour la récupérer, il faut d'abord obtenir la classe sur laquelle vous avez attaché la Capability (une instance de `Player` dans notre cas). Une fois cela fait (je n'explique pas comment faire, car cela dépend de sur quoi vous avez attaché la Capability), il faut utiliser dans notre cas(celui du joueur) :
110+
111+
```java
112+
LazyOptional<IEnergyStorage> energyStorageLazyOptional = player.getCapability(CapabilityEnergy.ENERGY);
113+
```
114+
115+
Encore une fois, remplacez bien `IEnergyStorage` par l'interface de votre Capability, `CapabilityEnergy.ENERGY` par l'instance de votre Capability et nommez la variable comme vous voulez.
116+
117+
Nous avons maintenant un `LazyOptional` de notre Capability.
118+
:::tip
119+
Qu'est-ce qu'un `LazyOptional` ?
120+
121+
C'est une classe créée par Forge et qui est similaire à la classe Optional (tapez java Optional sur google si vous ne savez pas ce que c'est). Si vous voulez en savoir plus, regardez dans la classe elle-même, c'est assez bien documenté.
122+
:::
123+
124+
Maintenant que vous possédez votre `LazyOptional`, vous pouvez faire ce que vous voulez avec.
125+
126+
Vous pouvez utiliser `LazyOptional#isPresent` pour savoir si votre Capability est présente, `LazyOptional#ifPresent` pour exécuter un `Consumer` si la capability est présente, et d'autres comme `LazyOptional#orElse`. Pour plus d'informations, je vous invite à regarder dans la class `LazyOptional`, sur [cette page](https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html) ou à rechercher sur Internet.
127+
128+
Exemples (si l'on utilise la Capability `IEnergyStorage`) :
129+
```java
130+
lazyOptional.ifPresent(cap -> cap.receiveEnergy(500, false));
131+
132+
IEnergyStorage energyStorage = lazyOptional.orElse(new EnergyStorage(10000));
133+
energyStorage.extractEnergy(500, false);
134+
```
135+
136+
### Sauvegarder la Capability
137+
138+
Si vous avez fait quelques tests par vous-mêmes, vous avez sûrement remarqué que la Capability n'est pas sauvegardée : c'est normal.
139+
140+
Pour sauvegarder sa Capability, il faut modifier votre Provider comme ceci :
141+
142+
- Tout d'abord, il faut savoir quel type de données vous voulez sauvegarder et trouver le `Tag`(anciennement `NBT`) correspondant : `IntTag` si vous souhaitez sauvegarder un `int`, `StringTag` pour un `String`, ou encore `CompoundTag` pour stocker différents types de données. Il en existe beaucoup d'autres, je vous invite donc à regarder le package *`net.minecraft.nbt`* pour la liste complète.
143+
- Ensuite, changez votre classe pour implémenter `ICapabilitySerializable<VotreTag>`(remplacez VotreTag par le `Tag` que vous avez choisi) au lieu de `ICapabilityProvider`. Cela devrait générer une erreur, c'est normal.
144+
- Ajoutez la fonction `serializeNBT` qui retourne le `Tag` que vous avez décidé d'utiliser que vous aurez préalablement set avec les données que vous voulez sauvegarder
145+
- Finalement, ajoutez la fonction `deserializeNBT` qui a pour argument le `Tag` que vous avez décidé d'utiliser et que vous pouvez récupérer pour l'utiliser
146+
147+
:::tip
148+
La plupart des implémentations par défaut des Capabilities fournies par Forge(regardez les classes qui implémentent l'interface de la Capability de votre choix) possèdent des fonctions permettant de sérialiser et de désérialiser des `Tag`. Si elles existent, il est donc préférable de les utiliser dans les fonctions correspondantes de votre Provider.
149+
:::
150+
151+
Voici ce que cela donne si l'on reprend le Provider créé plus haut :
152+
```java
153+
public class EnergyStorageProvider implements ICapabilitySerializable<IntTag> {
154+
private final LazyOptional<IEnergyStorage> energyStorageOptional;
155+
156+
public EnergyStorageProvider(){
157+
this.energyStorageOptional = LazyOptional.of(() -> new EnergyStorage(10000)); // Remplacez le new EnergyStorage() par votre implémentation de l'interface de la capability
158+
}
159+
160+
@Nonnull
161+
@Override
162+
public <T> LazyOptional<T> getCapability(@Nonnull Capability<T> cap, @Nullable Direction side) {
163+
return CapabilityEnergy.ENERGY.orEmpty(cap, this.energyStorageOptional);
164+
}
165+
166+
@Override
167+
public IntTag serializeNBT() {
168+
LazyOptional<EnergyStorage> energyStorage1 = energyStorageOptional.cast(); //Cette ligne sert à transformer le LazyOptional qui contient un IEnergyStorage en LazyOptional qui contient un EnergyStorage. Si vous faites ceci, soyez bien sûrs que votre LazyOptional du début contienne forcément une instance de la classe que vous castez, sinon vous aurez une erreur
169+
return (IntTag) energyStorage1.orElseThrow(() -> new IllegalArgumentException("Unable to serialize the capability : the capability is not present")).serializeNBT();
170+
}
171+
172+
@Override
173+
public void deserializeNBT(IntTag nbt) {
174+
LazyOptional<EnergyStorage> energyStorage1 = energyStorageOptional.cast();
175+
energyStorage1.orElseThrow(() -> new IllegalArgumentException("Unable to deserialize the capability : the capability is not present")).deserializeNBT(nbt);
176+
}
177+
178+
}
179+
```
180+
181+
### Créer une Capability
182+
183+
Si aucune des Capabilities fournies par Forge ne vous convient, vous pouvez créer la vôtre.
184+
185+
Pour ce faire, vous allez devoir créer plusieurs classes : l'interface de la Capability, une ou plusieurs implémentations et enfin une classe qui va contenir l'instance de la Capability.
186+
187+
#### L'interface de la Capability
188+
189+
Cette partie est relativement simple et dépend beaucoup de l'usage que vous voulez faire de votre Capability. Créez juste les fonctions dont vous avez besoin.
190+
191+
Exemple :
192+
```java
193+
public interface ILightCapability {
194+
195+
/**
196+
* Used to get the amount of light stored
197+
* @return the amount of light stored
198+
*/
199+
int getLight();
200+
201+
/**
202+
* Used to define the light stored
203+
* @param light the new amount of light
204+
*/
205+
void setLight(int light);
206+
207+
/**
208+
* Used to add an amount of light to the storage
209+
* @param light the amount of light to be added to the storage
210+
*/
211+
default void receiveLight(int light){
212+
setLight(getLight() + light);
213+
}
214+
215+
/**
216+
* Used to remove an amount of light to the storage
217+
* @param light the amount of light to be removed from the storage
218+
*/
219+
default void extractLight(int light){
220+
setLight(getLight() - light);
221+
}
222+
223+
}
224+
```
225+
226+
#### Les implémentations de l'interface de votre Capability
227+
228+
Après avoir créé l'interface de sa Capability, il faut aussi créer des implémentations de cette même interface : ce seront elles qui seront utilisées en temps réel par le biais des `LazyOptional` lorsque vous récupérez votre Capability.
229+
230+
La seule "règle" est qu'il faut que vous implémentiez votre interface, et vous pouvez aussi faire comme Forge et rajouter des fonctions pour sérialiser et désérialiser les données contenues dans la classe, pour rendre le code plus facile à comprendre et à éditer si vous utilisez plusieurs Provider par exemple.
231+
232+
Exemple :
233+
```java
234+
public class LightStorage implements ILightCapability{
235+
private final int maxLight;
236+
private int light;
237+
238+
public LightStorage(int maxLight){
239+
this.maxLight = maxLight;
240+
}
241+
242+
@Override
243+
public int getLight() {
244+
return light;
245+
}
246+
247+
@Override
248+
public void setLight(int light) {
249+
light = Math.min(light, maxLight); //Un peu de code pour empêcher le niveau de lumière ("light") de dépasser la valeur maximale définie dans le constructeur ou d'être en dessous de 0
250+
light = Math.max(light, 0);
251+
this.light = light;
252+
}
253+
254+
public Tag serializeNBT(){
255+
return IntTag.valueOf(getLight());
256+
}
257+
258+
public void deserializeNBT(Tag nbt){
259+
if(nbt instanceof IntTag){
260+
setLight(((IntTag) nbt).getAsInt());
261+
}
262+
}
263+
}
264+
```
265+
266+
#### Créer la classe contenant l'instance de la Capability
267+
268+
Il faut maintenant créer une classe qui contiendra l'instance par défaut de votre Capability(il s'agit en fait d'une instance de la classe `Capability`)
269+
270+
Le code pour la récupérer est exactement le même qu'[ici](#récupérer-linstance-dune-capability), il faut juste mettre ça dans une classe.
271+
272+
Exemple :
273+
```java
274+
public class LightCapability {
275+
public static Capability<ILightCapability> LIGHT_CAPABILITY = CapabilityManager.get(new CapabilityToken<>(){});
276+
}
277+
```
278+
279+
#### Enregistrer la Capability
280+
281+
Finalement, il faut enregistrer sa Capability à l'aide de la fonction `register` de l'event `RegisterCapabilitiesEvent` pour que Forge sache qu'elle existe.
282+
283+
N'oubliez pas de faire attention à ce que la classe dans laquelle vous mettez l'event soit bien "abonnée" aux flux d'events !
284+
285+
Exemple :
286+
```java
287+
@SubscribeEvent
288+
public void registerCaps(RegisterCapabilitiesEvent event) {
289+
event.register(ILightCapability.class);
290+
}
291+
```

0 commit comments

Comments
 (0)