package net.minecraft.client; import com.google.common.collect.ImmutableMap; import com.google.common.math.LongMath; import com.google.gson.JsonParser; import com.mojang.logging.LogUtils; import com.mojang.serialization.Codec; import com.mojang.serialization.JsonOps; import com.mojang.serialization.codecs.RecordCodecBuilder; import com.mojang.serialization.codecs.RecordCodecBuilder.Instance; import it.unimi.dsi.fastutil.objects.Object2BooleanFunction; import java.io.Reader; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Timer; import java.util.TimerTask; import java.util.Map.Entry; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; import javax.annotation.Nullable; import net.minecraft.Util; import net.minecraft.client.gui.components.toasts.SystemToast; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.packs.resources.ResourceManager; import net.minecraft.server.packs.resources.SimplePreparableReloadListener; import net.minecraft.util.profiling.ProfilerFiller; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; import org.slf4j.Logger; @OnlyIn(Dist.CLIENT) public class PeriodicNotificationManager extends SimplePreparableReloadListener>> implements AutoCloseable { private static final Codec>> CODEC = Codec.unboundedMap( Codec.STRING, RecordCodecBuilder.create( p_205303_ -> p_205303_.group( Codec.LONG.optionalFieldOf("delay", Long.valueOf(0L)).forGetter(PeriodicNotificationManager.Notification::delay), Codec.LONG.fieldOf("period").forGetter(PeriodicNotificationManager.Notification::period), Codec.STRING.fieldOf("title").forGetter(PeriodicNotificationManager.Notification::title), Codec.STRING.fieldOf("message").forGetter(PeriodicNotificationManager.Notification::message) ) .apply(p_205303_, PeriodicNotificationManager.Notification::new) ) .listOf() ); private static final Logger LOGGER = LogUtils.getLogger(); private final ResourceLocation notifications; private final Object2BooleanFunction selector; @Nullable private Timer timer; @Nullable private PeriodicNotificationManager.NotificationTask notificationTask; public PeriodicNotificationManager(ResourceLocation p_205293_, Object2BooleanFunction p_205294_) { this.notifications = p_205293_; this.selector = p_205294_; } protected Map> prepare(ResourceManager p_205300_, ProfilerFiller p_205301_) { try { Map map; try (Reader reader = p_205300_.openAsReader(this.notifications)) { map = CODEC.parse(JsonOps.INSTANCE, JsonParser.parseReader(reader)).result().orElseThrow(); } return map; } catch (Exception exception) { LOGGER.warn("Failed to load {}", this.notifications, exception); return ImmutableMap.of(); } } protected void apply(Map> p_205318_, ResourceManager p_205319_, ProfilerFiller p_205320_) { List list = p_205318_.entrySet() .stream() .filter(p_205316_ -> this.selector.apply(p_205316_.getKey())) .map(Entry::getValue) .flatMap(Collection::stream) .collect(Collectors.toList()); if (list.isEmpty()) { this.stopTimer(); } else if (list.stream().anyMatch(p_205326_ -> p_205326_.period == 0L)) { Util.logAndPauseIfInIde("A periodic notification in " + this.notifications + " has a period of zero minutes"); this.stopTimer(); } else { long i = this.calculateInitialDelay(list); long j = this.calculateOptimalPeriod(list, i); if (this.timer == null) { this.timer = new Timer(); } if (this.notificationTask == null) { this.notificationTask = new PeriodicNotificationManager.NotificationTask(list, i, j); } else { this.notificationTask = this.notificationTask.reset(list, j); } this.timer.scheduleAtFixedRate(this.notificationTask, TimeUnit.MINUTES.toMillis(i), TimeUnit.MINUTES.toMillis(j)); } } @Override public void close() { this.stopTimer(); } private void stopTimer() { if (this.timer != null) { this.timer.cancel(); } } private long calculateOptimalPeriod(List p_205313_, long p_205314_) { return p_205313_.stream().mapToLong(p_205298_ -> { long i = p_205298_.delay - p_205314_; return LongMath.gcd(i, p_205298_.period); }).reduce(LongMath::gcd).orElseThrow(() -> new IllegalStateException("Empty notifications from: " + this.notifications)); } private long calculateInitialDelay(List p_205311_) { return p_205311_.stream().mapToLong(p_205305_ -> p_205305_.delay).min().orElse(0L); } @OnlyIn(Dist.CLIENT) public static record Notification(long delay, long period, String title, String message) { public Notification(final long delay, final long period, final String title, final String message) { this.delay = delay != 0L ? delay : period; this.period = period; this.title = title; this.message = message; } } @OnlyIn(Dist.CLIENT) static class NotificationTask extends TimerTask { private final Minecraft minecraft = Minecraft.getInstance(); private final List notifications; private final long period; private final AtomicLong elapsed; public NotificationTask(List p_205350_, long p_205351_, long p_205352_) { this.notifications = p_205350_; this.period = p_205352_; this.elapsed = new AtomicLong(p_205351_); } public PeriodicNotificationManager.NotificationTask reset(List p_205357_, long p_205358_) { this.cancel(); return new PeriodicNotificationManager.NotificationTask(p_205357_, this.elapsed.get(), p_205358_); } @Override public void run() { long i = this.elapsed.getAndAdd(this.period); long j = this.elapsed.get(); for (PeriodicNotificationManager.Notification periodicnotificationmanager$notification : this.notifications) { if (i >= periodicnotificationmanager$notification.delay) { long k = i / periodicnotificationmanager$notification.period; long l = j / periodicnotificationmanager$notification.period; if (k != l) { this.minecraft .execute( () -> SystemToast.add( Minecraft.getInstance().getToastManager(), SystemToast.SystemToastId.PERIODIC_NOTIFICATION, Component.translatable(periodicnotificationmanager$notification.title, k), Component.translatable(periodicnotificationmanager$notification.message, k) ) ); return; } } } } } }