summary refs log tree commit diff stats
path: root/src/main/java/ganarchy/chewstuff/ChewableItem.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/ganarchy/chewstuff/ChewableItem.java')
-rw-r--r--src/main/java/ganarchy/chewstuff/ChewableItem.java195
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();
+    }
+}