package com.cursedcauldron.wildbackport.common.entities.warden; import com.google.common.collect.Streams; import com.mojang.datafixers.util.Pair; import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import net.minecraft.core.SerializableUUID; import net.minecraft.server.level.ServerLevel; import net.minecraft.util.ExtraCodecs; import net.minecraft.util.Mth; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.player.Player; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Optional; import java.util.Random; import java.util.UUID; import java.util.function.Predicate; import java.util.stream.Collectors; //<> public class WardenAngerManager { private int updateTimer = Mth.randomBetweenInclusive(new Random(System.nanoTime()), 0, 2); private static final Codec> SUSPECT_CODEC = RecordCodecBuilder.create(instance -> instance.group(SerializableUUID.CODEC.fieldOf("uuid").forGetter(Pair::getFirst), ExtraCodecs.NON_NEGATIVE_INT.fieldOf("anger").forGetter(Pair::getSecond)).apply(instance, Pair::of)); private final Predicate validTarget; protected final ArrayList suspects; private final SuspectComparator suspectComparator; protected final Object2IntMap suspectsToAngerLevel; protected final Object2IntMap suspectUuidsToAngerLevel; public static Codec createCodec(Predicate validTarget) { return RecordCodecBuilder.create(instance -> instance.group(SUSPECT_CODEC.listOf().fieldOf("suspects").orElse(Collections.emptyList()).forGetter(WardenAngerManager::getSuspects)).apply(instance, suspects -> new WardenAngerManager(validTarget, suspects))); } public WardenAngerManager(Predicate validTarget, List> suspects) { this.validTarget = validTarget; this.suspects = new ArrayList<>(); this.suspectComparator = new SuspectComparator(this); this.suspectsToAngerLevel = new Object2IntOpenHashMap<>(); this.suspectUuidsToAngerLevel = new Object2IntOpenHashMap<>(suspects.size()); suspects.forEach(pair -> this.suspectUuidsToAngerLevel.put(pair.getFirst(), pair.getSecond())); } private List> getSuspects() { return Streams.concat(this.suspects.stream().map(suspect -> Pair.of(suspect.getUUID(), this.suspectsToAngerLevel.getInt(suspect))), this.suspectUuidsToAngerLevel.object2IntEntrySet().stream().map(entry -> Pair.of(entry.getKey(), entry.getIntValue()))).collect(Collectors.toList()); } public void tick(ServerLevel world, Predicate suspectPredicate) { --this.updateTimer; if (this.updateTimer <= 0) { this.updateSuspectsMap(world); this.updateTimer = 2; } Iterator> uuidAngerLevels = this.suspectUuidsToAngerLevel.object2IntEntrySet().iterator(); while (uuidAngerLevels.hasNext()) { Object2IntMap.Entry entry = uuidAngerLevels.next(); int level = entry.getIntValue(); if (level <= 1) { uuidAngerLevels.remove(); } else { entry.setValue(level - 1); } } Iterator> angerLevels = this.suspectsToAngerLevel.object2IntEntrySet().iterator(); while (angerLevels.hasNext()) { Object2IntMap.Entry entry = angerLevels.next(); int level = entry.getIntValue(); Entity entity = entry.getKey(); Entity.RemovalReason reason = entity.getRemovalReason(); if (level > 1 && suspectPredicate.test(entity) && reason == null) { entry.setValue(level - 1); } else { this.suspects.remove(entity); angerLevels.remove(); if (level > 1 && reason != null) { switch (reason) { case CHANGED_DIMENSION, UNLOADED_TO_CHUNK, UNLOADED_WITH_PLAYER -> this.suspectUuidsToAngerLevel.put(entity.getUUID(), level - 1); } } } } this.suspects.sort(this.suspectComparator); } private void updateSuspectsMap(ServerLevel world) { Iterator> angerLevels = this.suspectUuidsToAngerLevel.object2IntEntrySet().iterator(); while (angerLevels.hasNext()) { Object2IntMap.Entry entry = angerLevels.next(); int level = entry.getIntValue(); Entity entity = world.getEntity(entry.getKey()); if (entity != null) { this.suspectsToAngerLevel.put(entity, level); this.suspects.add(entity); angerLevels.remove(); } } this.suspects.sort(this.suspectComparator); } public int increaseAngerAt(Entity entity, int amount) { boolean canAffectAnger = !this.suspectsToAngerLevel.containsKey(entity); int angerValue = this.suspectsToAngerLevel.computeInt(entity, (suspect, anger) -> Math.min(150, (anger == null ? 0 : anger) + amount)); if (canAffectAnger) { int anger = this.suspectUuidsToAngerLevel.removeInt(entity.getUUID()); this.suspectsToAngerLevel.put(entity, angerValue += anger); this.suspects.add(entity); } this.suspects.sort(this.suspectComparator); return angerValue; } public void removeSuspect(Entity entity) { this.suspectsToAngerLevel.removeInt(entity); this.suspects.remove(entity); } @Nullable private Entity getSuspect() { return this.suspects.stream().filter(this.validTarget).findFirst().orElse(null); } public int getPrimeSuspectAnger() { return this.suspectsToAngerLevel.getInt(this.getSuspect()); } public Optional getPrimeSuspect() { return Optional.ofNullable(this.getSuspect()).filter(suspect -> suspect instanceof LivingEntity).map(suspect -> (LivingEntity)suspect); } protected record SuspectComparator(WardenAngerManager angerManagement) implements Comparator { @Override public int compare(Entity first, Entity second) { if (first.equals(second)) { return 0; } else { int firstAngerLevel = this.angerManagement.suspectsToAngerLevel.getOrDefault(first, 0); int secondAngerLevel = this.angerManagement.suspectsToAngerLevel.getOrDefault(second, 0); boolean angryTowardsFirst = Angriness.getForAnger(firstAngerLevel).isAngry(); boolean angryTowardsSecond = Angriness.getForAnger(secondAngerLevel).isAngry(); if (angryTowardsFirst != angryTowardsSecond) { return angryTowardsFirst ? -1 : 1; } else if (angryTowardsFirst && first instanceof Player != second instanceof Player) { return first instanceof Player ? -1 : 1; } else { return firstAngerLevel > secondAngerLevel ? -1 : 1; } } } } }