package com.replaymod.pathing.player; import com.google.common.base.Function; import com.google.common.collect.Iterables; import com.google.common.collect.Ordering; import com.google.common.primitives.Longs; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import com.replaymod.core.mixin.MinecraftAccessor; import com.replaymod.core.mixin.TimerAccessor; import com.replaymod.replay.ReplayHandler; import com.replaymod.replaystudio.pathing.path.Keyframe; import com.replaymod.replaystudio.pathing.path.Path; import com.replaymod.replaystudio.pathing.path.Timeline; import com.replaymod.pathing.properties.FovProperty; import de.johni0702.minecraft.gui.utils.EventRegistrations; import net.minecraft.client.MinecraftClient; import net.minecraft.client.render.RenderTickCounter; import javax.annotation.Nullable; import java.util.Iterator; import static com.replaymod.core.utils.Utils.DEFAULT_MS_PER_TICK; import static com.replaymod.core.versions.MCVer.*; /** * Plays a timeline. */ public abstract class AbstractTimelinePlayer extends EventRegistrations { private final MinecraftClient mc = getMinecraft(); private final ReplayHandler replayHandler; private Timeline timeline; protected long startOffset; private boolean wasAsyncMode; //#if MC>=12100 //$$ private RenderTickCounter.Dynamic orgTimer; //#else private RenderTickCounter orgTimer; //#endif private long lastTime; private long lastTimestamp; private ListenableFuture future; private SettableFuture settableFuture; public AbstractTimelinePlayer(ReplayHandler replayHandler) { this.replayHandler = replayHandler; } public ListenableFuture start(Timeline timeline, long from) { startOffset = from; return start(timeline); } public ListenableFuture start(Timeline timeline) { this.timeline = timeline; Iterator iter = Iterables.concat(Iterables.transform(timeline.getPaths(), new Function>() { @Nullable @Override public Iterable apply(@Nullable Path input) { assert input != null; return input.getKeyframes(); } })).iterator(); if (!iter.hasNext()) { lastTimestamp = 0; } else { lastTimestamp = new Ordering() { @Override public int compare(@Nullable Keyframe left, @Nullable Keyframe right) { assert left != null; assert right != null; return Longs.compare(left.getTime(), right.getTime()); } }.max(iter).getTime(); } wasAsyncMode = replayHandler.getReplaySender().isAsyncMode(); replayHandler.getReplaySender().setSyncModeAndWait(); register(); lastTime = 0; MinecraftAccessor mcA = (MinecraftAccessor) mc; orgTimer = mcA.getTimer(); ReplayTimer timer = new ReplayTimer(); mcA.setTimer(timer); //noinspection ConstantConditions TimerAccessor timerA = (TimerAccessor) timer; //#if MC>=11200 timerA.setTickLength(DEFAULT_MS_PER_TICK); timer.tickDelta = timer.ticksThisFrame = 0; //#else //$$ timer.timerSpeed = 1; //$$ timer.elapsedPartialTicks = timer.elapsedTicks = 0; //#endif return future = settableFuture = SettableFuture.create(); } public ListenableFuture getFuture() { return future; } public boolean isActive() { return future != null && !future.isDone(); } { on(ReplayTimer.UpdatedCallback.EVENT, this::onTick); } public void onTick() { if (future.isDone()) { MinecraftAccessor mcA = (MinecraftAccessor) mc; mcA.setTimer(orgTimer); replayHandler.getReplaySender().setReplaySpeed(0); if (wasAsyncMode) { replayHandler.getReplaySender().setAsyncMode(true); } FovProperty.currentOverride = null; unregister(); return; } long time = getTimePassed(); if (time > lastTimestamp) { time = lastTimestamp; } // Apply to timeline timeline.applyToGame(time, replayHandler); // Apply a second time in case same of the packets have moved the camera from where it was timeline.applyToGame(time, replayHandler); // Update minecraft timer long replayTime = replayHandler.getReplaySender().currentTimeStamp(); if (lastTime == 0) { // First frame, no change yet lastTime = replayTime; } float timeInTicks = replayTime / 50f; float previousTimeInTicks = lastTime / 50f; float passedTicks = timeInTicks - previousTimeInTicks; RenderTickCounter renderTickCounter = ((MinecraftAccessor) mc).getTimer(); if (renderTickCounter instanceof ReplayTimer) { ReplayTimer timer = (ReplayTimer) renderTickCounter; timer.tickDelta += passedTicks; timer.ticksThisFrame = (int) timer.tickDelta; timer.tickDelta -= timer.ticksThisFrame; } lastTime = replayTime; if (time >= lastTimestamp) { settableFuture.set(null); } } public abstract long getTimePassed(); }