Skip to content

Instantly share code, notes, and snippets.

@gabizou
Created January 13, 2016 08:47
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save gabizou/4219d260b00fe8624fa2 to your computer and use it in GitHub Desktop.
Save gabizou/4219d260b00fe8624fa2 to your computer and use it in GitHub Desktop.
package org.spongepowered.cookbook.plugin;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.base.Objects;
import org.spongepowered.api.Sponge;
import org.spongepowered.api.data.DataContainer;
import org.spongepowered.api.data.DataHolder;
import org.spongepowered.api.data.manipulator.mutable.common.AbstractData;
import org.spongepowered.api.data.merge.MergeFunction;
import org.spongepowered.api.data.value.mutable.MutableBoundedValue;
import org.spongepowered.api.data.value.mutable.Value;
import java.util.Optional;
public class FakeData extends AbstractData<FakeData, ImmutableFakeData> {
private int level;
private String name;
private boolean enabled;
public FakeData() {
this(0, "", false);
}
public FakeData(int level, String name, boolean enabled) {
this.level = level;
this.name = name;
this.enabled = enabled;
}
public Value<Boolean> fakeBool() {
return Sponge.getRegistry().getValueFactory().createValue(FakeKeys.FAKE_BOOL, false, this.enabled);
}
public Value<String> fakeString() {
return Sponge.getRegistry().getValueFactory().createValue(FakeKeys.FAKE_STRING, "", this.name);
}
public MutableBoundedValue<Integer> fakeInt() {
return Sponge.getRegistry().getValueFactory().createBoundedValueBuilder(FakeKeys.FAKE_BOUNDED)
.defaultValue(0)
.minimum(0)
.maximum(20)
.actualValue(this.level)
.build();
}
@Override
protected void registerGettersAndSetters() {
registerFieldGetter(FakeKeys.FAKE_BOOL, () -> this.enabled);
registerFieldSetter(FakeKeys.FAKE_BOOL, value -> this.enabled = value);
registerKeyValue(FakeKeys.FAKE_BOOL, this::fakeBool);
registerFieldGetter(FakeKeys.FAKE_BOUNDED, () -> this.level);
registerFieldSetter(FakeKeys.FAKE_BOUNDED, value -> this.level = value);
registerKeyValue(FakeKeys.FAKE_BOUNDED, this::fakeInt);
registerFieldGetter(FakeKeys.FAKE_STRING, () -> this.name);
registerFieldSetter(FakeKeys.FAKE_STRING, value -> this.name = checkNotNull(value));
registerKeyValue(FakeKeys.FAKE_STRING, this::fakeString);
}
@Override
public Optional<FakeData> fill(DataHolder dataHolder, MergeFunction overlap) {
return Optional.empty(); // Yes, this should be implemented properly, but it isn't necessary currently.
}
@Override
public Optional<FakeData> from(DataContainer container) {
if (!container.contains(FakeKeys.FAKE_STRING.getQuery(), FakeKeys.FAKE_BOOL.getQuery(), FakeKeys.FAKE_BOUNDED.getQuery())) {
return Optional.empty();
}
final String string = container.getString(FakeKeys.FAKE_STRING.getQuery()).get();
final boolean bool = container.getBoolean(FakeKeys.FAKE_BOOL.getQuery()).get();
final int level = container.getInt(FakeKeys.FAKE_BOUNDED.getQuery()).get();
this.name = string;
this.enabled = bool;
this.level = level;
return Optional.of(this);
}
@Override
public FakeData copy() {
return new FakeData(this.level, this.name, this.enabled);
}
@Override
public ImmutableFakeData asImmutable() {
return new ImmutableFakeData(this.level, this.name, this.enabled);
}
@Override
public int compareTo(FakeData o) {
return 0;
}
@Override
public int getContentVersion() {
return 1;
}
@Override
public DataContainer toContainer() {
return super.toContainer()
.set(FakeKeys.FAKE_STRING, this.name)
.set(FakeKeys.FAKE_BOOL, this.enabled)
.set(FakeKeys.FAKE_BOUNDED, this.level);
}
@Override
public String toString() {
return Objects.toStringHelper(this)
.add("level", this.level)
.add("name", this.name)
.add("enabled", this.enabled)
.toString();
}
}
package org.spongepowered.cookbook.plugin;
import org.spongepowered.api.data.DataHolder;
import org.spongepowered.api.data.DataView;
import org.spongepowered.api.data.manipulator.DataManipulatorBuilder;
import org.spongepowered.api.util.persistence.InvalidDataException;
import java.util.Optional;
public class FakeDataManipulatorBuilder implements DataManipulatorBuilder<FakeData, ImmutableFakeData> {
@Override
public FakeData create() {
return new FakeData();
}
@Override
public Optional<FakeData> createFrom(DataHolder dataHolder) {
return Optional.of(dataHolder.get(FakeData.class).orElse(new FakeData()));
}
@Override
public Optional<FakeData> build(DataView container) throws InvalidDataException {
// Note that this should check the Queries.CONTENT_VERSION, but for the sake of demonstration
// it's not necessary
if (container.contains(FakeKeys.FAKE_BOOL, FakeKeys.FAKE_BOUNDED, FakeKeys.FAKE_STRING)) {
final boolean fakeBool = container.getBoolean(FakeKeys.FAKE_BOOL.getQuery()).get();
final int level = container.getInt(FakeKeys.FAKE_BOUNDED.getQuery()).get();
final String fakeString = container.getString(FakeKeys.FAKE_STRING.getQuery()).get();
return Optional.of(new FakeData(level, fakeString, fakeBool));
}
return Optional.empty();
}
}
package org.spongepowered.cookbook.plugin;
import org.spongepowered.api.Sponge;
import org.spongepowered.api.command.CommandException;
import org.spongepowered.api.command.CommandResult;
import org.spongepowered.api.command.CommandSource;
import org.spongepowered.api.command.args.CommandContext;
import org.spongepowered.api.command.args.GenericArguments;
import org.spongepowered.api.command.spec.CommandExecutor;
import org.spongepowered.api.command.spec.CommandSpec;
import org.spongepowered.api.entity.living.player.Player;
import org.spongepowered.api.event.Listener;
import org.spongepowered.api.event.game.state.GameInitializationEvent;
import org.spongepowered.api.event.game.state.GamePreInitializationEvent;
import org.spongepowered.api.plugin.Plugin;
import org.spongepowered.api.text.Text;
import java.util.Optional;
import javax.annotation.Resource;
@Plugin(id = "spongetest", name = "SpongeTest", version = "0.1")
@Resource
public class WorldsTest {
@Listener
public void onPreInit(GamePreInitializationEvent event) {
Sponge.getDataManager().register(FakeData.class, ImmutableFakeData.class, new FakeDataManipulatorBuilder());
}
@Listener
public void onGameInit(GameInitializationEvent event) {
CommandSpec skillDataSpec = CommandSpec.builder()
.description(Text.of("Applies skill data"))
.arguments(GenericArguments.optional(GenericArguments.onlyOne(GenericArguments.player(Text.of("player")))))
.executor(new SkillDataExecturo())
.build();
CommandSpec skillValidate = CommandSpec.builder()
.description(Text.of("Validates skill data"))
.arguments(GenericArguments.optional(GenericArguments.onlyOne(GenericArguments.player(Text.of("player")))))
.executor(new SkillValidator())
.build();
Sponge.getGame().getCommandManager().register(this, skillValidate, "validateData");
Sponge.getGame().getCommandManager().register(this, skillDataSpec, "fakeData");
}
public static class SkillDataExecturo implements CommandExecutor {
@Override
public CommandResult execute(CommandSource src, CommandContext args) throws CommandException {
Optional<Player> target = args.getOne("player");
if (target.isPresent()) {
Player player = target.get();
player.offer(new FakeData(1000, "Three Hundred", false));
} else {
if (src instanceof Player) {
Player player = (Player) src;
player.offer(new FakeData(1000, "Three Hunderd", false));
}
}
return CommandResult.success();
}
}
public static class SkillValidator implements CommandExecutor {
@Override
public CommandResult execute(CommandSource src, CommandContext args) throws CommandException {
Optional<Player> target = args.getOne("player");
if (target.isPresent()) {
Player player = target.get();
Optional<FakeData> optional = player.get(FakeData.class);
if (optional.isPresent()) {
src.sendMessage(Text.of("Data available!"));
System.out.println(optional.get().toString());
}
} else {
if (src instanceof Player) {
Player player = (Player) src;
Optional<FakeData> optional = player.get(FakeData.class);
if (optional.isPresent()) {
src.sendMessage(Text.of("Data available!"));
System.out.println(optional.get().toString());
}
}
}
return CommandResult.success();
}
}
}
package org.spongepowered.cookbook.plugin;
import static org.spongepowered.api.data.DataQuery.of;
import static org.spongepowered.api.data.key.KeyFactory.makeSingleKey;
import org.spongepowered.api.data.key.Key;
import org.spongepowered.api.data.value.mutable.MutableBoundedValue;
import org.spongepowered.api.data.value.mutable.Value;
public class FakeKeys {
public static final Key<Value<Boolean>> FAKE_BOOL = makeSingleKey(Boolean.class, Value.class, of("FakeBool"));
public static final Key<Value<String>> FAKE_STRING = makeSingleKey(String.class, Value.class, of("FakeString"));
public static final Key<MutableBoundedValue<Integer>> FAKE_BOUNDED = makeSingleKey(Integer.class, MutableBoundedValue.class, of("FakeInteger"));
}
package org.spongepowered.cookbook.plugin;
import com.google.common.collect.ComparisonChain;
import org.spongepowered.api.Sponge;
import org.spongepowered.api.data.DataContainer;
import org.spongepowered.api.data.MemoryDataContainer;
import org.spongepowered.api.data.key.Key;
import org.spongepowered.api.data.manipulator.immutable.common.AbstractImmutableData;
import org.spongepowered.api.data.value.BaseValue;
import org.spongepowered.api.data.value.immutable.ImmutableBoundedValue;
import org.spongepowered.api.data.value.immutable.ImmutableValue;
import java.util.Optional;
public class ImmutableFakeData extends AbstractImmutableData<ImmutableFakeData, FakeData> {
private final int level;
private final String name;
private final boolean enabled;
public ImmutableFakeData() {
this(0, "", false);
}
public ImmutableFakeData(int level, String name, boolean enabled) {
this.level = level;
this.name = name;
this.enabled = enabled;
}
public ImmutableValue<Boolean> fakeBool() {
return Sponge.getRegistry().getValueFactory().createValue(FakeKeys.FAKE_BOOL, false, this.enabled).asImmutable();
}
public ImmutableValue<String> fakeString() {
return Sponge.getRegistry().getValueFactory().createValue(FakeKeys.FAKE_STRING, "", this.name).asImmutable();
}
public ImmutableBoundedValue<Integer> fakeInt() {
return Sponge.getRegistry().getValueFactory().createBoundedValueBuilder(FakeKeys.FAKE_BOUNDED)
.defaultValue(0)
.minimum(0)
.maximum(20)
.actualValue(this.level)
.build()
.asImmutable();
}
@Override
protected void registerGetters() {
registerFieldGetter(FakeKeys.FAKE_BOOL, this::isEnabled);
registerKeyValue(FakeKeys.FAKE_BOOL, this::fakeBool);
registerFieldGetter(FakeKeys.FAKE_BOUNDED, this::getLevel);
registerKeyValue(FakeKeys.FAKE_BOUNDED, this::fakeInt);
registerFieldGetter(FakeKeys.FAKE_STRING, this::getName);
registerKeyValue(FakeKeys.FAKE_STRING, this::fakeString);
}
@Override
public <E> Optional<ImmutableFakeData> with(Key<? extends BaseValue<E>> key, E value) {
return Optional.empty();
}
@Override
public FakeData asMutable() {
return new FakeData(this.level, this.name, this.enabled);
}
@Override
public int compareTo(ImmutableFakeData o) {
return ComparisonChain.start()
.compare(o.enabled, this.enabled)
.compare(o.level, this.level)
.compare(o.name, this.name)
.result();
}
@Override
public int getContentVersion() {
return 1;
}
@Override
public DataContainer toContainer() {
return new MemoryDataContainer()
.set(FakeKeys.FAKE_STRING, this.name)
.set(FakeKeys.FAKE_BOOL, this.enabled)
.set(FakeKeys.FAKE_BOUNDED, this.level);
}
private int getLevel() {
return this.level;
}
private String getName() {
return this.name;
}
private boolean isEnabled() {
return this.enabled;
}
}
@ryantheleach
Copy link

