Wykorzystanie DataManipulators
Ten poradnij jest dla autorów, którzy chcą pomóc w tworzeniu DataAPI (API operujące danymi) tworząc DataManipulators. Zaktualizowaną listę dostępnychDataManipulators do wdrożenia można odnaleźć w SpongeCommon Issue #8.
Aby w pełni zaimplementować DataManipulator te kroki muszą być wykonane:
Wdrożyć
DataManipulator
samodzielnieZaimplementuj ImmutableDataManipulator
Gdy te kroki zostały wykonane, należy wykonać następnie:
Rejestracja Key w
KeyRegistryModule
Wdrożyć
DataProcessor
Zaimplementuj
ValueProcessor
dla każdego Value reprezentowanego przezDataManipulator
Jeśli data odnosi się do bloku, kilka metod również musi być zamieszanych w blok.
Informacja
Upewnij się, że możesz śledzić nasze Zasady współpracy.
Poniższy fragment pokazuje import/ścieżki dla niektórych klas z SpongeCommon których będziesz potrzebować:
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. Implement the DataManipulator - Wdrażanie Manipulatora Danych
Konwencja nazewnictwa dla implementacji DataManipulator
jest nazwą interfejsu poprzedzonego „Sponge”. Aby zaimplementować :javadoc:interfejs HealthData, tworzymy klasę o nazwie SpongeHealthData
w odpowiednim pakiecie. Dla implementacji DataManipulator
najpierw należy rozszerzyć odpowiednią klasę abstrakcyjną z org. pongepowered.common.data.manipulator.mutable.common
pakiet. Najbardziej generyczna jest AbstractData
ale istnieją również abstrakcje, które zmniejszają kod boilerplate jeszcze bardziej dla niektórych szczególnych przypadków, takich jak DataManipulator
s zawierających tylko jedną wartość.
public class SpongeHealthData extends AbstractData<HealthData, ImmutableHealthData> implements HealthData {
[...]
}
Istnieją dwa typy argumentów do klasy AbstractData. Pierwszym z nich jest interfejs zaimplementowanego przez klase, a drugim interfejs zaimplementowanego przez odpowiednie ImmutableDataManipulator
.
Konstruktor obiektu
W większości przypadków podczas wdrażania abstrakcyjnego ``DataManipulator``musisz posiadać dwa konstruktory:
Jeden bez argumentów, które wywołuje konstruktora drugiego z wartości „domyślne”.
Drugiego konstruktora, który wspiera wszystkie podane wartości.
Drugi konstruktor musi
wywołać konstruktor
AbstractData
, przekazując odwołanie klasy zaimplementowanego interfejsu.Upewnij się, że wartości przekazywane są prawidłowo
Wywołaj metodę:
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();
}
[...]
}
Ponieważ wiemy, że zarówno obecne zdrowie, jak i maksymalne zdrowie są ograniczone, musimy mieć pewność, że żadne wartości poza tymi granicami nie będą mogły zostać przekazane. Aby to osiągnąć, używamy guava Preconditions
z którego zaimportujemy wymagane metody statycznie.
Informacja
Nigdy nie używaj tak zwanych wartości magicznych (dowolnych liczb, wartości logicznych itp.) w swoim kodzie. Zamiast tego zlokalizuj klasę DataConstants
i użyj odpowiedniej stałej - lub, jeśli to konieczne, stwórz ją.
Uprawnienia definiowane przez interfejs
Interfejs, który implementujemy określa kilka metod dostępu do obiektów Value. Dla HealthData
, są to HealthData#health() i HealthData#maxHealth(). Każde wywołanie tych metod powinno zwrócić nam nowy obiekt Value
.
public MutableBoundedValue<Double> health() {
return SpongeValueFactory.boundedBuilder(Keys.HEALTH)
.minimum(0)
.maximum(this.maxHealth)
.defaultValue(this.maxHealth)
.actualValue(this.health)
.build();
}
Wskazówka
Odkąd Double
jest Comparable
(porównywanie) to nie trzeba jawnie określać comparatora.
Jeżeli żadna bieżąca wartość nie zostanie określona, wywołując BaseValue#get() na obiektcie Value
zwrócona zostanie domyślna wartość.
Kopiowanie i Serializacja
Obydwie metody DataManipulator#copy() i DataManipulator#asImmutable() nie wymagają trudu przy implementacji. W obu przypadkach wystarczy zwrócić mutowalny lub niemutowalny manipulator danych, zawierający te same dane co bieżąca instancja.
Metoda DataSerializable#toContainer() jest używana w celach serializacji. Użyj DataContainer#createNew() jako wyniku i zaaplikuj do niego wartości przechowywane w tej instancji. DataContainer jest w zasadzie mapą mapującą DataQuery do wartości. Ponieważ Key zawsze zawiera odpowiadające mu DataQuery
, po prostu użyj ich przekazując Key
bezpośrednio do funkcji.
public DataContainer toContainer() {
return super.toContainer()
.set(Keys.HEALTH, this.health)
.set(Keys.MAX_HEALTH, this.maxHealth);
}
registerGettersAndSetters()
DataManipulator
zawiera również metody do pobierania i ustawiania danych za pomocą kluczy. Wdrożenie to jest obsługiwane przez AbstractData
, ale musimy przedstawić które dane mają dostęp i jak. W związku z tym w metodzie registerGettersAndSetters()
musimy przypisać następujące informacje dla każdej wartości:
zarejestruj :javadoc:
Supplier
aby bezpośrednio uzyskać wartośćzarejestruj Consumer aby bezpośrednio ustawić wartość
zarejestrować
Supplier<Value>
aby pobrać zmiennąValue
(wartość)
Supplier
i Consumer
są funkcjonalnymi interfejsami więc mogą być używane w Java 8 Lambdas.
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);
}
Consumer
zarejestrowany jako setter pola musi wykonać odpowiednie środki kontroli, by upewnić się, że dostarczona wartość jest odpowiednia. Dotyczy to zwłaszcza :javadoc:`DataHolder`ów, które nie akceptują wartości ujemnych. Jeśli wartość jest nieprawidłowa to powinien zostać rzucony wyjątek ``IllegalArgumentException``.
Wskazówka
Kryteria sprawdzające poprawność danych dla settera są tym samym co poszczególne obiekty Value
, więc możesz oddelegować walidację do wywołania this.health().set()
i po prostu ustawić this.currentHealth = wartość
, jeżeli pierwsza linijka nie rzuci wyjątkiem.
To jest to. DataManipulator
powinieneś skończyć teraz.
2. Implement the ImmutableDataManipulator
Implementowanie :javadoc:``ImmutableDataManipulator``a jest podobne do implementowania wariantu mutowalnego.
Jedyne różnice to:
Jako klasę wartości do
DataManipulator
dodaje się z nazwyImmutableSponge
W zamian dziedziczy z
ImmutableAbstractData
Zamiast metody
registerGettersAndSetters()
wywoływana jestregisterGetters()
Podczas tworzenia ImmutableDataHolder``a lub ``ImmutableValue
sprawdź czy jest sens używać ImmutableDataCachingUtil
. Dla przykładu, jeśli używasz WetData
, który nie zawiera nic więcej niż jeden boolean, to dużo lepszym rozwiązaniem jest zachowanie tylko dwóch zcacheowanych instancji ImmutableWetData
- jeda dla każdej możliwej wartości. Aczkolwiek, dla manipulatorów i wartości z wieloma możliwymi wartościami (jak np. SigData
) cache’owanie okazuje się być zbyt zasobożerne.
Wskazówka
Aby zapobiec przypadkowym zmianom należy zadeklarować ImmutableDataManipulator
jako final
.
3. Zarejestruj Key (klucz) w KeyRegistryModule
Następnym krokiem jest zarejestrowanie swojego Key w Keys. Aby to zrobić, zlokalizuj klasę KeyRegistryModule
i znajdź metodę registerDefaults()
. Następnie dodaj linię do rejestracji (i utworzenia) swoich kluczy.
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());
Metoda register(Key)
rejestruje twój Key
w celu późniejszego użycia. String użyty dla id powinien być stałą nazwą z klasy Keys
napisany małymi literami. Key
jest tworzony za pomocą buildera Key.Builder dostarczanego przez metodę Key#builder(). Musisz ustawić TypeToken
, id
, czytelny dla człowieka name
i DataQuery
. DataQuery
jest używane do serializacji. Jest on tworzony ze statycznie zaimportowanej metody DataQuery.of()
akceptującej string jako parametr. Ten string znaków powinien również być stałą nazwą, pozbawioną podkreśleń i drukowanych liter.
4. Wdróż DataProcessors
Następny jest DataProcessor
. DataProcessor
służy jako pomost między naszym ``DataManipulator``em a obiektami Minecrafta. Za każdym razem, gdy żądamy danych lub oferujemy je ``DataHolder``owi, który istnieje w Vanilla Minecraftcie, wywołania te są delegowane do ``DataProcessor``a lub ``ValueProcessor``a.
Dla twojej nazwy, powinieneś użyć nazwy interfejsu DataManipulator
oraz dodać przyrostek Procesor
do nazwy. Przykładowo dla HealthData
tworzymy HealthDataProcessor
.
W celu zmniejszenia standardowego kodu, DataProcessor` powinien dziedziczyć od odpowiedniej klasy abstrakcji w pakiecie ``org.spongepowered.common.data.pocessor.common
. Ponieważ zdrowie może być obecne tylko przy niektórych jednostkach, możemy korzystać z „AbstractEntityDataProcessor” który jest przeznaczony dla jednostki
bazowany na net.minecraft.entity.Entity
.
AbstractEntitySingleDataProcessor
wymaga mniej pracy implementacji, ale nie może służyć jako DaneZdrowotne
ponieważ zawiera więcej niż jedną wartość.
public class HealthDataProcessor
extends AbstractEntityDataProcessor<EntityLivingBase, HealthData, ImmutableHealthData> {
public HealthDataProcessor() {
super(EntityLivingBase.class);
}
[...]
}
W zależności od tego, jakiej abstrakcji używasz, metody które musisz zaimplementować mogą się znacznie różnić w zależności od tego ile implementacji można wykonać w ramach klasy abstrakcyjnej. Generalnie, metody mogą być kategoryzowane.
Wskazówka
Możliwe jest utworzenie wielu DataProcessor``ów dla tych samych danych. Jeśli znacznie różne ``DataHolder``y powinny być wspierane (na przykład zarówno ``TileEntity
jak i ItemStack
), korzystne może być stworzenie jednego procesora dla każdego ``DataHolder``a w celu pełnego wykorzystania dostarczonych abstrakcji. Upewnij się, że śledzisz strukturę pakietów dla przedmiotów, tileenitities i entities.
Metody sprawdzania poprawności
Zawsze zwracaj wartość boolean. Jeśli którakolwiek z metod supports(target)
jest użyta, powinna wykonać ogólne sprawdzenie czy dostarczony cel obsługuje rodzaj danych obsługiwany przez nasz DataProcessor
. Biorąc pod uwagę poziom abstrakcji, być może nie będziesz musiał go wcale implementować. jeśli musisz po prostu zaimplementować najbardziej szczegółową wersję, ponieważ te bardziej ogólne zazwyczaj ich do nich oddelegowują.
For our HealthDataProcessor
supports()
is implemented by the AbstractEntityDataProcessor
. Per
default, it will return true if the supplied argument is an instance of the class specified when calling the
super()
constructor.
Instead, we are required to provide a doesDataExist()
method. Since the abstraction does not know how to
obtain the data, it leaves this function to be implemented. As the name says, the method should check if the data
already exists on the supported target. For the HealthDataProcessor
, this always returns true, since every
living entity always has health.
@Override
protected boolean doesDataExist(EntityLivingBase entity) {
return true;
}
Metody ustawiacza
A setter method receives a DataHolder
of some sort and some data that should be applied to it, if possible.
The DataProcessor
interface defines a set()
method accepting a DataHolder
and a DataManipulator
which returns a DataTransactionResult
. Depending on the abstraction class used, some of the necessary
functionality might already be implemented.
In this case, the AbstractEntityDataProcessor
takes care of most of it and just requires a method to set
some values to return true
if it was successful and false
if it was not. All checks if the
DataHolder
supports the Data
is taken care of, the abstract class will just pass a Map mapping each
Key
from the DataManipulator
to its value and then construct a DataTransactionResult
depending on
whether the operation was successful or not.
@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;
}
Wskazówka
To understand DataTransactionResults, check the corresponding docs page and refer to the DataTransactionResult.Builder docs to create one.
Ostrzeżenie
Especially when working with ItemStacks it is likely that you will need to deal with
NBTTagCompound
s directly. Many NBT keys are already defined as constants in the NbtDataUtil
class.
If your required key is not there, you need to add it in order to avoid «magic values» in the code.
Metody usuwania
The remove()
method attempts to remove data from the DataHolder
and returns a DataTransactionResult
.
Removal is not abstracted in any abstract DataProcessor
as the abstractions have no way of knowing if the data
is always present on a compatible DataHolder
(like WetData
or HealthData
) or if it may or may not be present
(like LoreData
). If the data is always present, remove()
must always fail. If it may or may not be present,
remove()
should remove it.
Since a living entity always has health, HealthData
is always present and removal therefore not supported.
Therefore we just return DataTransactionResult#failNoData().
@Override
public DataTransactionResult remove(DataHolder dataHolder) {
return DataTransactionResult.failNoData();
}
Metody getter
Getter methods obtain data from a DataHolder
and return an optional DataManipulator
. The
DataProcessor
interface specifies the methods from()
and createFrom()
, the difference being that
from()
will return Optional.empty()
if the data holder is compatible, but currently does not contain the
data, while createFrom()
will provide a DataManipulator
holding default values in that case.
Again, AbstractEntityDataProcessor
will provide most of the implementation for this and only requires a
method to get the actual values present on the DataHolder
. This method is only called after supports()
and doesDataExist()
both returned true, which means it is run under the assumption that the data is present.
Ostrzeżenie
If the data may not always exist on the target DataHolder
, e.g. if the remove()
function may be successful
(see above), it is imperative that you implement the doesDataExist()
method so that it returns true
if the data is present and false
if it is not.
@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);
}
Filtruj Metody
A filler method is different from a getter method in that it receives a DataManipulator
to fill with values.
These values either come from a DataHolder
or have to be deserialized from a DataContainer
. The method
returns Optional.empty()
if the DataHolder
is incompatible.
AbstractEntityDataProcessor
already handles filling from DataHolders
by creating a DataManipulator
from the holder and then merging it with the supplied manipulator, but the DataContainer
deserialization it
cannot provide.
@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);
}
The fill()
method is to return an Optional
of the altered healthData
, if and only if all required data could
be obtained from the DataContainer
.
Inne metody
W zależności od zastosowanej abstrakcyjnej superklasy mogą być wymagane inne metody. Na przykład `` AbstractEntityDataProcessor`` musi stworzyć instancje `` DataManipulator`` w różnych punktach. Nie może tego zrobić, ponieważ nie zna klasy implementacji ani konstruktora. Dlatego wykorzystuje abstrakcyjną funkcję, która musi zostać zapewniona przez ostateczną implementację. To nie robi nic więcej niż tworzenie „DataManipulator” z domyślnymi danymi.
If you implemented your DataManipulator
as recommended, you can just use the no-args constructor.
@Override
protected HealthData createManipulator() {
return new SpongeHealthData();
}
5. Implementowanie ValueProcessors
Not only a DataManipulator
may be offered to a DataHolder
, but also a keyed Value
on its own.
Therefore, you need to provide at least one ValueProcessor
for every Key
present in your
DataManipulator
. A ValueProcessor
is named after the constant name of its Key
in the Keys
class
in a fashion similar to its DataQuery
. The constant name is stripped of underscores, used in upper camel case
and then suffixed with ValueProcessor
.
A ValueProcessor
should always inherit from AbstractSpongeValueProcessor
, which already will handle a
portion of the supports()
checks based on the type of the DataHolder
. For Keys.HEALTH
, we’ll create
and construct HealthValueProcessor
as follows.
public class HealthValueProcessor
extends AbstractSpongeValueProcessor<EntityLivingBase, Double, MutableBoundedValue<Double>> {
public HealthValueProcessor() {
super(EntityLivingBase.class, Keys.HEALTH);
}
[...]
}
Now the AbstractSpongeValueProcessor
will relieve us of the necessity to check if the value is supported.
It is assumed to be supported if the target ValueContainer
is of the type EntityLivingBase
.
Wskazówka
Aby uzyskać dokładniejszą kontrolę nad tym, co obiekty `` EntityLivingBase`` są obsługiwane, metoda `` supports (EntityLivingBase) `` może zostać zastąpiona.
Ponownie, większość prac jest wykonywana przez klasę abstrakcji. Musimy tylko wdrożyć dwie metody pomocnicze do tworzenia wartości `` Value`` i jej niezmiennego odpowiednika oraz trzy metody uzyskiwania, ustawiania i usuwania danych.
@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());
}
Ponieważ nie jest możliwe, aby `` EntityLivingBase`` nie miał zdrowia, ta metoda nigdy nie zwróci `` 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;
}
Metoda `` set () `` zwróci wartość logiczną wskazującą, czy wartość mogła zostać pomyślnie ustawiona. Ta implementacja odrzuci wartości spoza granic używanych w naszych metodach budowania wartości powyżej.
@Override
public DataTransactionResult removeFrom(ValueContainer<?> container) {
return DataTransactionResult.failNoData();
}
Ponieważ dane są zawsze obecne, próby usunięcia go zakończą się niepowodzeniem.
6. Rejestruj Procesory
In order for Sponge to be able to use our manipulators and processors, we need to register them. This is done in the
DataRegistrar
class. In the setupSerialization()
method there are two large blocks of registrations to which we
add our processors.
DataProcessors
`` DataProcessor`` jest rejestrowany obok interfejsu i klas implementacji `` DataManipulator``, który obsługuje. Dla każdej pary zmiennych / niezmiennych `` DataManipulator`` co najmniej jeden `` DataProcessor`` musi być zarejestrowany.
DataUtil.registerDataProcessorAndImpl(HealthData.class, SpongeHealthData.class,
ImmutableHealthData.class, ImmutableSpongeHealthData.class,
new HealthDataProcessor());
ValueProcessors
Value processors are registered at the bottom of the very same function. For each Key
multiple processors
can be registered by subsequent calls of the registerValueProcessor()
method.
DataUtil.registerValueProcessor(Keys.HEALTH, new HealthValueProcessor());
DataUtil.registerValueProcessor(Keys.MAX_HEALTH, new MaxHealthValueProcessor());
Implementowanie bloków danych
Block data is somewhat different from other types of data in that it is implemented by mixing in to the block itself.
There are several methods in org.spongepowered.mixin.core.block.MixinBlock
that must be overridden to implement
data for blocks.
@Mixin(BlockHorizontal.class)
public abstract class MixinBlockHorizontal extends MixinBlock {
[...]
}
supports()
should return true
if either the ImmutableDataManipulator
interface is assignable from the
Class
passed in as the argument, or the superclass supports it.
@Override
public boolean supports(Class<? extends ImmutableDataManipulator<?, ?>> immutable) {
return super.supports(immutable) || ImmutableDirectionalData.class.isAssignableFrom(immutable);
}
getStateWithData()
should return a new BlockState
with the data from the ImmutableDataManipulator
applied
to it. If the manipulator is not directly supported, the method should delegate to the superclass.
@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()
is the equivalent of getStateWithData()
, but works with single Key
s.
@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);
}
W końcu,``getManipulators()`` powinno zwrócić listę dla wszystkich ImmutableDataManipulator
s bloków wsparcia, wraz z obecnymi wartościami dla zapewnionych IBlockState
. Powinno również zawierać wszystkie ImmutableDataManipulator
s z superklas.
@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();
}
Dodatkowe informacje
With Data
being a rather abstract concept in Sponge, it is hard to give general directions on how to
acquire the needed data from the Minecraft classes itself. It may be helpful to take a look at already
implemented processors similar to the one you are working on to get a better understanding of how it should work.
If you are stuck or are unsure about certain aspects, go visit the #spongedev
IRC channel,
the #dev
channel on Discord, the forums, or open up an Issue on GitHub.
Be sure to check the Data Processor Implementation Checklist
for general contribution requirements.