Implémenter des DataManipulators
Ce guide est destiné aux contributeurs qui souhaitent aider à l’implémentation de la Data API en créant des DataManipulators. Une liste à jour de tous les DataManipulators à implémenter peut être trouvée sur la page SpongeCommon Issue #8 (GitHub).
Pour totalement implémenter un DataManipulator, les étapes suivantes doivent être respectées:
Implémenter le
DataManipulator
en lui-mêmeImplémenter le ImmutableDataManipulator
Après avoir suivi ces étapes, les suivantes doivent être faites:
Enregistrer la Key dans le
KeyRegistryModule
Implémenter le
DataProcessor
Implémenter le
ValueProcessor
pour chaque Value représentée par leDataManipulator
Si les données s’appliquent à un bloc, plusieurs méthodes doivent également être mélangées au bloc.
Note
Assurez-vous de suivre nos Lignes directrices des contributions.
Les extraits suivants montrent les imports/chemins de certaines classes de SpongeCommon dont vous aurez besoin:
import org.spongepowered.common.data.DataProcessor;
import org.spongepowered.common.data.ValueProcessor;
import org.spongepowered.common.data.manipulator.immutable.entity.ImmutableSpongeHealthData;
import org.spongepowered.common.data.manipulator.mutable.common.AbstractData;
import org.spongepowered.common.data.manipulator.mutable.entity.SpongeHealthData;
import org.spongepowered.common.data.processor.common.AbstractEntityDataProcessor;
import org.spongepowered.common.util.Constants;
import org.spongepowered.common.data.util.NbtDataUtil;
import org.spongepowered.common.registry.type.data.KeyRegistryModule;
1. Implémenter le DataManipulator
La convention de nommage pour les implémentations d’un DataManipulator
est le nom de l’interface préfixé avec « Sponge ». Donc pour implémenter l’interface HealthData, nous créerons une classe nommée SpongeHealthData
dans le package approprié. Pour implémenter le DataManipulator
, il faut tout d’abord étendre une classe abstraite appropriée du package org.spongepowered.common.data.manipulator.mutable.common
. Le plus générique est l”AbstractData
, mais il y a aussi des abstractions qui réduisent encore plus la taille du code pour certains cas particuliers comme les DataManipulator
s qui ne contiennent qu’une seule valeur.
public class SpongeHealthData extends AbstractData<HealthData, ImmutableHealthData> implements HealthData {
[...]
}
Il y a deux arguments à la classe AbstractData. Le premier est l’interface implémentée par la classe, le second est l’interface implémentée par le ImmutableDataManipulator
correspondant.
Le Constructeur
Dans la plupart des cas, lorsque vous implémentez un DataManipulator
abstrait, vous devez avoir deux constructeurs :
Un sans arguments (no-args) qui appelle le second constructeur avec des valeurs « par défaut »
L’autre constructeur qui prend toutes les valeurs qu’il supporte.
Le second constructeur doit
faire appel au constructeur
AbstractData
, en passant la référence de la classe pour l’interface implémentée.s’assurer que les valeurs données sont valides
appeler la méthode
registerGettersAndSetters()
import static com.google.common.base.Preconditions.checkArgument;
public class SpongeHealthData extends AbstractData<HealthData, ImmutableHealthData> implements HealthData {
private double health;
private double maxHealth;
public SpongeHealthData() {
this(20D, 20D);
}
public SpongeHealthData(double health, double maxHealth) {
super(HealthData.class);
checkArgument(maxHealth > 0);
this.health = health;
this.maxHealth = maxHealth;
registerGettersAndSetters();
}
[...]
}
Maintenant que nous savons que la vie actuelle et la vie maximum ont une limite, nous devons vérifier qu’aucune valeur donnée ne dépasse ces limites. Pour faire cela, nous utiliserons les Preconditions
de guava dont nous importerons les méthodes requises statiquement.
Note
N’utilisez jamais ce que l’on appelle « valeurs magiques » (des nombres arbitraires, booléens, etc.) dans votre code. Au lieu de cela, localisez la classe DataConstants
et utilisez une constante de raccord - ou en créer une, si nécessaire.
Accesseurs définis par l’Interface
L’interface que nous implémentons spécifie quelques méthodes pour accéder à l’objet Value. Pour HealrhData
, ce sont HealthData#health() et HealthData#maxHealth(). Chaque appel qui leur est fait devra donner une nouvelle Value
.
public MutableBoundedValue<Double> health() {
return SpongeValueFactory.boundedBuilder(Keys.HEALTH)
.minimum(0)
.maximum(this.maxHealth)
.defaultValue(this.maxHealth)
.actualValue(this.health)
.build();
}
Astuce
Depuis que Double
est un Comparable
, nous n’avons plus besoin de spécifier un comparateur.
Si aucune valeur n’est actuellement spécifiée, l’appel BaseValue#get() sur la Value
retourne la valeur par défaut.
Copie et Serialization
Les deux méthodes DataManipulator#copy() et DataManipulator#asImmutable() ne sont pas compliquées à implémenter. Pour les deux, vous n’aurez qu’à retourner un data manipulator mutable ou immuable respectivement, contenant les mêmes données que l’instance actuelle.
La méthode DataSerializable#toContainer() est utilisée pour la sérialisation. Utilisez DataContainer#createNew() comme résultat et appliquez-y les valeurs stockées dans l’instance actuelle. Un DataContainer est surtout une map qui associe des DataQuerys aux valeurs. Puisqu’une Key contient toujours un DataQuery
correspondant, il suffit d’utiliser ceux-ci en passant la Key
directement.
public DataContainer toContainer() {
return super.toContainer()
.set(Keys.HEALTH, this.health)
.set(Keys.MAX_HEALTH, this.maxHealth);
}
registerGettersAndSetters()
Un DataManipulator
permet également de fournir des méthodes qui servent à obtenir et définir des données en utilisant des clés. L’implémentation pour le faire est gérée par AbstractData
, mais il faut lui dire à quelle donnée accéder et comment y accéder. Il nous faut donc suivre les instructions suivantes pour chaque valeur de la méthode registerGettersAndSetters()
:
enregistrer un Supplier pour directement obtenir la valeur
enregistrer un Consumer pour directement obtenir la valeur
enregistrer un
Supplier<Value>
pour obtenir la mutableValue
Le Supplier
et le Consumer
sont des interfaces fonctionnelles, les Lambdas de Java 8 peuvent donc être utilisées.
private SpongeHealthData setCurrentHealthIfValid(double value) {
if (value >= 0 && value <= (double) Float.MAX_VALUE) {
this.health = value;
} else {
throw new IllegalArgumentException("Invalid value for current health");
}
return this;
}
private SpongeHealthData setMaximumHealthIfValid(double value) {
if (value >= 0 && value <= (double) Float.MAX_VALUE) {
this.maxHealth = value;
} else {
throw new IllegalArgumentException("Invalid value for maximum health");
}
return this;
}
private void registerGettersAndSetters() {
registerFieldGetter(Keys.HEALTH, () -> this.health);
registerFieldSetter(Keys.HEALTH, this::setCurrentHealthIfValid);
registerKeyValue(Keys.HEALTH, this::health);
registerFieldGetter(Keys.MAX_HEALTH, () -> this.maxHealth);
registerFieldSetter(Keys.MAX_HEALTH, this::setMaximumHealthIfValid);
registerKeyValue(Keys.MAX_HEALTH, this::maxHealth);
}
Le Consumer
enregistré en tant que setter doit effectuer les vérifications adéquates pour s’assurer que la valeur fournie est valide. En particulier pour les DataHolders qui n’acceptent pas les valeurs négatives. Si une valeur est invalide, une IllegalArgumentException
doit être levée.
Astuce
Les critères de validité pour ces setters sont les mêmes que pour l’objet Value
respectif, ainsi vous pouvez déléguer la vérification de la validité à un appel de this.health().set()
et juste définir this.health = value
si la première ligne n’a pas encore levé d’exception.
Et voilà. Le DataManipulator
devrait être terminé maintenant.
2. Implémenter le ImmutableDataManipulator
Implémenter le ImmutableDataManipulator est similaire à l’implémentation de celui qui est mutable.
Les seules différences sont :
Le nom de la classe est formé en préfixant le nom des
DataManipulator
s mutables avecImmutableSponge
Il hérite de
ImmutableAbstractData
à la placeAu lieu de
registerGettersAndSetters()
, la méthode s’appelleregisterGetters()
Quand vous créez des ImmutableDataHolder
s ou des ImmutableValue
s, vérifiez s’il est judicieux d’utiliser le ImmutableDataCachingUtil
. Par exemple, si vous avez un WetData
qui ne contient rien d’autre qu’un booléen, il est plus facile de ne retenir que deux instances mises en cache de ImmutableWetData
- une pour chaque valeur possible. Pour les manipulateurs et les valeurs avec plusieurs valeurs possibles (comme SignData
), la mise en cache serait trop gourmande.
Astuce
Vous devez déclarer les champs d’un ImmutableDataManipulator
comme final
afin d’éviter tous changements accidentels.
3. Enregistrer la Key dans le KeyRegistryModule
Le prochaine étape est d’enregistrer votre Keys aux Keys. Pour ce faire, trouvez la classe KeyRegistryModule
puis la méthode registerDefaults()
. Vous y ajouterez une ligne pour enregistrer (et créer) vos clés.
import static org.spongepowered.api.data.DataQuery.of;
this.register("health", Key.builder()
.type(TypeTokens.BOUNDED_DOUBLE_VALUE_TOKEN)
.id("health")
.name("Health")
.query(of("Health"))
.build());
this.register("max_health", Key.builder()
.type(TypeTokens.BOUNDED_DOUBLE_VALUE_TOKEN)
.id("max_health")
.name("Max Health")
.query(of("MaxHealth"))
.build());
The register(Key)
method registers your Key
s for later use. The string used for the id should be the
corresponding constant name from the Keys
utility class in lowercase. The Key
itself is created by using the
Key.Builder provided by the Key#builder() method. You have to set a TypeToken
, an id
,
human readable name
and a DataQuery
.
The DataQuery
is used for serialization. It is created from the statically imported DataQuery.of()
method
accepting a string. This string should also be the constant name, stripped of underscores and capitalization changed to
upper camel case.
4. Implémenter les DataProcessors
Ensuite vient le DataProcessor
. Un DataProcessor
sert de pont entre notre DataManipulator
et les objets de Minecraft. Chaque fois que des données sont demandées ou offertes aux DataHolders
qui existent dans Minecraft de base, ces appels finissent par être délégués à un DataProcessor
ou un ValueProcessor
.
Pour votre nom, vous devez utiliser le nom de l’interface DataManipulator
et ajouter Processor
. Ainsi, pour HealthData
nous créons un HealthDataProcessor
.
Afin de réduire le code réutilisable, le DataProcessor
doit hériter de la classe abstraite appropriée dans le package org.spongepowered.common.data.processor.common
. Puisque la santé peut seulement être présente sur certaines entités, nous pouvons faire usage du AbstractEntityDataProcessor
qui vise spécifiquement les Entities
issues de net.minecraft.entity.Entity
. AbstractEntitySingleDataProcessor
nécessiterait moins de travail d’implémentation, mais ne peut pas être utilisé puisque HealthData
contient plus d’une valeur.
public class HealthDataProcessor
extends AbstractEntityDataProcessor<EntityLivingBase, HealthData, ImmutableHealthData> {
public HealthDataProcessor() {
super(EntityLivingBase.class);
}
[...]
}
Selon quel abstraction vous utilisez, les méthodes que vous devez implémenter peuvent différer grandement, selon la quantité de travail d’implémentation qui pourrait déjà être fait dans la classe abstraite. Généralement, les méthodes peuvent être classées.
Astuce
Il est possible de créer plusieurs DataProcessor
s pour les mêmes données. Si des DataHolder
s largement différents doivent être supportés (par exemple une TileEntity
et un ItemStack
correspondant), il peut être avantageux de créer un processeur pour chaque type de DataHolder
afin d’utiliser pleinement les abstractions fournies. Veillez à suivre la structure de packages pour les items, les tileentities et les entities.
Méthodes de Validation
Retourne toujours une valeur booléenne. Si n’importe quelle des méthodes supports(target)
est appelée ça doit effectuer une vérification générale si la cible fournie supporte généralement le type de données gérées par notre DataProcessor
. Selon votre niveau d’abstraction vous pouvez ne pas avoir à l’implémenter du tout, si vous devez, implémentez juste le plus spécifique, comme les plus génériques leur délègue souvent.
Pour notre HealthDataProcessor
, supports()
est implémenté par le AbstractEntityDataProcessor
. Par défaut, il va retourner true si l’argument fourni est une instance de la classe spécifiée lors de l’appel du constructeur super()
.
Au lieu de cela, nous sommes obligés de fournir une méthode doesDataExist()
. Puisque l’abstraction ne sait pas comment obtenir les données, il laisse cette fonction à être implémentée. Comme le nom le dit, la méthode doit vérifier si les données existent déjà sur la cible supportée. Pour le HealthDataProcessor
, cela retourne toujours true, puisque chaque entité vivante possède toujours de la santé.
@Override
protected boolean doesDataExist(EntityLivingBase entity) {
return true;
}
Méthodes Setter
Une méthode setter reçoit un DataHolder
d’une sorte et certaines données qui devraient lui être appliquées, si possible.
L’interface DataProcessor
définit une méthode set()
acceptant un DataHolder
et un DataManipulator
, et qui retourne un DataTransactionResult
. Selon la classe d’abstraction utilisée, certaines des fonctionnalités nécessaires peuvent déjà être implémentées.
Dans ce cas, l”AbstractEntityDataProcessor
s’occupe de la plupart d’elles et nécessite juste une méthode pour définir certaines valeurs poru retourner true
si c’était réussi et false
si ça ne l’était pas. Toutes les vérifications de si le DataHolder
supporte la Data
sont prises en charge, la classe abstraite va juste passer un mappage de Map pour chaque Key
depuis le DataManipulateur
à sa valeur, et construit alors un DataTransactionResult
selon si l’opération a réussi ou non.
@Override
protected boolean set(EntityLivingBase entity, Map<Key<?>, Object> keyValues) {
entity.getEntityAttribute(SharedMonsterAttributes.MAX_HEALTH)
.setBaseValue(((Double) keyValues.get(Keys.MAX_HEALTH)).floatValue());
float health = ((Double) keyValues.get(Keys.HEALTH)).floatValue();
entity.setHealth(health);
return true;
}
Astuce
Pour comprendre les DataTransactionResults, vérifiez la page de docs correspondante et référez-vous aux docs de DataTransactionResult.Builder pour en créer un.
Avertissement
Surtout lors du travail avec les ItemStacks il est probable que vous aillez besoin de faire face directement à des NBTTagCompound
s. De nombreuses clés NBT sont déjà définies en tant que constantes dans la classe NbtDataUtil
. Si votre clé requise n’est pas là, vous devez l’ajouter afin d’éviter des “nombres magiques” dans le code.
Méthode de Suppression
La méthode remove()
tente de supprimer les données du DataHolder
et retourne un DataTransactionResult
.
La suppression n’est pas abstraite dans n’importe quel DataProcessor
abstrait puisque les abstractions n’ont aucun moyen de savoir si les données sont toujours présentes sur un DataHolder
compatible (comme WetData
ou HealthData
) ou si elles peuvent ou ne peuvent pas être présentes (comme LoreData
). Si les données sont toujours présentes, remove()
doit toujours échouer. Si elles peuvent ou peuvent ne pas être présentes, remove()
doit les supprimer.
Puisqu’une entité vivante possède toujours de la vie, HealthData
est toujours présent et la suppression n’est par conséquent pas supportée. Donc nous retournons juste DataTransactionResult#failNoData().
@Override
public DataTransactionResult remove(DataHolder dataHolder) {
return DataTransactionResult.failNoData();
}
Méthodes Getter
Les méthodes getter obtiennent des données depuis un DataHolder
et retournent un DataManipulator
optionnel. L’interface DataProcessor
spécifie les méthodes from()
et createFrom()
, la différence est que from()
va retourner Optional.empty()
si le data holder est dompatible, mais ne contient pas les données, alors que createFrom()
va fournir un DataManipulator
détenant les valeurs par défaut dans ce cas.
Encore une fois, AbstractEntityDataProcessor
va fournir la plupart des implémentations pour cela et ne nécessite qu’une méthode pour récupérer les valeurs présentes sur le DataHolder
. Cette méthode est seulement appeler après que supports()
et doesDataExist()
aient retourné true, ce qui signifie qu’elle est exécutée sous l’hypothèse que les données sont présentes.
Avertissement
Si les données peuvent ne pas toujours exister sur le DataHolder
cible, par exemple si la fonction remove()
peut être réussie (voir ci-dessus), il est impératif de réécrire la méthode doesDataExist()
afin qu’elle retourne true
si les données sont présentes et false
si elles ne le sont pas.
@Override
protected Map<Key<?>, ?> getValues(EntityLivingBase entity) {
final double health = entity.getHealth();
final double maxHealth = entity.getMaxHealth();
return ImmutableMap.of(Keys.HEALTH, health, Keys.MAX_HEALTH, maxHealth);
}
Méthodes Filler
Une méthode filler est différente d’une méthode getter puisqu’elle reçoit un DataManipulator
pour le remplir avec des valeurs. Ces valeurs viennent soit d’un DataHolder
ou doivent être désérialisées à partir d’un DataContainer
. La méthode retourne Optional.empty()
si le DataHolder
n’est pas compatible.
AbstractEntityDataProcessor
traite déjà le remplissage depuis les DataHolders
en créant un DataManipulator
depuis le holder et le fusionne avec le manipulateur fourni, mais il ne peut pas fournir la désérialisation du DataContainer
.
@Override
public Optional<HealthData> fill(DataContainer container, HealthData healthData) {
if (!container.contains(Keys.MAX_HEALTH.getQuery()) || !container.contains(Keys.HEALTH.getQuery())) {
return Optional.empty();
}
healthData.set(Keys.MAX_HEALTH, getData(container, Keys.MAX_HEALTH));
healthData.set(Keys.HEALTH, getData(container, Keys.HEALTH));
return Optional.of(healthData);
}
La méthode fill()
consiste à retourner un Optional
des healthData
altérées, si et seulement si toutes les données nécessaires peuvent être obtenues depuis le DataContainer
.
Autres Méthodes
Selon la superclasse abstraite utilisée, certaines autres méthodes peuvent être nécessaires. Par exemple, AbstractEntityDataProcessor
a besoin de créer des instances de DataManipulator
en différents points. Il ne peut pas faire ça puisqu’il ne connaît ni la classe d’implémentation, ni le constructeur à utiliser. Par conséquent, il utilise une fonction abstraite qui doit être fournie par l’implémentation finale. Cela ne fait rien de plus que de créer un DataManipulator
avec les données par défaut.
Si vous avez implémenté votre DataManipulator
comme c’est recommandé, vous pouvez simplement utiliser le constructeur sans argument.
@Override
protected HealthData createManipulator() {
return new SpongeHealthData();
}
5. Implémenter les ValueProcessors
Non seulement un DataManipulator
peut être offert à un DataHolder
, mais aussi une clé Value
seule. Par conséquent, vous devez fournir au moins un ValueProcessor
pour chaque Key
présente dans votre DataManipulator
. Un ValueProcessor
est nommé d’après le nom de la constante de sa Key
dans la classe Keys
de manière similaire à son DataQuery
. Le nom de la constante est dépouillée de tirets-bas (underscores), utilisée en upper camel case et puis suffixé par ValueProcessor
.
Un ValueProcessor
doit toujours hériter de AbstractSpongeValueProcessor
, qui gère déjà une partie des vérifications supports()
basées sur le type de DataHolder
. Pour Keys.HEALTH
, nous allons créer et construire HealthValueProcessor
comme suit.
public class HealthValueProcessor
extends AbstractSpongeValueProcessor<EntityLivingBase, Double, MutableBoundedValue<Double>> {
public HealthValueProcessor() {
super(EntityLivingBase.class, Keys.HEALTH);
}
[...]
}
Maintenant le AbstractSpongeValueProcessor
va nous soulager de la nécessité de vérifier si la valeur est supportée. Elle est supposée être supportée si le ValueContainer
cible est de type EntityLivingBase
.
Astuce
Pour un contrôle plus précis sur quels objets de EntityLivingBase
sont supportés, la méthode supports(EntityLivingBase)
peut être réécrite.
Encore une fois, la plupart du travail est fait par la classe d’abstraction. Nous devons juste implémenter deux méthodes auxiliaires pour créer une Value
et son homologue immuable, et trois méthodes pour récupérer, définir et supprimer les données.
@Override
protected MutableBoundedValue<Double> constructValue(Double health) {
return SpongeValueFactory.boundedBuilder(Keys.HEALTH)
.minimum(0D)
.maximum(((Float) Float.MAX_VALUE).doubleValue())
.defaultValue(20D)
.actualValue(health)
.build();
}
@Override
protected ImmutableBoundedValue<Double> constructImmutableValue(Double value) {
return constructValue(value).asImmutable();
}
@Override
protected Optional<Double> getVal(EntityLivingBase container) {
return Optional.of((double) container.getHealth());
}
Puisqu’il est impossible pour une EntityLivingBase
de ne pas avoir de santé, cette méthode ne retournera jamais Optional.empty()
.
@Override
protected boolean set(EntityLivingBase container, Double value) {
if (value >= 0 && value <= (double) Float.MAX_VALUE) {
container.setHealth(value.floatValue());
return true;
}
return false;
}
La méthode set()
va retourner une valeur booléenne indiquant si la valeur a pu être définie correctement. Cette implémentation va rejeter les valeurs en dehors des limites utilisées dans nos méthodes de construction de valeur ci-dessus.
@Override
public DataTransactionResult removeFrom(ValueContainer<?> container) {
return DataTransactionResult.failNoData();
}
Puisque les données sont garanties d’être toujours présentes, les tentatives pour les supprimer échoueront.
6. Enregistrer des Processeurs
Afin que Sponge soit capable d’utiliser nos manipulateurs et nos processeurs, nous devons les enregistrer. Cela se fait dans la classe DataRegistrar
. Dans la méthode setupSerialization()
il y a deux grands blocs d’enregistrements auxquels nous ajoutons nos processeurs.
DataProcessors
Un DataProcessor
est enregistré parallèlement à l’interface et aux classes d’implémentation du DataManipulator
qu’il gère. Pour chaque pair de DataManipulator
s mutables/immuables, au moins un DataProcessor
doit être enregistré.
DataUtil.registerDataProcessorAndImpl(HealthData.class, SpongeHealthData.class,
ImmutableHealthData.class, ImmutableSpongeHealthData.class,
new HealthDataProcessor());
ValueProcessors
Les processeurs de valeurs sont enregistrés en bas de la même fonction. Pour chaque Key
plusieurs processeurs peuvent être enregistrés par les appels suivants de la méthode registerValueProcessor()
.
DataUtil.registerValueProcessor(Keys.HEALTH, new HealthValueProcessor());
DataUtil.registerValueProcessor(Keys.MAX_HEALTH, new MaxHealthValueProcessor());
Implémenter les Données de Blocs
Les données de blocs sont quelque peu différentes des autres types de données car elles sont implémentées par le mélange dans le bloc lui-même. Il existe plusieurs méthodes dans org.spongepowered.mixin.core.block.MixinBlock
qui doivent être réécrites pour implémenter des données pour les blocs.
@Mixin(BlockHorizontal.class)
public abstract class MixinBlockHorizontal extends MixinBlock {
[...]
}
supports()
doit retourner true` si l'interface ``ImmutableDataManipulator
est assignable depuis la Class
passée en argument, ou si la superclasse le supporte.
@Override
public boolean supports(Class<? extends ImmutableDataManipulator<?, ?>> immutable) {
return super.supports(immutable) || ImmutableDirectionalData.class.isAssignableFrom(immutable);
}
getStateWithData()
doit retourner un nouveau BlockState
avec les données du ImmutableDataManipulator
appliquées. Si le manipulateur n’est pas directement supporté, la méthode doit déléguer à la superclasse.
@Override
public Optional<BlockState> getStateWithData(IBlockState blockState, ImmutableDataManipulator<?, ?> manipulator) {
if (manipulator instanceof ImmutableDirectionalData) {
final Direction direction = ((ImmutableDirectionalData) manipulator).direction().get();
final EnumFacing facing = DirectionResolver.getFor(direction);
return Optional.of((BlockState) blockState.withProperty(BlockHorizontal.FACING, facing));
}
return super.getStateWithData(blockState, manipulator);
}
getStateWithValue()
est l’équivalent de getStateWithData()
, mais fonctionne avec des Key
s seules.
@Override
public <E> Optional<BlockState> getStateWithValue(IBlockState blockState, Key<? extends BaseValue<E>> key, E value) {
if (key.equals(Keys.DIRECTION)) {
final Direction direction = (Direction) value;
final EnumFacing facing = DirectionResolver.getFor(direction);
return Optional.of((BlockState) blockState.withProperty(BlockHorizontal.FACING, facing));
}
return super.getStateWithValue(blockState, key, value);
}
Enfin, getManipulators()
doit retourner une liste de tous les ImmutableDataManipulator
s supportés par le bloc, ainsi que les valeurs actuelles pour le IBlockState
fourni. Il doit inclure tous les ImmutableDataManipulator
s de la superclasse.
@Override
public List<ImmutableDataManipulator<?, ?>> getManipulators(IBlockState blockState) {
return ImmutableList.<ImmutableDataManipulator<?, ?>>builder()
.addAll(super.getManipulators(blockState))
.add(new ImmutableSpongeDirectionalData(DirectionResolver.getFor(blockState.getValue(BlockHorizontal.FACING))))
.build();
}
Informations Supplémentaires
Avec Data
étant un concept assez abstrait dans Sponge, il est difficile de donner des directives générales sur la façon d’acquérir les données nécessaires depuis les classes de Minecraft. Il peut être utile de jeter un oeil aux processeurs déjà implémentés similaires à ceux sur lesquels vous travaillez pour obtenir une meilleure compréhension de la façon dont il devrait fonctionner.
Si vous êtes bloqué ou que vous hésitez sur certains points, allez sur le channel IRC #spongedev
, le channel #dev
sur Discord, les forums, ou ouvrez un ticket sur GitHub. Vérifiez la Data Processor Implementation Checklist pour connaître les conditions de contribution.