Stored data and commands
Every time a player harvests, you record it in two places.
| Record | API | Where it is saved | Sent to clients |
|---|---|---|---|
| Crystals the player has gained | Entity data | Player save data. Kept after respawning | To that player only |
| Harvest count of this world | World storage | World folder | To all players |
Declarations
In CrystalGardenMod, add the storage ID, its options, and the entity data key.
/** Garden totals of the world, copied to every client so the HUD can show them. */
static final ResourceId GARDEN_STATS = ResourceId.of(MOD_ID, "garden_stats");
static final StorageOptions GARDEN_STATS_OPTIONS = StorageOptions.DEFAULT.syncedToClients();
/** Crystals each player harvested; saved with the player and sent to that player's client. */
static EntityDataKey harvested;
Create the key inside onInitializeCommon. true means it is sent to that player's own client.
harvested = mod.content().entityData("harvested", true);
Record the harvest
Replace the temporary harvest from Growing block.
/** Gives the crystals of a ripe sprout to {@code player} and records the harvest. */
static void harvest(WorldView world, BlockPos pos, PlayerView player) {
world.setProperty(pos, STAGE, 0);
player.give(CrystalGardenMod.CRYSTAL, CRYSTALS_PER_HARVEST);
StoredData mine = player.data(CrystalGardenMod.harvested);
mine.data().putInt("count", mine.data().getInt("count", 0) + CRYSTALS_PER_HARVEST);
mine.markChanged();
StoredData stats = world.storage(CrystalGardenMod.GARDEN_STATS, CrystalGardenMod.GARDEN_STATS_OPTIONS);
stats.data().putInt("harvests", stats.data().getInt("harvests", 0) + 1);
stats.markChanged();
world.playSound(pos, ResourceId.minecraft("block.amethyst_block.chime"), 1.0f, 1.2f);
world.spawnParticles(
ResourceId.minecraft("happy_villager"), pos.x() + 0.5, pos.y() + 0.6, pos.z() + 0.5, 8, 0.3);
}
data()is aDataTreethat holds integers, strings, subtrees, and more. After you change it, callmarkChanged()so it is saved and sent to clients.- The same storage ID is always opened with the same options, wherever you open it. If they differ, you get an
IllegalArgumentException. That is why the options are kept in one constant. - Sounds and particles use vanilla IDs as they are.
Commands and permissions
Create a new file, GardenCommands.java. Anyone can use /garden stats. Only people with the permission can use /garden grow <position>.
final class GardenCommands {
private GardenCommands() {}
static void register(CommandRegistrar commands) {
commands.register(CommandNode.literal("garden")
.then(CommandNode.literal("stats").executes(context -> {
StoredData stats = context.source()
.world()
.storage(CrystalGardenMod.GARDEN_STATS, CrystalGardenMod.GARDEN_STATS_OPTIONS);
int harvests = stats.data().getInt("harvests", 0);
String mine = context.source()
.player()
.map(player -> ", you harvested "
+ player.data(CrystalGardenMod.harvested)
.data()
.getInt("count", 0)
+ " crystal(s)")
.orElse("");
context.source().sendMessage("Crystal Garden: " + harvests + " harvest(s) in this world" + mine);
return harvests;
}))
.then(CommandNode.literal("grow")
.requiresPermission(CrystalGardenMod.growPermission)
.then(CommandNode.argument("pos", CommandArgumentType.blockPos())
.executes(context -> {
if (!CrystalSprout.ripen(context.source().world(), context.getBlockPos("pos"))) {
context.source().sendFailure("There is no crystal sprout there");
return 0;
}
context.source().sendMessage("The crystal sprout is ripe");
return 1;
}))));
}
}
Put ripen in CrystalSprout.
/** Ripens the sprout at {@code pos} at once; false when there is no sprout. */
static boolean ripen(WorldView world, BlockPos pos) {
if (!world.blockAt(pos).equals(CrystalGardenMod.CRYSTAL_SPROUT)) {
return false;
}
return world.setProperty(pos, STAGE, RIPE);
}
In the entrypoint, create the permission node and register the command. Create the permission node before the command.
/** Who may ripen sprouts with {@code /garden grow}: operators (level 2) unless a permission mod says otherwise. */
static PermissionNode growPermission;
// inside onInitializeCommon
growPermission = mod.permissions().node("grow", 2);
GardenCommands.register(mod.commands());
- The node ID is
crystal_garden:grow. The default is operator level 2. A permission provider (a permission management mod) can allow or deny it per player. - The integer a command's execute function returns is the result value that command blocks and
/execute storeread.
Check
In game, place one sprout, ripen it at once with /garden grow <x> <y> <z> (the sprout's coordinates, shown as the targeted block on the F3 screen), then harvest it. /garden stats shows Crystal Garden: 1 harvest(s) in this world, you harvested 3 crystal(s). The numbers are still there after you close and reopen the world.
The command argument types and permission providers are in Commands and permissions. Version conversion of stored data is in Stored data and versions.
Next step
Add the join check.