Skip to content

Commit 4eec8ca

Browse files
authored
Ported tickrate rendering from LoTAS (#288)
2 parents 8ee0ab2 + 62e9c06 commit 4eec8ca

12 files changed

Lines changed: 350 additions & 3 deletions

src/main/java/com/minecrafttas/tasmod/TASmodClient.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ private void registerEventListeners() {
203203
EventListenerRegistry.register(savestateHandlerClient);
204204

205205
EventListenerRegistry.register(virtual.interpolationHandler);
206+
EventListenerRegistry.register(tickratechanger);
206207
}
207208

208209
@Override
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.minecrafttas.tasmod.mixin.tickrate;
2+
3+
import org.spongepowered.asm.mixin.Mixin;
4+
import org.spongepowered.asm.mixin.Shadow;
5+
import org.spongepowered.asm.mixin.injection.At;
6+
import org.spongepowered.asm.mixin.injection.Inject;
7+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
8+
9+
import com.minecrafttas.tasmod.TASmodClient;
10+
11+
import paulscode.sound.Source;
12+
13+
@Mixin(Source.class)
14+
public abstract class MixinAudioPitch {
15+
16+
@Shadow(remap = false)
17+
public float pitch;
18+
19+
@Inject(method = "setPitch", at = @At(value = "RETURN"), remap = false)
20+
public void redosetPitch(float value, CallbackInfo ci) {
21+
pitch = value * (TASmodClient.tickratechanger.ticksPerSecond / 20F);
22+
}
23+
24+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.minecrafttas.tasmod.mixin.tickrate;
2+
3+
import org.spongepowered.asm.mixin.Mixin;
4+
import org.spongepowered.asm.mixin.injection.At;
5+
import org.spongepowered.asm.mixin.injection.ModifyVariable;
6+
7+
import com.minecrafttas.tasmod.TASmodClient;
8+
9+
import net.minecraft.client.renderer.RenderItem;
10+
//#else
11+
//$$ import net.minecraft.client.renderer.entity.RenderItem;
12+
//#endif
13+
14+
@Mixin(RenderItem.class)
15+
public abstract class MixinEnchantmentGlimm {
16+
17+
@ModifyVariable(method = "renderEffect", at = @At("STORE"), index = 2, ordinal = 0)
18+
public float modifyrenderEffect1(float f) {
19+
return (TASmodClient.tickratechanger.getMilliseconds() % 3000L) / 3000.0F / 8F;
20+
}
21+
22+
@ModifyVariable(method = "renderEffect", at = @At("STORE"), index = 3, ordinal = 1)
23+
public float modifyrenderEffect2(float f) {
24+
return (TASmodClient.tickratechanger.getMilliseconds() % 4873L) / 4873.0F / 8F;
25+
}
26+
27+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package com.minecrafttas.tasmod.mixin.tickrate;
2+
3+
import org.spongepowered.asm.mixin.Mixin;
4+
import org.spongepowered.asm.mixin.Shadow;
5+
import org.spongepowered.asm.mixin.injection.At;
6+
import org.spongepowered.asm.mixin.injection.ModifyVariable;
7+
8+
import com.minecrafttas.tasmod.TASmodClient;
9+
10+
import net.minecraft.client.Minecraft;
11+
12+
/**
13+
* This mixin tries to make the animation of the advancement toasts dependent on the tickrate while keeping the code as vanilla as possible<br>
14+
* <br>
15+
* While I spent a long amount of time watching this code, I still don't quite fully understand the math behind this...<br>
16+
* <br>
17+
* Here's what I could find out: Toasts have 2 different states represented in visibility in the ToastInstance.<br>
18+
* And if it's set to SHOW the fly in animation and sound will play, the same goes with HIDE where it flies out after a certain amount of time<br>
19+
* After a lot of trial and error I found out that animationTimer, which was originally "i", is the way to go...<br>
20+
* <br>
21+
* So just as RenderItem and GuiSubtitleOverlay, things are done with an offset for tickrate 0 and simple multiplication<br>
22+
* Also I used a copy of the vanilla ToastInstance-class to make it work for every subtitle on screen... If you seek to make it work for only 1, at the end is a commented code that shows you how to use @ModyfyVarable<br>
23+
* <br>
24+
* There is one compromise I had to make... When you change the tickrate while a toast is showing, it will stay at the old tickrate until it's done...<br>
25+
* Maybe I still fix this and get into this mess once more, but for now this will do and don't make the subtitles stuck in a loop until you change to the old tickrate<br>
26+
* Am I doing this right with commenting code? I hope so...<br>
27+
* <br>
28+
* Update 02.03.21:<br>
29+
* Well, it's been roughly a year since I have touched this code and I am finally back with updating this. And while I am at it, I removed that compromise mentioned earlier.<br>
30+
*
31+
* Update 26.03.26 Unease <br>
32+
* Reusing 5 year old code for legacy... how fitting
33+
*
34+
* @author Scribble
35+
* @author Unease
36+
*/
37+
@Mixin(targets = "net/minecraft/client/gui/toasts/GuiToast$ToastInstance")
38+
public abstract class MixinGuiToast {
39+
40+
/**
41+
* Vanilla, current time in ms when the animationBegan. The delta between animationTimer and animation time shows the progress
42+
*/
43+
@Shadow
44+
private long animationTime;
45+
/**
46+
* Vanilla, the time the animation is visible. Used in the toast instances (e.g. AdvancementToast) to time their animation. Also used to set the visibility to HIDE and make the toast go away
47+
*/
48+
@Shadow
49+
private long visibleTime;
50+
51+
/**
52+
* When entering tickrate 0, store is the (ms) time when tickrate 0 was activated. This time replaces the "animationTimer" during tickrate 0
53+
*/
54+
private long store = 0L;
55+
/**
56+
* Makes sure the code runs only once when switching between tickrate 0 and not tickrate 0... I have yet to find a more elegant solution...
57+
*/
58+
private boolean once = false;
59+
/**
60+
* The offset of the ms time when using tickrate 0. This is used to "resume" the animation without any jumps, after exiting tickrate 0
61+
*/
62+
private long offset = 0L;
63+
/**
64+
* When changing the tickrate while a toast is on screen, the {@link #animationTime} is not correct. The correct animationTime can be optained by subtracting the current time from this delta<br>
65+
*/
66+
private long animationDelta = 0L;
67+
/**
68+
* When changing the tickrate while a toast is on screen, the {@link #visibleTime} is not correct. The correct visibleTime can be optained by subtracting the current time from this delta<br>
69+
*/
70+
private long visibleDelta = 0L;
71+
/**
72+
* Used to detect a change in the tickrate and to run the code on.
73+
*/
74+
private float ticksave = TASmodClient.tickratechanger.ticksPerSecond;
75+
76+
@ModifyVariable(method = "render(II)Z", at = @At(value = "STORE", ordinal = 0))
77+
public long modifyAnimationTime(long animationTimer) {
78+
//===========TICKRATE OTHER THAN 0===========
79+
if (TASmodClient.tickratechanger.ticksPerSecond != 0) {
80+
if (once) {
81+
once = false;
82+
offset = Minecraft.getSystemTime() - store;
83+
}
84+
85+
animationTimer = (long) ((Minecraft.getSystemTime() - offset) * (TASmodClient.tickratechanger.ticksPerSecond / 20));
86+
87+
if (ticksave != TASmodClient.tickratechanger.ticksPerSecond) {
88+
ticksave = TASmodClient.tickratechanger.ticksPerSecond;
89+
animationTime = animationTimer - animationDelta;
90+
visibleTime = animationTimer - visibleDelta;
91+
}
92+
animationDelta = animationTimer - animationTime;
93+
visibleDelta = animationTimer - visibleTime;
94+
//===========TICKRATE 0===========
95+
} else {
96+
if (!once) {
97+
once = true;
98+
store = (long) ((Minecraft.getSystemTime() - offset));
99+
}
100+
animationTimer = (long) (store * (TASmodClient.tickratechanger.tickrateSaved / 20));
101+
}
102+
return animationTimer;
103+
}
104+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.minecrafttas.tasmod.mixin.tickrate;
2+
3+
import java.util.Map;
4+
5+
import org.spongepowered.asm.mixin.Mixin;
6+
import org.spongepowered.asm.mixin.Shadow;
7+
8+
import net.minecraft.client.audio.ISound;
9+
import net.minecraft.client.audio.SoundManager;
10+
11+
@Mixin(SoundManager.class)
12+
public class MixinSoundManager implements com.minecrafttas.tasmod.util.Ducks.SoundManagerDuck {
13+
14+
@Shadow
15+
private boolean loaded;
16+
@Shadow
17+
private Map<String, ISound> playingSounds;
18+
@Shadow
19+
private SoundManager.SoundSystemStarterThread sndSystem;
20+
21+
@Override
22+
public void updatePitch() {
23+
if (this.loaded) {
24+
playingSounds.forEach((sourceName, sound) -> {
25+
sndSystem.setPitch(sourceName, sound.getPitch());
26+
});
27+
}
28+
}
29+
30+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package com.minecrafttas.tasmod.mixin.tickrate;
2+
3+
import org.spongepowered.asm.mixin.Mixin;
4+
import org.spongepowered.asm.mixin.Shadow;
5+
import org.spongepowered.asm.mixin.injection.At;
6+
import org.spongepowered.asm.mixin.injection.Inject;
7+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
8+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
9+
10+
import com.minecrafttas.tasmod.TASmodClient;
11+
12+
import net.minecraft.client.Minecraft;
13+
import net.minecraft.client.gui.GuiSubtitleOverlay;
14+
15+
@Mixin(GuiSubtitleOverlay.Subtitle.class)
16+
public class MixinSubtitle {
17+
18+
@Shadow
19+
private long startTime;
20+
21+
private long offset = 0;
22+
private long store = 0;
23+
24+
private boolean once = false;
25+
26+
@Inject(method = "getStartTime", at = @At(value = "HEAD"), cancellable = true)
27+
public void redoGetStartTime(CallbackInfoReturnable<Long> ci) {
28+
if (TASmodClient.tickratechanger.ticksPerSecond == 0) {
29+
if (!once) {
30+
once = true;
31+
store = Minecraft.getSystemTime() - offset;
32+
}
33+
offset = Minecraft.getSystemTime() - store;
34+
} else {
35+
if (once) {
36+
once = false;
37+
}
38+
}
39+
ci.setReturnValue(this.startTime + offset);
40+
ci.cancel();
41+
}
42+
43+
@Inject(method = "refresh", at = @At(value = "HEAD"))
44+
public void resetOnRefresh(CallbackInfo ci) {
45+
offset = 0;
46+
store = 0;
47+
once = false;
48+
}
49+
}
50+
//#else
51+
//$$ @Mixin(Minecraft.class)
52+
//$$ public class MixinSubtitle {
53+
//$$
54+
//$$ }
55+
//#endif
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.minecrafttas.tasmod.mixin.tickrate;
2+
3+
import org.spongepowered.asm.mixin.Mixin;
4+
import org.spongepowered.asm.mixin.injection.Constant;
5+
import org.spongepowered.asm.mixin.injection.ModifyConstant;
6+
7+
import com.minecrafttas.tasmod.TASmodClient;
8+
9+
import net.minecraft.client.gui.GuiSubtitleOverlay;
10+
11+
@Mixin(GuiSubtitleOverlay.class)
12+
public abstract class MixinSubtitleOverlay {
13+
14+
@ModifyConstant(method = "renderSubtitles", constant = @Constant(longValue = 3000L))
15+
public long applyTickrate(long threethousand) {
16+
float multiplier = TASmodClient.tickratechanger.ticksPerSecond == 0 ? 20F / TASmodClient.tickratechanger.tickrateSaved : 20F / TASmodClient.tickratechanger.ticksPerSecond;
17+
return (long) (threethousand * multiplier);
18+
}
19+
20+
@ModifyConstant(method = "renderSubtitles", constant = @Constant(floatValue = 3000F))
21+
public float applyTickrate2(float threethousand) {
22+
float multiplier = TASmodClient.tickratechanger.ticksPerSecond == 0 ? 20F / TASmodClient.tickratechanger.tickrateSaved : 20F / TASmodClient.tickratechanger.ticksPerSecond;
23+
return threethousand * multiplier;
24+
}
25+
26+
}
27+
//#else
28+
//$$ import net.minecraft.client.Minecraft;
29+
//$$ @Mixin(Minecraft.class)
30+
//$$ public abstract class MixinSubtitleOverlay {
31+
//$$
32+
//$$ }
33+
//#endif
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.minecrafttas.tasmod.mixin.tickrate;
2+
3+
import org.spongepowered.asm.mixin.Mixin;
4+
import org.spongepowered.asm.mixin.injection.At;
5+
import org.spongepowered.asm.mixin.injection.ModifyVariable;
6+
7+
import com.minecrafttas.tasmod.TASmodClient;
8+
9+
import net.minecraft.client.renderer.RenderGlobal;
10+
11+
@Mixin(RenderGlobal.class)
12+
public class MixinWorldborder {
13+
14+
@ModifyVariable(method = "renderWorldBorder", at = @At(value = "STORE"), index = 20, ordinal = 4)
15+
public float injectf3(float f) {
16+
return (TASmodClient.tickratechanger.getMilliseconds() % 3000L) / 3000.0F;
17+
}
18+
19+
}

src/main/java/com/minecrafttas/tasmod/tickratechanger/TickrateChangerClient.java

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@
44

55
import java.nio.ByteBuffer;
66

7+
import com.minecrafttas.mctcommon.events.EventClient.EventClientGameLoop;
78
import com.minecrafttas.mctcommon.events.EventListenerRegistry;
89
import com.minecrafttas.mctcommon.networking.Client.Side;
910
import com.minecrafttas.mctcommon.networking.exception.PacketNotImplementedException;
1011
import com.minecrafttas.mctcommon.networking.exception.WrongSideException;
1112
import com.minecrafttas.mctcommon.networking.interfaces.ClientPacketHandler;
1213
import com.minecrafttas.mctcommon.networking.interfaces.PacketID;
1314
import com.minecrafttas.tasmod.TASmodClient;
15+
import com.minecrafttas.tasmod.events.EventClient.EventClientTickPost;
1416
import com.minecrafttas.tasmod.events.EventTickratechanger;
1517
import com.minecrafttas.tasmod.networking.TASmodBufferBuilder;
1618
import com.minecrafttas.tasmod.registries.TASmodPackets;
@@ -25,7 +27,7 @@
2527
* @author Scribble
2628
*
2729
*/
28-
public class TickrateChangerClient implements ClientPacketHandler {
30+
public class TickrateChangerClient implements ClientPacketHandler, EventClientGameLoop {
2931
/**
3032
* The current tickrate of the client
3133
*/
@@ -47,6 +49,26 @@ public class TickrateChangerClient implements ClientPacketHandler {
4749
*/
4850
public long millisecondsPerTick = 50L;
4951

52+
/**
53+
* Timestamp when the last tickrate change was initiated
54+
*/
55+
public long timestampSinceLastTickRateChange = System.currentTimeMillis();
56+
57+
/**
58+
* Time since the last tickrate change was initiated without being affected by tickrate
59+
*/
60+
public long fakeTimeSinceTickRateChange = System.currentTimeMillis();
61+
62+
/**
63+
* Timestamp since last game loop and only updated when the tickrate is 0
64+
*/
65+
private long timestampSinceLastGameLoop;
66+
67+
/**
68+
* Counts the milliseconds when the tickrate is 0
69+
*/
70+
private float timeOffset = 0L;
71+
5072
/**
5173
* The tickrate steps that can be set via {@link #increaseTickrate()} and {@link #decreaseTickrate()}
5274
*/
@@ -142,6 +164,15 @@ public void changeServerTickrate(float tickrate) {
142164
}
143165
}
144166

167+
/**
168+
* @return Milliseconds affected by tickrate
169+
*/
170+
public long getMilliseconds() {
171+
long time = (long) (System.currentTimeMillis() - timestampSinceLastTickRateChange - timeOffset);
172+
time *= (ticksPerSecond / 20F);
173+
return (long) (fakeTimeSinceTickRateChange + time);
174+
}
175+
145176
/**
146177
* <p>Toggles between tickrate 0 and tickrate > 0
147178
*/
@@ -342,4 +373,12 @@ private static int clamp(long value, int min, int max) {
342373
}
343374
return (int) Math.min(max, Math.max(value, min));
344375
}
376+
377+
@Override
378+
public void onRunClientGameLoop(Minecraft mc) {
379+
if (ticksPerSecond == 0) {
380+
timeOffset += System.currentTimeMillis() - timestampSinceLastGameLoop;
381+
timestampSinceLastGameLoop = System.currentTimeMillis();
382+
}
383+
}
345384
}

src/main/java/com/minecrafttas/tasmod/util/Ducks.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,4 +142,9 @@ public static interface PlayerChunkMapDuck {
142142
*/
143143
public void forceTick();
144144
}
145+
146+
public static interface SoundManagerDuck {
147+
148+
public void updatePitch();
149+
}
145150
}

0 commit comments

Comments
 (0)