package com.minelittlepony.unicopia.entity; import java.util.*; import java.util.function.Predicate; import java.util.stream.Stream; import org.jetbrains.annotations.Nullable; import com.minelittlepony.unicopia.compat.trinkets.TrinketsDelegate; import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.util.*; import net.minecraft.entity.LivingEntity; import net.minecraft.item.ItemConvertible; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NbtCompound; import net.minecraft.util.Identifier; import net.minecraft.registry.Registries; public class ItemTracker implements NbtSerialisable, Copyable, Tickable, TrinketsDelegate.Inventory { public static final long TICKS = 1; public static final long SECONDS = 20 * TICKS; public static final long HOURS = 1000 * TICKS; public static final long DAYS = 24 * HOURS; public static String formatTicks(long ticks) { long days = ticks / (SECONDS * 60 * 24); ticks %= (SECONDS * 60 * 60 * 24); long hours = ticks / (SECONDS * 60 * 60); ticks %= (SECONDS * 60 * 60); long minutes = ticks / (SECONDS * 60); ticks %= (SECONDS * 60); long seconds = ticks / SECONDS; return String.format("%dd, %dh %dm %ds", days, hours, minutes, seconds); } private final Map items = new HashMap<>(); public static Predicate wearing(Trackable charm, Predicate range) { return e -> Living.getOrEmpty(e) .map(Living::getArmour) .map(a -> a.getTicks(charm)) .filter(range) .isPresent(); } public static Predicate between(long minTime, long maxTime) { return before(maxTime).and(after(minTime)); } public static Predicate before(long maxTime) { return ticks -> ticks <= maxTime; } public static Predicate after(long maxTime) { return ticks -> ticks <= maxTime; } private final Living living; public ItemTracker(Living living) { this.living = living; } @Override public LivingEntity asEntity() { return living.asEntity(); } @Override public void tick() { update(living.getArmourStacks()); } private void update(Stream stacks) { final Set found = new HashSet<>(); final Set foundStacks = new HashSet<>(); stacks.forEach(stack -> { if (stack.getItem() instanceof Trackable trackable) { if (items.compute(trackable, (item, prev) -> prev == null ? 1 : prev + 1) == 1) { trackable.onEquipped(this.living); } found.add(trackable); foundStacks.add(stack); } }); items.entrySet().removeIf(e -> { if (!found.contains(e.getKey())) { e.getKey().onUnequipped(living, e.getValue()); return true; } return false; }); if (!(living instanceof Pony)) { foundStacks.forEach(stack -> { stack.inventoryTick(living.asWorld(), living.asEntity(), 0, false); }); } } public long forceRemove(Trackable charm) { @Nullable Long time = items.remove(charm); return time == null ? 0 : time; } public long getTicks(Trackable charm) { return items.getOrDefault(charm.asItem(), 0L); } public boolean contains(Trackable charm) { return getTicks(charm) > 0; } @Override public void toNBT(NbtCompound compound) { items.forEach((charm, count) -> { compound.putLong(Registries.ITEM.getId(charm.asItem()).toString(), count); }); } @Override public void fromNBT(NbtCompound compound) { items.clear(); compound.getKeys().stream().map(Identifier::tryParse) .filter(Objects::nonNull) .map(id -> Map.entry(Registries.ITEM.get(id), compound.getLong(id.toString()))) .filter(i -> i.getKey() instanceof Trackable && i.getValue() > 0) .forEach(item -> items.put((Trackable)item.getKey(), item.getValue())); } @Override public void copyFrom(ItemTracker other, boolean alive) { items.clear(); if (alive) { items.putAll(other.items); } } public interface Trackable extends ItemConvertible { void onUnequipped(Living living, long timeWorn); void onEquipped(Living living); } }