summary refs log blame commit diff stats
path: root/src/main/java/ganarchy/chewstuff/ChewableItem.java
blob: e3c8f60f0221429325ca6b7bbf1c1f20166bfdb5 (plain) (tree)


































































































































































































                                                                                   
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();
    }
}