Merge remote-tracking branch 'origin/develop' into snapshot
This commit is contained in:
@@ -26,7 +26,7 @@ package com.replaymod.core;
|
||||
//$$ public class LoadingPlugin implements IFMLLoadingPlugin {
|
||||
//$$
|
||||
//$$ public LoadingPlugin() {
|
||||
//$$ if ((Boolean) Launch.blackboard.get("fml.deobfuscatedEnvironment")) {
|
||||
//$$ if (Launch.blackboard.get("fml.deobfuscatedEnvironment") != Boolean.FALSE) {
|
||||
//$$ // Outside of the dev env, this is the job of the tweaker
|
||||
//$$ MixinBootstrap.init();
|
||||
//$$ }
|
||||
|
||||
@@ -28,6 +28,7 @@ import net.minecraft.text.Style;
|
||||
import net.minecraft.text.LiteralText;
|
||||
import net.minecraft.text.TranslatableText;
|
||||
import net.minecraft.util.Formatting;
|
||||
import net.minecraft.util.crash.CrashException;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
|
||||
//#if MC>=11400
|
||||
@@ -56,6 +57,7 @@ import net.fabricmc.loader.api.FabricLoader;
|
||||
//$$ import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
|
||||
//$$ import net.minecraftforge.versions.mcp.MCPVersion;
|
||||
//#else
|
||||
//$$ import static com.replaymod.core.versions.MCVer.FML_BUS;
|
||||
//$$ import net.minecraft.client.resources.IResourcePack;
|
||||
//$$ import net.minecraftforge.common.config.Configuration;
|
||||
//#endif
|
||||
@@ -72,14 +74,12 @@ import net.fabricmc.loader.api.FabricLoader;
|
||||
//$$ import net.minecraftforge.fml.common.Mod.Instance;
|
||||
//$$ import net.minecraftforge.fml.common.event.FMLInitializationEvent;
|
||||
//$$ import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
|
||||
//$$ import static com.replaymod.core.versions.MCVer.FORGE_BUS;
|
||||
//#if MC>=10800
|
||||
//$$ import net.minecraftforge.fml.client.FMLClientHandler;
|
||||
//#else
|
||||
//$$ import com.replaymod.replay.InputReplayTimer;
|
||||
//$$
|
||||
//$$ import java.util.ArrayDeque;
|
||||
//$$ import static com.replaymod.core.versions.MCVer.FML_BUS;
|
||||
//#endif
|
||||
//#endif
|
||||
//$$ import net.minecraftforge.fml.common.Mod;
|
||||
@@ -544,6 +544,10 @@ public class ReplayMod implements
|
||||
inRunLater = true;
|
||||
try {
|
||||
runnable.run();
|
||||
} catch (CrashException e) {
|
||||
e.printStackTrace();
|
||||
System.err.println(e.getReport().asString());
|
||||
mc.setCrashReport(e.getReport());
|
||||
} finally {
|
||||
inRunLater = false;
|
||||
}
|
||||
@@ -552,11 +556,11 @@ public class ReplayMod implements
|
||||
//#else
|
||||
//$$ if (mc.isCallingFromMinecraftThread() && inRunLater) {
|
||||
//#if MC>=10800
|
||||
//$$ FORGE_BUS.register(new Object() {
|
||||
//$$ FML_BUS.register(new Object() {
|
||||
//$$ @SubscribeEvent
|
||||
//$$ public void onRenderTick(TickEvent.RenderTickEvent event) {
|
||||
//$$ if (event.phase == TickEvent.Phase.START) {
|
||||
//$$ FORGE_BUS.unregister(this);
|
||||
//$$ FML_BUS.unregister(this);
|
||||
//$$ defer.run();
|
||||
//$$ }
|
||||
//$$ }
|
||||
@@ -578,6 +582,10 @@ public class ReplayMod implements
|
||||
//$$ inRunLater = true;
|
||||
//$$ try {
|
||||
//$$ runnable.run();
|
||||
//$$ } catch (ReportedException e) {
|
||||
//$$ e.printStackTrace();
|
||||
//$$ System.err.println(e.getCrashReport().getCompleteReport());
|
||||
//$$ mc.crashed(e.getCrashReport());
|
||||
//$$ } finally {
|
||||
//$$ inRunLater = false;
|
||||
//$$ }
|
||||
|
||||
@@ -8,6 +8,10 @@ import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
import java.util.Queue;
|
||||
|
||||
//#if MC>=11400
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
//#endif
|
||||
|
||||
//#if MC<11400
|
||||
//$$ import java.util.concurrent.FutureTask;
|
||||
//#endif
|
||||
@@ -24,6 +28,13 @@ public interface MinecraftAccessor {
|
||||
@Accessor("renderTickCounter")
|
||||
void setTimer(RenderTickCounter value);
|
||||
|
||||
//#if MC>=11400
|
||||
@Accessor
|
||||
CompletableFuture<Void> getResourceReloadFuture();
|
||||
@Accessor
|
||||
void setResourceReloadFuture(CompletableFuture<Void> value);
|
||||
//#endif
|
||||
|
||||
//#if MC>=11400
|
||||
@Accessor
|
||||
Queue<Runnable> getRenderTaskQueue();
|
||||
|
||||
@@ -104,17 +104,14 @@ public class RecordingEventHandler extends EventRegistrations {
|
||||
}
|
||||
//#endif
|
||||
|
||||
public void onPlayerJoin() {
|
||||
public void spawnRecordingPlayer() {
|
||||
try {
|
||||
packetListener.save(new PlayerSpawnS2CPacket(mc.player));
|
||||
} catch(Exception e1) {
|
||||
e1.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void onPlayerRespawn() {
|
||||
try {
|
||||
packetListener.save(new PlayerSpawnS2CPacket(mc.player));
|
||||
ClientPlayerEntity player = mc.player;
|
||||
assert player != null;
|
||||
packetListener.save(new PlayerSpawnS2CPacket(player));
|
||||
//#if MC>=11500
|
||||
packetListener.save(new EntityTrackerUpdateS2CPacket(player.getEntityId(), player.getDataTracker(), true));
|
||||
//#endif
|
||||
lastX = lastY = lastZ = null;
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
|
||||
@@ -81,7 +81,7 @@ public abstract class MixinNetHandlerPlayClient {
|
||||
// Only add spawn packet for our own player and only if he isn't known yet
|
||||
if (data.getUuid().equals(mcStatic.player.getGameProfile().getId())
|
||||
&& !this.playerListEntries.containsKey(data.getUuid())) {
|
||||
handler.onPlayerJoin();
|
||||
handler.spawnRecordingPlayer();
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
@@ -96,7 +96,7 @@ public abstract class MixinNetHandlerPlayClient {
|
||||
//$$ public void recordOwnJoin(S01PacketJoinGame packet, CallbackInfo ci) {
|
||||
//$$ RecordingEventHandler handler = getRecordingEventHandler();
|
||||
//$$ if (handler != null) {
|
||||
//$$ handler.onPlayerJoin();
|
||||
//$$ handler.spawnRecordingPlayer();
|
||||
//$$ }
|
||||
//$$ }
|
||||
//#endif
|
||||
@@ -116,7 +116,7 @@ public abstract class MixinNetHandlerPlayClient {
|
||||
public void recordOwnRespawn(PlayerRespawnS2CPacket packet, CallbackInfo ci) {
|
||||
RecordingEventHandler handler = getRecordingEventHandler();
|
||||
if (handler != null) {
|
||||
handler.onPlayerRespawn();
|
||||
handler.spawnRecordingPlayer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,10 +95,9 @@ public class GuiRenderSettings extends GuiScreen implements Closeable {
|
||||
}
|
||||
// Update export arguments to match new Preset
|
||||
exportArguments.setText(newPreset.getValue());
|
||||
// If the user hasn't changed the output file by themselves,
|
||||
if (!outputFileManuallySet) {
|
||||
// generate a new output file name with updated file extension
|
||||
outputFile = generateOutputFile(newPreset);
|
||||
// Update output file ending
|
||||
if (outputFile != null) {
|
||||
outputFile = conformExtension(outputFile, newPreset);
|
||||
outputFileButton.setLabel(outputFile.getName());
|
||||
}
|
||||
updateInputs();
|
||||
@@ -125,15 +124,20 @@ public class GuiRenderSettings extends GuiScreen implements Closeable {
|
||||
public final GuiButton outputFileButton = new GuiButton().setMinSize(new Dimension(0, 20)).onClick(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GuiFileChooserPopup popup = GuiFileChooserPopup.openSaveGui(GuiRenderSettings.this, "replaymod.gui.save",
|
||||
encodingPresetDropdown.getSelectedValue().getFileExtension());
|
||||
popup.setFolder(outputFile.getParentFile());
|
||||
popup.setFileName(outputFile.getName());
|
||||
Futures.addCallback(
|
||||
GuiFileChooserPopup.openSaveGui(GuiRenderSettings.this, "replaymod.gui.save",
|
||||
encodingPresetDropdown.getSelectedValue().getFileExtension()).getFuture(),
|
||||
popup.getFuture(),
|
||||
new FutureCallback<File>() {
|
||||
@Override
|
||||
public void onSuccess(@Nullable File result) {
|
||||
if (result != null) {
|
||||
if (!result.getName().equals(outputFile.getName())) {
|
||||
userDefinedOutputFileName = true;
|
||||
}
|
||||
outputFile = result;
|
||||
outputFileManuallySet = true;
|
||||
outputFileButton.setLabel(result.getName());
|
||||
}
|
||||
}
|
||||
@@ -318,7 +322,7 @@ public class GuiRenderSettings extends GuiScreen implements Closeable {
|
||||
private final ReplayHandler replayHandler;
|
||||
private final Timeline timeline;
|
||||
private File outputFile;
|
||||
private boolean outputFileManuallySet;
|
||||
private boolean userDefinedOutputFileName;
|
||||
|
||||
public GuiRenderSettings(ReplayHandler replayHandler, Timeline timeline) {
|
||||
this.replayHandler = replayHandler;
|
||||
@@ -500,14 +504,19 @@ public class GuiRenderSettings extends GuiScreen implements Closeable {
|
||||
bitRateField.setValue(settings.getBitRate());
|
||||
bitRateUnit.setSelected(0);
|
||||
}
|
||||
if (settings.getOutputFile() == null) {
|
||||
outputFile = generateOutputFile(settings.getEncodingPreset());
|
||||
outputFileManuallySet = false;
|
||||
File savedOutputFile = settings.getOutputFile();
|
||||
if (savedOutputFile == null || !savedOutputFile.getParentFile().exists()) {
|
||||
this.outputFile = generateOutputFile(settings.getEncodingPreset());
|
||||
userDefinedOutputFileName = false;
|
||||
} else if (savedOutputFile.exists()) {
|
||||
String name = generateOutputFile(settings.getEncodingPreset()).getName();
|
||||
this.outputFile = new File(savedOutputFile.isDirectory() ? savedOutputFile : savedOutputFile.getParentFile(), name);
|
||||
userDefinedOutputFileName = false;
|
||||
} else {
|
||||
outputFile = settings.getOutputFile();
|
||||
outputFileManuallySet = true;
|
||||
this.outputFile = conformExtension(savedOutputFile, settings.getEncodingPreset());
|
||||
userDefinedOutputFileName = true;
|
||||
}
|
||||
outputFileButton.setLabel(outputFile.getName());
|
||||
outputFileButton.setLabel(this.outputFile.getName());
|
||||
nametagCheckbox.setChecked(settings.isRenderNameTags());
|
||||
stabilizeYaw.setChecked(settings.isStabilizeYaw());
|
||||
stabilizePitch.setChecked(settings.isStabilizePitch());
|
||||
@@ -543,7 +552,7 @@ public class GuiRenderSettings extends GuiScreen implements Closeable {
|
||||
videoHeight.getInteger(),
|
||||
frameRateSlider.getValue() + 10,
|
||||
bitRateField.getInteger() << (10 * bitRateUnit.getSelected()),
|
||||
serialize ? null : outputFile,
|
||||
serialize && !userDefinedOutputFileName ? outputFile.getParentFile() : outputFile,
|
||||
nametagCheckbox.isChecked(),
|
||||
stabilizeYaw.isChecked() && (serialize || stabilizeYaw.isEnabled()),
|
||||
stabilizePitch.isChecked() && (serialize || stabilizePitch.isEnabled()),
|
||||
@@ -564,6 +573,14 @@ public class GuiRenderSettings extends GuiScreen implements Closeable {
|
||||
return new File(folder, fileName + "." + encodingPreset.getFileExtension());
|
||||
}
|
||||
|
||||
protected File conformExtension(File file, RenderSettings.EncodingPreset preset) {
|
||||
String name = file.getName();
|
||||
if (name.contains(".")) {
|
||||
name = name.substring(0, name.lastIndexOf('.'));
|
||||
}
|
||||
return new File(file.getParentFile(), name + "." + preset.getFileExtension());
|
||||
}
|
||||
|
||||
protected Path getSettingsPath() {
|
||||
return ReplayModRender.instance.getRenderSettingsPath();
|
||||
}
|
||||
|
||||
@@ -44,7 +44,9 @@ import org.lwjgl.opengl.GL11;
|
||||
|
||||
//#if MC>=11400
|
||||
import com.replaymod.render.mixin.MainWindowAccessor;
|
||||
import net.minecraft.client.gui.screen.Screen;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
//#else
|
||||
//$$ import net.minecraft.client.gui.ScaledResolution;
|
||||
//$$ import org.lwjgl.input.Mouse;
|
||||
@@ -219,7 +221,15 @@ public class VideoRenderer implements RenderInfo {
|
||||
//#endif
|
||||
|
||||
if (!settings.isHighPerformance() || framesDone % fps == 0) {
|
||||
drawGui();
|
||||
while (drawGui() && paused) {
|
||||
try {
|
||||
//noinspection BusyWait
|
||||
Thread.sleep(50);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Updating the timer will cause the timeline player to update the game state
|
||||
@@ -398,6 +408,20 @@ public class VideoRenderer implements RenderInfo {
|
||||
|
||||
private void executeTaskQueue() {
|
||||
//#if MC>=11400
|
||||
while (true) {
|
||||
while (mc.overlay != null) {
|
||||
drawGui();
|
||||
((MinecraftMethodAccessor) mc).replayModExecuteTaskQueue();
|
||||
}
|
||||
|
||||
CompletableFuture<Void> resourceReloadFuture = ((MinecraftAccessor) mc).getResourceReloadFuture();
|
||||
if (resourceReloadFuture != null) {
|
||||
((MinecraftAccessor) mc).setResourceReloadFuture(null);
|
||||
mc.reloadResources().thenRun(() -> resourceReloadFuture.complete(null));
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
((MCVer.MinecraftMethodAccessor) mc).replayModExecuteTaskQueue();
|
||||
//#else
|
||||
//$$ Queue<FutureTask<?>> scheduledTasks = ((MinecraftAccessor) mc).getScheduledTasks();
|
||||
@@ -430,14 +454,14 @@ public class VideoRenderer implements RenderInfo {
|
||||
//#endif
|
||||
}
|
||||
|
||||
public void drawGui() {
|
||||
public boolean drawGui() {
|
||||
do {
|
||||
//#if MC>=11400
|
||||
if (GLFW.glfwWindowShouldClose(getWindow(mc).getHandle()) || ((MinecraftAccessor) mc).getCrashReporter() != null) {
|
||||
//#else
|
||||
//$$ if (Display.isCloseRequested() || ((MinecraftAccessor) mc).getCrashReporter() != null) {
|
||||
//#endif
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Resize the GUI framebuffer if the display size changed
|
||||
@@ -510,12 +534,26 @@ public class VideoRenderer implements RenderInfo {
|
||||
int mouseX = (int) mc.mouse.getX() * getWindow(mc).getScaledWidth() / displayWidth;
|
||||
int mouseY = (int) mc.mouse.getY() * getWindow(mc).getScaledHeight() / displayHeight;
|
||||
|
||||
gui.toMinecraft().tick();
|
||||
gui.toMinecraft().render(
|
||||
//#if MC>=11600
|
||||
//$$ new MatrixStack(),
|
||||
//#endif
|
||||
mouseX, mouseY, 0);
|
||||
if (mc.overlay != null) {
|
||||
Screen orgScreen = mc.currentScreen;
|
||||
try {
|
||||
mc.currentScreen = gui.toMinecraft();
|
||||
mc.overlay.render(
|
||||
//#if MC>=11600
|
||||
//$$ new MatrixStack(),
|
||||
//#endif
|
||||
mouseX, mouseY, 0);
|
||||
} finally {
|
||||
mc.currentScreen = orgScreen;
|
||||
}
|
||||
} else {
|
||||
gui.toMinecraft().tick();
|
||||
gui.toMinecraft().render(
|
||||
//#if MC>=11600
|
||||
//$$ new MatrixStack(),
|
||||
//#endif
|
||||
mouseX, mouseY, 0);
|
||||
}
|
||||
//#else
|
||||
//$$ int mouseX = Mouse.getX() * scaled.getScaledWidth() / mc.displayWidth;
|
||||
//$$ int mouseY = scaled.getScaledHeight() - Mouse.getY() * scaled.getScaledHeight() / mc.displayHeight - 1;
|
||||
@@ -558,15 +596,9 @@ public class VideoRenderer implements RenderInfo {
|
||||
//$$ Mouse.setGrabbed(false);
|
||||
//$$ }
|
||||
//#endif
|
||||
if (paused) {
|
||||
try {
|
||||
Thread.sleep(50);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
}
|
||||
}
|
||||
} while (paused && !hasFailed() && !cancelled);
|
||||
|
||||
return !hasFailed() && !cancelled;
|
||||
} while (true);
|
||||
}
|
||||
|
||||
private boolean displaySizeChanged() {
|
||||
|
||||
@@ -70,11 +70,14 @@ import net.minecraft.network.packet.s2c.play.EntitySpawnGlobalS2CPacket;
|
||||
//#endif
|
||||
|
||||
//#if MC>=11400
|
||||
import com.replaymod.core.versions.MCVer;
|
||||
import net.minecraft.network.packet.s2c.play.ChunkDataS2CPacket;
|
||||
import net.minecraft.network.packet.s2c.play.PlayerActionResponseS2CPacket;
|
||||
import net.minecraft.network.packet.s2c.play.OpenContainerS2CPacket;
|
||||
import net.minecraft.network.packet.s2c.play.OpenWrittenBookS2CPacket;
|
||||
import net.minecraft.entity.EntityType;
|
||||
import net.minecraft.text.TranslatableText;
|
||||
import net.minecraft.world.chunk.light.LightingProvider;
|
||||
//#else
|
||||
//$$ import net.minecraft.client.resources.I18n;
|
||||
//$$ import net.minecraft.entity.Entity;
|
||||
@@ -459,6 +462,24 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend
|
||||
//#endif
|
||||
}
|
||||
}
|
||||
|
||||
//#if MC>=11400
|
||||
if (p instanceof ChunkDataS2CPacket) {
|
||||
Runnable doLightUpdates = () -> {
|
||||
if (mc.world != null) {
|
||||
LightingProvider provider = mc.world.getChunkManager().getLightingProvider();
|
||||
while (provider.hasUpdates()) {
|
||||
provider.doLightUpdates(Integer.MAX_VALUE, true, true);
|
||||
}
|
||||
}
|
||||
};
|
||||
if (MCVer.isOnMainThread()) {
|
||||
doLightUpdates.run();
|
||||
} else {
|
||||
MCVer.scheduleOnMainThread(doLightUpdates);
|
||||
}
|
||||
}
|
||||
//#endif
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// We'd rather not have a failure parsing one packet screw up the whole replay process
|
||||
|
||||
@@ -284,7 +284,7 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe
|
||||
//$$ public void onTick(TickEvent.ClientTickEvent event) {
|
||||
//$$ if (event.phase != TickEvent.Phase.START) return;
|
||||
//#endif
|
||||
if (!asyncMode) return;
|
||||
if (!asyncMode || paused()) return;
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
long realTimePassed = now - lastAsyncUpdateTime;
|
||||
|
||||
@@ -578,7 +578,7 @@ public class CameraEntity
|
||||
|
||||
private void handleInputEvents() {
|
||||
if (this.client.options.keyAttack.wasPressed() || this.client.options.keyUse.wasPressed()) {
|
||||
if (canSpectate(this.client.targetedEntity)) {
|
||||
if (this.client.currentScreen == null && canSpectate(this.client.targetedEntity)) {
|
||||
ReplayModReplay.instance.getReplayHandler().spectateEntity(
|
||||
//#if MC<=10710
|
||||
//$$ (EntityLivingBase)
|
||||
|
||||
@@ -100,7 +100,7 @@ public class GuiPathing {
|
||||
public final GuiTexturedButton renderButton = new GuiTexturedButton().onClick(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!preparePathsForPlayback()) return;
|
||||
if (!preparePathsForPlayback(false)) return;
|
||||
|
||||
// Clone the timeline passed to the settings gui as it may be stored for later rendering in a queue
|
||||
SPTimeline spTimeline = mod.getCurrentTimeline();
|
||||
@@ -299,11 +299,12 @@ public class GuiPathing {
|
||||
if (player.isActive()) {
|
||||
player.getFuture().cancel(false);
|
||||
} else {
|
||||
boolean ignoreTimeKeyframes = Keyboard.isKeyDown(Keyboard.KEY_LSHIFT);
|
||||
Path timePath = mod.getCurrentTimeline().getTimePath();
|
||||
|
||||
if (!preparePathsForPlayback()) return;
|
||||
if (!preparePathsForPlayback(ignoreTimeKeyframes)) return;
|
||||
|
||||
timePath.setActive(!Keyboard.isKeyDown(Keyboard.KEY_LSHIFT));
|
||||
timePath.setActive(!ignoreTimeKeyframes);
|
||||
// Start from cursor time unless the control key is pressed (then start from beginning)
|
||||
int startTime = Keyboard.isKeyDown(Keyboard.KEY_LCONTROL)? 0 : GuiPathing.this.timeline.getCursorPosition();
|
||||
ListenableFuture<Void> future = player.start(mod.getCurrentTimeline().getTimeline(), startTime);
|
||||
@@ -519,10 +520,20 @@ public class GuiPathing {
|
||||
}).start();
|
||||
}
|
||||
|
||||
private boolean preparePathsForPlayback() {
|
||||
private boolean preparePathsForPlayback(boolean ignoreTimeKeyframes) {
|
||||
SPTimeline timeline = mod.getCurrentTimeline();
|
||||
timeline.getTimeline().getPaths().forEach(Path::updateAll);
|
||||
|
||||
// Make sure there are at least two position keyframes
|
||||
if (timeline.getPositionPath().getSegments().isEmpty()) {
|
||||
GuiInfoPopup.open(replayHandler.getOverlay(), "replaymod.chat.morekeyframes");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ignoreTimeKeyframes) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Make sure time keyframes's values are monotonically increasing
|
||||
int lastTime = 0;
|
||||
for (Keyframe keyframe : timeline.getTimePath().getKeyframes()) {
|
||||
@@ -538,9 +549,8 @@ public class GuiPathing {
|
||||
lastTime = time;
|
||||
}
|
||||
|
||||
// Make sure there are at least two position- and two time-keyframes
|
||||
if (timeline.getPositionPath().getSegments().isEmpty()
|
||||
|| timeline.getTimePath().getSegments().isEmpty()) {
|
||||
// Make sure there are at least two time keyframes
|
||||
if (timeline.getTimePath().getSegments().isEmpty()) {
|
||||
GuiInfoPopup.open(replayHandler.getOverlay(), "replaymod.chat.morekeyframes");
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user