diff options
Diffstat (limited to 'src/main/java/ganarchy/chewstuff/ChewableItem.java')
-rw-r--r-- | src/main/java/ganarchy/chewstuff/ChewableItem.java | 195 |
1 files changed, 195 insertions, 0 deletions
diff --git a/src/main/java/ganarchy/chewstuff/ChewableItem.java b/src/main/java/ganarchy/chewstuff/ChewableItem.java new file mode 100644 index 0000000..e3c8f60 --- /dev/null +++ b/src/main/java/ganarchy/chewstuff/ChewableItem.java @@ -0,0 +1,195 @@ +package ganarchy.chewstuff; + +import dev.emi.trinkets.api.SlotReference; +import dev.emi.trinkets.api.TrinketItem; +import dev.emi.trinkets.api.TrinketsApi; +import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.effect.StatusEffect; +import net.minecraft.entity.effect.StatusEffectInstance; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Wearable; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.network.packet.s2c.play.EntityStatusEffectS2CPacket; +import net.minecraft.server.network.ServerPlayerEntity; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.stream.Collectors; + +/** + * An item that can be chewed. + */ +public class ChewableItem extends TrinketItem implements Wearable { + /** + * The cooldown between activations, in ticks. + */ + private static final int COOLDOWN = 5 * 20; + /** + * Number of effects to bind to. + */ + private final int effects; + + /** + * @param effects Number of effects to bind to. + * @param settings The item settings. + */ + public ChewableItem(int effects, Settings settings) { + super(settings); + this.effects = effects; + } + + @Override + public void onEquip(ItemStack stack, SlotReference slot, LivingEntity entity) { + if (entity.world.isClient) { + return; + } + var maybeInfo = ChewComponents.CHEW.maybeGet(entity); + maybeInfo.ifPresent(info -> { + info.sendUpdate = true; + }); + } + + @Override + public void onUnequip( + ItemStack stack, SlotReference slot, LivingEntity entity + ) { + if (entity.world.isClient) { + return; + } + var maybeInfo = ChewComponents.CHEW.maybeGet(entity); + maybeInfo.ifPresent(info -> { + if (!info.effects.isEmpty()) { + resetEffects(entity, info); + } + }); + } + + @Override + public void tick(ItemStack stack, SlotReference slot, LivingEntity entity) { + if (entity.world.isClient) { + return; + } + if (entity instanceof PlayerEntity player) { + if (player.getItemCooldownManager().isCoolingDown(this)) { + return; + } + } + var maybeInfo = ChewComponents.CHEW.maybeGet(entity); + maybeInfo.ifPresent(info -> { + if (info.effects.isEmpty()) { + var effects = entity.getStatusEffects(); + if (effects.size() >= this.effects) { + effects = new ArrayList<>(effects); + Collections.shuffle((List<?>) effects); + info.effects = effects.stream().filter( + effect -> !effect.isAmbient() + ).limit(this.effects).collect( + Collectors.toMap( + StatusEffectInstance::getEffectType, + StatusEffectInstance::getDuration, + (i1, i2) -> { + // don't crash the game in prod + if ( + FabricLoader.getInstance() + .isDevelopmentEnvironment() + ) { + throw new IllegalStateException(); + } else { + return Integer.min(i1, i2); + } + }, + HashMap::new + ) + ); + info.sendUpdate = true; + } else { + return; + } + } + if (info.effects.size() != this.effects) { + return; + } + if (info.sendUpdate) { + sendEffectUpdate(entity, info); + } + var effects = info.effects.keySet(); + for (StatusEffect effect : effects) { + if (info.effects.get(effect) <= 0) { + continue; + } + var effectInstance = entity.getStatusEffect(effect); + if (effectInstance == null || effectInstance.isAmbient()) { + info.effects.put(effect, 0); + continue; + } + // don't wanna deal with LivingEntity internals. + if (effectInstance.getDuration() == 1) { + continue; + } + if (!effectInstance.update(entity, () -> {})) { + throw new IllegalStateException(); + } + } + if (info.effects.values().stream().allMatch(i -> i == 0)) { + resetEffects(entity, info); + } + stack.damage(1, entity, livingEntity -> { + TrinketsApi.onTrinketBroken(stack, slot, livingEntity); + }); + }); + } + + private void sendEffectUpdate(LivingEntity entity, ChewComponent info) { + if (entity instanceof ServerPlayerEntity player) { + for (StatusEffect effect : info.effects.keySet()) { + var instance = player.getStatusEffect(effect); + if (instance == null) { + continue; + } + NbtCompound nbt = new NbtCompound(); + instance.writeNbt(nbt); + nbt.putInt("Duration", instance.getDuration() / 2); + var toSend = StatusEffectInstance.fromNbt(nbt); + if (toSend != null) { + player.networkHandler.sendPacket( + new EntityStatusEffectS2CPacket( + player.getId(), + toSend + ) + ); + } + } + } + } + + /** + * Stops tracking effects, enables the cooldown, and updates the player, + * as needed. + * + * @param entity The entity. + * @param info The tracked effects. + */ + private void resetEffects(LivingEntity entity, ChewComponent info) { + if (entity instanceof ServerPlayerEntity player) { + for (StatusEffect effect : info.effects.keySet()) { + var instance = player.getStatusEffect(effect); + if (instance == null) { + continue; + } + player.networkHandler.sendPacket( + new EntityStatusEffectS2CPacket( + player.getId(), + instance + ) + ); + } + player.getItemCooldownManager().set(this, COOLDOWN); + } + info.effects.clear(); + info.applied.clear(); + } +} |