Skip to content

Commit

Permalink
Add Codecs for IChunkEnergy, IMaxHealth and IPigSpawner implementations
Browse files Browse the repository at this point in the history
- Refactor SerializableCapabilityProvider to use Codec for serialisation
  - The class itself still implements INBTSerializable as required by CapabilityProvider
- Refactor SimpleCapabilityProvider to use LazyOptional directly
  • Loading branch information
Choonster committed Jun 21, 2024
1 parent 18905c2 commit c00e0ac
Show file tree
Hide file tree
Showing 11 changed files with 152 additions and 115 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package choonster.testmod3.capability;

import com.google.common.base.Preconditions;
import com.mojang.serialization.Codec;
import net.minecraft.core.Direction;
import net.minecraft.core.HolderLookup;
import net.minecraft.nbt.NbtOps;
import net.minecraft.nbt.Tag;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.capabilities.ICapabilityProvider;
Expand All @@ -11,37 +12,43 @@

/**
* A simple implementation of {@link ICapabilityProvider} and {@link INBTSerializable} that supports a single {@link Capability} handler instance.
* <p>
* The handler instance must implement {@link INBTSerializable}.
*
* @author Choonster
*/
public class SerializableCapabilityProvider<HANDLER> extends SimpleCapabilityProvider<HANDLER> implements INBTSerializable<Tag> {
private final INBTSerializable<Tag> serializableInstance;
public class SerializableCapabilityProvider<HANDLER, IMPLEMENTATION extends HANDLER>
extends SimpleCapabilityProvider<HANDLER, IMPLEMENTATION> implements INBTSerializable<Tag> {
private final Codec<IMPLEMENTATION> instanceCodec;

/**
* Create a provider for the specified handler instance.
*
* @param capability The Capability instance to provide the handler for
* @param facing The Direction to provide the handler for
* @param instance The handler instance to provide
* @param capability The Capability instance to provide the handler for
* @param facing The Direction to provide the handler for
* @param defaultInstance The default handler instance to provide
* @param instanceCodec The codec for the instance type
*/
@SuppressWarnings("unchecked")
public SerializableCapabilityProvider(final Capability<HANDLER> capability, @Nullable final Direction facing, final HANDLER instance) {
super(capability, facing, instance);

Preconditions.checkArgument(instance instanceof INBTSerializable, "instance must implement INBTSerializable");

serializableInstance = (INBTSerializable<Tag>) instance;
public SerializableCapabilityProvider(
final Capability<HANDLER> capability,
@Nullable final Direction facing,
final IMPLEMENTATION defaultInstance,
final Codec<IMPLEMENTATION> instanceCodec
) {
super(capability, facing, defaultInstance);
this.instanceCodec = instanceCodec;
}

@Override
public Tag serializeNBT(final HolderLookup.Provider registries) {
return serializableInstance.serializeNBT(registries);
final var ops = registries.createSerializationContext(NbtOps.INSTANCE);

return instanceCodec.encodeStart(ops, getInstance()).getOrThrow();
}

@Override
public void deserializeNBT(final HolderLookup.Provider registries, final Tag tag) {
serializableInstance.deserializeNBT(registries, tag);
final var ops = registries.createSerializationContext(NbtOps.INSTANCE);

final var instance = instanceCodec.parse(ops, tag).getOrThrow();
replaceInstance(instance);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package choonster.testmod3.capability;

import choonster.testmod3.util.CapabilityNotPresentException;
import com.google.common.base.Preconditions;
import net.minecraft.core.Direction;
import net.minecraftforge.common.capabilities.Capability;
Expand All @@ -10,10 +11,11 @@
/**
* A simple implementation of {@link ICapabilityProvider} that supports a single {@link Capability} handler instance.
*
* @param <HANDLER> The capability handler type
* @param <IMPLEMENTATION> The handler implementation type
* @author Choonster
*/
public class SimpleCapabilityProvider<HANDLER> implements ICapabilityProvider {

public class SimpleCapabilityProvider<HANDLER, IMPLEMENTATION extends HANDLER> implements ICapabilityProvider {
/**
* The {@link Capability} instance to provide the handler for.
*/
Expand All @@ -22,24 +24,19 @@ public class SimpleCapabilityProvider<HANDLER> implements ICapabilityProvider {
/**
* The {@link Direction} to provide the handler for.
*/
@Nullable
protected final Direction facing;

/**
* The handler instance to provide.
*/
protected final HANDLER instance;

/**
* A lazy optional containing handler instance to provide.
*/
protected final LazyOptional<HANDLER> lazyOptional;
private LazyOptional<IMPLEMENTATION> instanceOptional;

public SimpleCapabilityProvider(final Capability<HANDLER> capability, @Nullable final Direction facing, final HANDLER instance) {
public SimpleCapabilityProvider(final Capability<HANDLER> capability, @Nullable final Direction facing, final IMPLEMENTATION instance) {
this.capability = Preconditions.checkNotNull(capability, "capability");
this.facing = facing;
this.instance = Preconditions.checkNotNull(instance, "instance");

lazyOptional = LazyOptional.of(() -> this.instance);
instanceOptional = LazyOptional.of(() -> instance);
}

/**
Expand All @@ -54,7 +51,7 @@ public SimpleCapabilityProvider(final Capability<HANDLER> capability, @Nullable
*/
@Override
public <T> LazyOptional<T> getCapability(final Capability<T> capability, @Nullable final Direction facing) {
return getCapability().orEmpty(capability, lazyOptional);
return capability == getCapability() ? instanceOptional.cast() : LazyOptional.empty();
}

/**
Expand All @@ -81,7 +78,12 @@ public Direction getFacing() {
*
* @return A lazy optional containing the handler instance
*/
public final HANDLER getInstance() {
return instance;
protected final IMPLEMENTATION getInstance() {
return instanceOptional.orElseThrow(CapabilityNotPresentException::new);
}

protected final void replaceInstance(final IMPLEMENTATION newInstance) {
instanceOptional.invalidate();
instanceOptional = LazyOptional.of(() -> newInstance);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import choonster.testmod3.TestMod3;
import choonster.testmod3.api.capability.chunkenergy.IChunkEnergy;
import choonster.testmod3.network.UpdateChunkEnergyValueMessage;
import com.mojang.serialization.Codec;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.Level;
import net.minecraftforge.energy.EnergyStorage;
Expand All @@ -14,6 +15,17 @@
* @author Choonster
*/
public class ChunkEnergy extends EnergyStorage implements IChunkEnergy {
public static ChunkEnergy createDefault(final int capacity, final Level level, final ChunkPos chunkPos) {
return new ChunkEnergy(capacity, level, chunkPos);
}

public static Codec<ChunkEnergy> codec(final int capacity, final Level level, final ChunkPos chunkPos) {
return Codec.INT.xmap(
(energy) -> new ChunkEnergy(energy, capacity, level, chunkPos),
ChunkEnergy::getEnergyStored
);
}

/**
* The {@link Level} containing this instance's chunk.
*/
Expand All @@ -24,11 +36,14 @@ public class ChunkEnergy extends EnergyStorage implements IChunkEnergy {
*/
private final ChunkPos chunkPos;

public ChunkEnergy(final int capacity, final Level level, final ChunkPos chunkPos) {
super(capacity);
private ChunkEnergy(final int capacity, final Level level, final ChunkPos chunkPos) {
this(capacity, capacity, level, chunkPos);
}

private ChunkEnergy(final int energy, final int capacity, final Level level, final ChunkPos chunkPos) {
super(capacity, capacity, capacity, energy);
this.level = level;
this.chunkPos = chunkPos;
energy = capacity;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
*
* @author Choonster
*/
public class ChunkEnergyCapability {
public final class ChunkEnergyCapability {
/**
* The {@link IChunkEnergy} {@link Capability} instance.
*/
Expand Down Expand Up @@ -74,8 +74,14 @@ private static class EventHandler {
@SubscribeEvent
public static void attachChunkCapabilities(final AttachCapabilitiesEvent<LevelChunk> event) {
final var chunk = event.getObject();
final var chunkEnergy = new ChunkEnergy(DEFAULT_CAPACITY, chunk.getLevel(), chunk.getPos());
event.addCapability(ID, new SerializableCapabilityProvider<>(CHUNK_ENERGY_CHUNK_CAPABILITY, DEFAULT_FACING, chunkEnergy));

final var level = chunk.getLevel();
final var chunkPos = chunk.getPos();

final var chunkEnergy = ChunkEnergy.createDefault(DEFAULT_CAPACITY, level, chunkPos);
final var codec = ChunkEnergy.codec(DEFAULT_CAPACITY, level, chunkPos);

event.addCapability(ID, new SerializableCapabilityProvider<>(CHUNK_ENERGY_CHUNK_CAPABILITY, DEFAULT_FACING, chunkEnergy, codec));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,13 @@
import choonster.testmod3.TestMod3;
import choonster.testmod3.api.capability.maxhealth.IMaxHealth;
import com.mojang.logging.LogUtils;
import net.minecraft.core.HolderLookup;
import net.minecraft.nbt.FloatTag;
import com.mojang.serialization.Codec;
import net.minecraft.network.protocol.game.ClientboundUpdateAttributesPacket;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.attributes.AttributeModifier;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraftforge.common.util.INBTSerializable;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;

Expand All @@ -23,14 +21,25 @@
*
* @author Choonster
*/
public class MaxHealth implements IMaxHealth, INBTSerializable<FloatTag> {
public class MaxHealth implements IMaxHealth {
private static final Logger LOGGER = LogUtils.getLogger();

/**
* The ID of the {@link AttributeModifier}.
*/
protected static final ResourceLocation MODIFIER_ID = ResourceLocation.fromNamespaceAndPath(TestMod3.MODID, "bonus_max_health");

public static MaxHealth empty(@Nullable final LivingEntity entity) {
return new MaxHealth(entity);
}

public static Codec<MaxHealth> codec(@Nullable final LivingEntity entity) {
return Codec.FLOAT.xmap(
(bonusMaxHealth) -> new MaxHealth(entity, bonusMaxHealth),
MaxHealth::getBonusMaxHealth
);
}

/**
* The entity this is attached to.
*/
Expand All @@ -42,8 +51,13 @@ public class MaxHealth implements IMaxHealth, INBTSerializable<FloatTag> {
*/
private float bonusMaxHealth;

public MaxHealth(@Nullable final LivingEntity entity) {
private MaxHealth(@Nullable final LivingEntity entity) {
this(entity, 0);
}

private MaxHealth(@Nullable final LivingEntity entity, final float bonusMaxHealth) {
this.entity = entity;
this.bonusMaxHealth = bonusMaxHealth;
}

/**
Expand Down Expand Up @@ -91,16 +105,6 @@ public void synchronise() {
}
}

@Override
public FloatTag serializeNBT(final HolderLookup.Provider registries) {
return FloatTag.valueOf(bonusMaxHealth);
}

@Override
public void deserializeNBT(final HolderLookup.Provider registries, final FloatTag tag) {
bonusMaxHealth = tag.getAsFloat();
}

/**
* Create the {@link AttributeModifier}.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.capabilities.CapabilityManager;
import net.minecraftforge.common.capabilities.CapabilityToken;
import net.minecraftforge.common.capabilities.ICapabilityProvider;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.event.AttachCapabilitiesEvent;
import net.minecraftforge.event.entity.player.PlayerEvent;
Expand Down Expand Up @@ -55,16 +54,6 @@ public static LazyOptional<IMaxHealth> getMaxHealth(final LivingEntity entity) {
return entity.getCapability(MAX_HEALTH_CAPABILITY, DEFAULT_FACING);
}

/**
* Create a provider for the specified {@link IMaxHealth} instance.
*
* @param maxHealth The IMaxHealth
* @return The provider
*/
public static ICapabilityProvider createProvider(final IMaxHealth maxHealth) {
return new SerializableCapabilityProvider<>(MAX_HEALTH_CAPABILITY, DEFAULT_FACING, maxHealth);
}

/**
* Format a max health value.
*
Expand All @@ -81,17 +70,18 @@ public static String formatMaxHealth(final float maxHealth) {
@SuppressWarnings("unused")
@Mod.EventBusSubscriber(modid = TestMod3.MODID)
private static class EventHandler {

/**
* Attach the {@link IMaxHealth} capability to all living entities.
*
* @param event The event
*/
@SubscribeEvent
public static void attachCapabilities(final AttachCapabilitiesEvent<Entity> event) {
if (event.getObject() instanceof LivingEntity) {
final var maxHealth = new MaxHealth((LivingEntity) event.getObject());
event.addCapability(ID, createProvider(maxHealth));
if (event.getObject() instanceof final LivingEntity entity) {
final var maxHealth = MaxHealth.empty(entity);
final var codec = MaxHealth.codec(entity);

event.addCapability(ID, new SerializableCapabilityProvider<>(MAX_HEALTH_CAPABILITY, DEFAULT_FACING, maxHealth, codec));
}
}

Expand Down
Loading

0 comments on commit c00e0ac

Please sign in to comment.