@resource
public class WorldsTest {

  1. you have WorldsTest not FakeData.
  2. What does the Resource annotation do?

@ryantheleach
Copy link

createValue element and default is backwards?

@ryantheleach
Copy link

Saladoc also pointed out that the comparison chain is likely backwards.
o might need to be swapped with this.

@JamieS1211
Copy link

Line 6 FakeDataManipulatorBuilder should

import org.spongepowered.api.util.persistence.InvalidDataException;

be

import org.spongepowered.api.data.persistence.InvalidDataException;

@theboomer
Copy link

To spare others the same degree of hairloss I have suffered:
This example works if you access the whole data, but it will not work for actually getting the Values by using keys. Data can be offered, and successfully added, and visibly there... but not retrievable via the Value based retrieval methods.

The method registerGettersAndSetters() in the data class is not used, and must be called in the constructor.
Likewise, the method registerGetters() in the immutable data class is not used, and must be called in its constructor.

With those additions, you then will actually be able to get the Values with key access, or .getValues() methods on the data.

@Daniel12321
Copy link

Is there any way to add custom data to BlockState's ?

@ryantheleach
Copy link

@Daniel12321 Not really no.

TileEntities you can, because there is an arbitrary NBT structure attached to them.
BlockStates get optimized down to a map of BlockStates to integers so they can be stored in memory efficiently.
So the block traits that Mojang register end up getting enumerated and turned into singletons representing the type of each block.

The closest thing you can do to attach data to locations is to create your own store assuming it's sparse and accessed infrequently, you could get away with a database, otherwise you are basically going to be stuck reimplementing the chunk structure and storing your data in that.

One option might be to allow storing data on chunks, referenced by location, in a similar vein to tile entities / the current block tracking solution. But it's hairy and probably best handled by the Sponge team at a later date once everything else that is currently in the API is working and implemented.

@ryantheleach
Copy link

I forked this for others trying to follow along in the comments.

https://gist.github.com/ryantheleach/6d3c7d8ea5cddde47d426183e120e6d7

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment