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.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 {
/**
* 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.getWorld().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.getWorld().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.getWorld().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();
}
}