Menerapkan datamanipulator
Peringatan
These docs were written for SpongeAPI 7 and are likely out of date. If you feel like you can help update them, please submit a PR!
Ini merupakan petunjuk bagi para kontributor yang ingin membantu pelaksanaan Data API dengan membuat DataManipulators. Daftar DataManipulators yang telah terupdate yang akan dijalankan bisa di temukan di 'SpongeCommon Issue #8 <https://github.com/SpongePowered/SpongeCommon/issues/8>'_.
To fully implement a DataManipulator these steps must be followed:
Terapkan ' ' datamanipulator ' ' sendiri
Implement the ImmutableDataManipulator
Bila langkah-langkah ini telah selesai, berikut juga harus dilakukan:
Register the Key in the
KeyRegistryModule
Terapkan ' ' datamanipulator ' '
Implement the
ValueProcessor
for each Value being represented by theDataManipulator
Jika data berlaku untuk blok, beberapa metode juga harus dicampur ke blok.
Catatan
Pastikan anda mengukiti ../petunjuk kami.
The following snippet shows the imports/paths for some classes in SpongeCommon that you will need:
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. Terapkan Datamanipulator
The naming convention for DataManipulator
implementations is the name of the interface prefixed with "Sponge".
So to implement the HealthData interface, we create a class named SpongeHealthData
in the appropriate package.
For implementing the DataManipulator
first have it extend an appropriate abstract class from the
org.spongepowered.common.data.manipulator.mutable.common
package. The most generic there is AbstractData
but there are also abstractions that reduce boilerplate code even more for some special cases like
DataManipulator
s only containing a single value.
public class SpongeHealthData extends AbstractData<HealthData, ImmutableHealthData> implements HealthData {
[...]
}
Ada dua jenis argumen untuk AbstractData kelas. Yang pertama adalah interface yang diimplementasikan oleh kelas ini, yang kedua adalah antarmuka yang dilaksanakan oleh yang bersangkutan, ImmutableDataManipulator.
Pembuatnya
Dalam kebanyakan kasus saat menerapkan abstrak ' ' Datamanipulator ' ' kamu harus memiliki dua konstruktor:
Satu tanpa argumen (tidak-args) yang akan memanggil konstruktor kedua dengan "gagal" nilai
Konstruktor kedua yang mengambil semua nilai yang didukungnya.
Konstruktor kedua harus
lakukan panggilan ke
AbstrakData
konstruktor, melewati tahapan rujukan untuk pelaksanaan antar muka.pastikan nilai yang dilewatkan benar
hubungi
registerGettersAndSetters()
metode
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();
}
[...]
}
Since we know that both current health and maximum health are bounded values, we need to make sure no values
outside of these bounds can be passed. To achieve this, we use guava's Preconditions
of which we import the
required methods statically.
Catatan
Never use so-called magic values (arbitrary numbers, booleans etc.) in your code. Instead, locate the
DataConstants
class and use a fitting constant - or create one, if necessary.
Para pengakses ditentukan oleh Antarmuka
The interface we implement specifies some methods to access Value objects. For HealthData
, those are
HealthData#health() and HealthData#maxHealth(). Every call to those should yield a new Value
.
public MutableBoundedValue<Double> health() {
return SpongeValueFactory.boundedBuilder(Keys.HEALTH)
.minimum(0)
.maximum(this.maxHealth)
.defaultValue(this.maxHealth)
.actualValue(this.health)
.build();
}
Tip
Karena Double
adalah Comparable
, kita tidak perlu secara eksplisit menentukan komparator.
If no current value is specified, calling BaseValue#get() on the Value
returns the default value.
Penyalinan dan serialisasi
The two methods DataManipulator#copy() and DataManipulator#asImmutable() are not much work to implement. For both you just need to return a mutable or an immutable data manipulator respectively, containing the same data as the current instance.
The method DataSerializable#toContainer() is used for serialization purposes. Use
DataContainer#createNew() as the result and apply to it the values stored within this instance.
A DataContainer is basically a map mapping DataQuerys to values. Since a Key always
contains a corresponding DataQuery
, just use those by passing the Key
directly.
public DataContainer toContainer() {
return super.toContainer()
.set(Keys.HEALTH, this.health)
.set(Keys.MAX_HEALTH, this.maxHealth);
}
registerGettersAndSetters()
Metode toContainer() digunakan untuk serialisasi tujuan. Menggunakan MemoryDataContainer sebagai hasil dan menerapkan nilai-nilai yang disimpan dalam contoh ini. DataContainer pada dasarnya adalah sebuah peta pemetaan DataQuerys untuk nilai-nilai. Sejak a Kunci selalu berisi sesuai DataQuery, hanya digunakan oleh orang-orang yang lewat Kunci ini diatas:
register a Supplier to directly get the value
register a Consumer to directly set the value
daftarkan
Supplier<Value>
untuk mendapatkanNilai
yang berubah-ubah
Supplier
dan Consumer
merupakan penghubung fungsional, sehingga Java 8 Lambdas dapat digunakan.
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);
}
The Consumer
registered as field setter must perform the adequate checks to make sure the supplied value is valid.
This applies especially for DataHolders which won't accept negative values. If a value is invalid, an
IllegalArgumentException
should be thrown.
Tip
The validity criteria for those setters are the same as for the respective Value
object, so you might delegate
the validity check to a call of this.health().set()
and just set this.health = value
if the first
line has not thrown an exception yet.
Itu dia, ' ' Datamanipulator ' ' harus dilakukan sekarang.
2. Terapkan ImmutableDataManipulator
Implementing the ImmutableDataManipulator is similar to implementing the mutable one.
Satu-satunya perbedaan adalah:
Nama kelas dibentuk oleh awalan nama
DataManipulator
s yang bisa dapat diubah denganImmutableSponge
Mewarisi dari
ImmutableAbstractData
sebagai gantinyaGanti dari
registerGettersAndSetters()
, metode ini disebutregisterGetters()
When creating ImmutableDataHolder
s or ImmutableValue
s, check if it makes sense to use the
ImmutableDataCachingUtil
. For example, if you have WetData
which contains nothing more than a boolean, it
is more feasible to retain only two cached instances of ImmutableWetData
- one for each possible value. For
manipulators and values with many possible values (like SignData
) however, caching is proven to be too expensive.
Tip
Anda harus mendeklarasikan bagian dari ImmutableDataManipulator
sebagai final
untuk mencegah perubahan yang tidak disengaja.
3. Register the Key in the KeyRegistryModule
The next step is to register your Keys to the Keys. To do so, locate the
KeyRegistryModule
class and find the registerDefaults()
method.
There add a line to register (and create) your used keys.
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. Terapkan DataProcessors
Berikutnya adalah DataProcessor. DataProcessor berfungsi sebagai jembatan antara kita DataManipulator dan Minecraft benda-benda. Setiap kali ada data yang diminta atau ditawarkan untuk DataHolders yang ada di Minecraft Vanilla, panggilan-panggilan itu berakhir menjadi didelegasikan ke DataProcessor atau ValueProcessor.
For your name, you should use the name of the DataManipulator
interface and append Processor
. Thus, for
HealthData
we create a HealthDataProcessor
.
Dalam rangka untuk mengurangi kode boilerplate, DataProcessor harus mewarisi dari yang sesuai kelas abstrak dalam org.spongepowered.umum.data.prosesor.umum paket. Karena kesehatan hanya dapat hadir pada badan-badan tertentu, kita dapat menggunakan AbstractEntityDataProcessor yang khusus ditujukan pada Entitas yang didasarkan pada net.minecraft.entitas.Entitas. AbstractEntitySingleDataProcessor akan memerlukan waktu kurang pelaksanaan pekerjaan, tetapi tidak dapat digunakan sebagai HealthData berisi lebih dari satu nilai.
public class HealthDataProcessor
extends AbstractEntityDataProcessor<EntityLivingBase, HealthData, ImmutableHealthData> {
public HealthDataProcessor() {
super(EntityLivingBase.class);
}
[...]
}
Tergantung pada abstraksi yang anda gunakan, metode yang anda harus menerapkan mungkin sangat berbeda, tergantung pada seberapa jauh pelaksanaan pekerjaan sudah bisa dilakukan di kelas abstrak. Umumnya, metode-metode yang dapat dikategorikan.
Tip
It is possible to create multiple DataProcessor
s for the same data. If vastly different DataHolder
s
should be supported (for example both a TileEntity
and a matching ItemStack
), it may be beneficial to
create one processor for each type of DataHolder
in order to make full use of the provided abstractions.
Make sure you follow the package structure for items, tileentities and entities.
Metode validasi
Always return a boolean value. If any of the supports(target)
methods is called it should perform a general check if
the supplied target generally supports the kind of data handled by our DataProcessor
. Based on your level of
abstraction you might not have to implement it at all, if you have to just implement the most specific one, as the more
generic ones usually delegate to them.
Untuk HealthDataProcessor
kami Support()
dijalankan oleh AbstractEntityDataProcessor
. Per default, hal tersebut akan kembali benar jika argumen yang diberikan merupakan turunan dari kelas yang telah ditentukan saat memanggil super()
konstruktor.
Sebagai gantinya, kita diminta untuk menyediakan metode doesDataExist()
. Karena abstraksi tidak tahu bagaimana mendapatkan data, hal tersebut membiarkan fungsi ini dijalankan. Seperti nama nya, metode tersebut harus memeriksa apakah data sudah ada pada target yang didukung. Untuk HealthDataProcessor
, hal ini selalu kembali benar, karena setiap entitas yang hidup selalu memiliki kesehatan.
@Override
protected boolean doesDataExist(EntityLivingBase entity) {
return true;
}
Metode Pengaturan
Metode pengaturan menerima DataHolder
dari beberapa jenis dan beberapa data yang harus dijalankan padanya, jika memungkinkan.
Antarmuka DataProcessor
mendefinisikan sebuah set()
metode menerima``DataHolder`` dan DataManipulator
yang mengembalikan DataTransactionResult
. Bergantung pada kelas abstraksi yang digunakan, beberapa fungsi yang diperlukan mungkin sudah diterapkan.
Pada kasus ini, AbstractEntitydataProcessor
mengurus sebagian besar proses dan hanya membutuhkan sebuah metode untuk mengeset sebagian nilai agar kembali true
jika metode tersebut sukses dijalankan dan false
jika tidak sukses. Semua pengecekan jika DataHolder
mendukung Data
akan ditangani, kelas abstrak hanya akan memberikan peta tiap Key
dari DataManipulator
ke nilainya dan kemudian menyusun DataTransactionResult
tergantung apakah operasi berhasi dijalankan atau tidak.
@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;
}
Tip
To understand DataTransactionResults, check the corresponding docs page and refer to the DataTransactionResult.Builder docs to create one.
Peringatan
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.
Metode penghapusan
Metode remove()
mencoba untuk menghapus data dari DataHolder
dan mengembalikan 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();
}
Metode Getter
Metode getter mendapatkan data dari DataHolder
dan mengembalikan``DataManipulator`` opsional. Antarmuka DataProcessor
menentukan metode dari()
dan createFrom()
, perbedaannya adalah bahwa dari()
akan kembali Opsional.empty()
jika dudukan data kompatibel, namun saat ini tidak berisi data, sementara createFrom()
akan memberikan DataManipulator
memegang nilai bawaan dalam kasus itu.
Sekali lagi, AbstractEntityDataProcessor
akan menyediakan sebagian besar pengerjaan untuk prosess ini dan hanya membutuhkan satu metode untuk mendapatkan nilai sebenarnya yang tersedia di DataHolder
. Metode ini hanya di butuhkan setelah supports()
dan doesDataExist()
keduanya telah kembali benar, artinya metode tersebut bekerja dengan anggapan bahwa data tersedia.
Peringatan
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);
}
Metode penyaringan
Metode pengisian berbeda dari metode getter ketika menerima DataManipulator
untuk diisi dengan nilai-nilai. Nilai-nilai tersebut bisa datang dari DataHolder
atau harus diserialkan kembali dari DataContainer
. Metode ini mengembalikan Optional.empty()
jika DataHolder
tidak sesuai.
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
.
Metode lain
Tergantung pada abstract superclass yang digunakan, beberapa metode lain mungkin akan di butuhkan. Sebagai contoh, AbstractentityDataProcessor
perlu membuat DataManipulator
secara instan di beberapa titik. Hal ini tidak bisa di lakukan karena metode tersebut tidak mengetahui kelompok pelaksana juga tidak mengetahui konstruktor yang akan di gunakan. Oleh sebab itu metode ini akan menggunakan fungsi abstrak yang harus disediakan oleh pelaksana akhir. Hal ini hanya membuat DataManipulator
dengan data bawaan.
Jika anda menjalankan DataManipulator
anda sesuai dengan yang di rekomendasikan, anda hanya perlu menggunakan no-args konstruktor.
@Override
protected HealthData createManipulator() {
return new SpongeHealthData();
}
5. Terapkan ValueProcessors
Tidak hanya DataManipulator
yang mungkin di tawarkan kepada DataHolder
, tetapi juga kelengkapan Value
dengan sendiri nya. Untuk itu, anda harus menyediakan minimal satu ValueProcessor
untuk setiap Key
yang ada di DataManipulator
anda. Sebuah ValueProcessor
dinamai mengikuti nama tetap setiap Key
di dalam kelompok Keys
dengan bentuk yang sama dengan DataQuery
. Nama tetap tersebut dihilangkan garis bawah, digunakan dalam bentuk huruf besar dan di gabungkan dengan ValueProcessor
.
ValueProcessor
harus selalu di ambil dari AbstractSpongeValueProcessor
, yang akan memproses sebagian pengecekan Support()
sesuai dengan jenis DataHolder
. Untuk Key.HEALTH
, kita akan membuat dan mengkonstruk HealthValueProcessor
sebagai berikut.
public class HealthValueProcessor
extends AbstractSpongeValueProcessor<EntityLivingBase, Double, MutableBoundedValue<Double>> {
public HealthValueProcessor() {
super(EntityLivingBase.class, Keys.HEALTH);
}
[...]
}
Sekarang AbstractSpongeValueProcessor
akan menggantikan kita dari keharusan mengecek apakah nilai telah didukung. Hal ini akan di asumsikan telah didukung jika target ValueContainer
adalah jenis EntityLivingBase
.
Tip
Untuk lebih memudahkan pengendalian terhadap objek EntityLivingBase
yang telah didukung, metode support(EntityLivingBase)
bisa diganti.
Sekali lagi, kebanyakan proses telah dilakukan oleh bagian abstraksi. Kita hanya perlu menjalankan dua metode pembantu untuk membuat value
dan mitra tetapnya dan tiga metode untuk mendapatkan, mengatur dan menghapus data.
@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());
}
Dikarenakan tidak mungkin sebuah EntityLivingBase
tidak memiliki kesehatan, metode ini tidak akan pernah kembali ke 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;
}
Metode set()
akan mengembalikan nilai boolean yang akan mengindikasikan apakah nilai dapat berhasil diatur. Melakukan proses ini akan menolak nilai di luar batas-batas yang telah digunakan didalam metode konstruksi nilai kami di atas.
@Override
public DataTransactionResult removeFrom(ValueContainer<?> container) {
return DataTransactionResult.failNoData();
}
Karena data dijamin selalu hadirkan, usaha untuk menghapusnya selalu gagal.
6. Daftar prosesor
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.
Dataprosesor
DataProcessor
terdaftar bersama kelas penghubung dan pelaksana dari DataManipulator
yang ditanganinya. Untuk setiap DataManipulator
s yang tidak kekal/kekal minimal satu DataProcessor
harus terdaftar.
DataUtil.registerDataProcessorAndImpl(HealthData.class, SpongeHealthData.class,
ImmutableHealthData.class, ImmutableSpongeHealthData.class,
new HealthDataProcessor());
Nilaiprosesor
Nilai prosesor terdaftar dibagian bawah dari fungsi yang sama. Untuk setiap Key
beberapa prosesor bisa didaftarkan dengan beberapa panggilan dari metode registeryValueProcessor()
.
DataUtil.registerValueProcessor(Keys.HEALTH, new HealthValueProcessor());
DataUtil.registerValueProcessor(Keys.MAX_HEALTH, new MaxHealthValueProcessor());
Melakukan data blok
Blok data berbeda-beda dari satu jenis dengan yang lain dimana hal tersebut di jalankan dengan mencampurkan blok tersebut dengan blok itu sendiri. Ada beberapa metode di org.spongepowered.mixin.core.block.MixinBlock
yang harus diganti untuk menjalankan data untuk setiap blok.
@Mixin(BlockHorizontal.class)
public abstract class MixinBlockHorizontal extends MixinBlock {
[...]
}
supports()
harus kembali benar
jika baik penghubung ImmutableDataManipulator
bisa dialihkan dari Class
yang dilalui sebagai argumen, atau superkelas mendukungnya.
@Override
public boolean supports(Class<? extends ImmutableDataManipulator<?, ?>> immutable) {
return super.supports(immutable) || ImmutableDirectionalData.class.isAssignableFrom(immutable);
}
getStateWithData()
harus kembali baru BlockState
dengan data dari ImmutableDataManipulator
yang dijalankan untuk nya. Jika manipolator tidak didukung secara langsung, metode harus didelegasikan kepada 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()
sama dengan get StateWithData()
, tetapi bekerja dengan satu 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);
}
Pada akhirnya, getManipulators()
harus mengembalikan dafter semua immutableDataManipulator
s blok yang didukung, bersamaan dengan nial sekarang untuk disajikan IBlockState
. Data tersebut harus mencakup semua ImmutableDataManipulator
s yang berasal dari superclass.
@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();
}
Informasi lebih lanjut
Dengan Data
yang merupakan konsep yang sedikit abstrak didalam Sponge, hal tersebut mempersulit untuk memberikan petunjuk umum tentang cara memperoleh data yang dibutuhkan dari kelas-kelas Minecraft itu sendiri. Mungkin akan membantu dengan memperhatikan prosesor yang serupa dengan yang anda kerjakan yang telah dijalankan untuk mendapatkan pemahaman yang lebih baik tentang bagaimana prosesor tersebut mestinya bekerja.
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.