Skip to content

Commit 4f121ec

Browse files
author
Klotzi111
committedJul 1, 2022
added support for Minecraft 1.19
fixed bug: toggle key bindings for auto jump and skin layers now also works with alternative key bindings from NMUK
1 parent 868e3e0 commit 4f121ec

33 files changed

+739
-201
lines changed
 

‎README.md

+20-5
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,29 @@
1-
# Amecs
2-
3-
[![curseforge downloads](http://cf.way2muchnoise.eu/full_amecs_downloads.svg)](https://minecraft.curseforge.com/projects/amecs)
4-
[![curseforge mc versions](http://cf.way2muchnoise.eu/versions/amecs.svg)](https://minecraft.curseforge.com/projects/amecs)
1+
# Note
2+
This is a fork of [Siphalor/amecs](https://github.com/Siphalor/amecs)
53

4+
# Amecs
65
![logo](src/main/resources/assets/amecs/logo.png?raw=true)
76

87
## API
98
If you want to use the api provided by this mod you'll want to implement and include this mod:
109

11-
[See here 😜](https://github.com/Siphalor/amecs-api)
10+
[See here 😜](https://github.com/Klotzi111/amecs-api)
11+
12+
## DEV Runtime
13+
14+
If you're a modder and you want Amecs in you development build you can do so by including it like this in the `build.gradle`:
15+
16+
```groovy
17+
repositories {
18+
maven {
19+
url "https://jitpack.io"
20+
}
21+
}
22+
23+
dependencies {
24+
modRuntimeOnly("com.github.Klotzi111:amecs:multiversion-SNAPSHOT")
25+
}
26+
```
1227

1328
## License
1429

‎build.gradle

+31-23
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
plugins {
2-
id 'fabric-loom' version '0.11-SNAPSHOT'
2+
id 'fabric-loom' version '0.12-SNAPSHOT'
33
id 'maven-publish'
44
}
55

6-
sourceCompatibility = JavaVersion.VERSION_1_8
7-
targetCompatibility = JavaVersion.VERSION_1_8
6+
ext {
7+
jdk_version = 8
8+
9+
klotzi111_localDeps = "true".equals(System.getenv("klotzi111_localDeps"))
10+
}
11+
12+
sourceCompatibility = jdk_version
13+
targetCompatibility = jdk_version
814

915
archivesBaseName = project.archives_base_name
1016
version = "$mod_version+mc$minecraft_version".toString()
1117
group = project.maven_group
1218

13-
loom {
14-
}
15-
1619
sourceSets {
1720
testmod {
1821
compileClasspath += main.compileClasspath
@@ -21,14 +24,14 @@ sourceSets {
2124
}
2225

2326
repositories {
27+
mavenLocal()
2428
maven {
2529
name "Siphalor's Maven"
2630
url "https://maven.siphalor.de"
2731
}
2832
maven {
2933
url "https://jitpack.io"
3034
}
31-
mavenLocal()
3235
}
3336

3437
dependencies {
@@ -37,6 +40,22 @@ dependencies {
3740
mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"
3841
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
3942

43+
// FabricMultiVersionHelper
44+
include(modApi(klotzi111_localDeps ? "de.klotzi111:FabricMultiVersionHelper:1+" : "com.github.Klotzi111:FabricMultiVersionHelper:main-SNAPSHOT"))
45+
46+
// Amecs API
47+
def amecs_api = modApi(klotzi111_localDeps ? "de.siphalor:amecsapi-${project.target_minecraft_major_version}:1+" : "com.github.Klotzi111:amecs-api:main-SNAPSHOT") {
48+
exclude(group: "*")
49+
}
50+
include(amecs_api)
51+
52+
// NMUK
53+
modCompileOnly(klotzi111_localDeps ? "de.siphalor:nmuk-${project.target_minecraft_major_version}:1+" : "com.github.Klotzi111:nmuk:main-SNAPSHOT") {
54+
exclude(group: "*")
55+
}
56+
57+
testmodRuntimeOnly("com.github.astei:lazydfu:master-SNAPSHOT")
58+
4059
// we need those old api versions because newer versions require java 16 and that is not available on older minecraft versions
4160
// these old fabric apis are required because we want to support down to minecraft 1.14
4261
def fabric_deps = [
@@ -48,19 +67,6 @@ dependencies {
4867
include(modApi("net.fabricmc.fabric-api:" + dep.getKey() + ":" + dep.getValue()))
4968
}
5069

51-
def amecs_api = modApi("de.siphalor:amecsapi-${project.target_minecraft_major_version}:1.3.5+mc1.18.1") {
52-
exclude group: "net.fabricmc.fabric-api"
53-
exclude module: "nmuk-${project.target_minecraft_major_version}"
54-
}
55-
include(amecs_api)
56-
57-
modApi("de.siphalor:nmuk-${project.target_minecraft_major_version}:1.1+") {
58-
exclude group: "net.fabricmc.fabric-api"
59-
exclude module: "amecsapi-${project.target_minecraft_major_version}"
60-
}
61-
62-
modRuntimeOnly("com.github.astei:lazydfu:master-SNAPSHOT")
63-
6470
testmodImplementation sourceSets.main.output
6571
}
6672

@@ -76,11 +82,13 @@ processResources {
7682
}
7783
}
7884

79-
// ensure that the encoding is set to UTF-8, no matter what the system default is
80-
// this fixes some edge cases with special characters not displaying correctly
81-
// see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html
8285
tasks.withType(JavaCompile) {
86+
// ensure that the encoding is set to UTF-8, no matter what the system default is
87+
// this fixes some edge cases with special characters not displaying correctly
88+
// see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html
89+
// If Javadoc is generated, this must be specified in that task too.
8390
options.encoding = "UTF-8"
91+
options.release = jdk_version
8492
}
8593

8694
// Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task

‎gradle.properties

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
org.gradle.jvmargs = -Xmx1G
22

33
#Fabric properties
4-
minecraft_version = 1.18.2
5-
yarn_mappings = 1.18.2+build.3
6-
loader_version = 0.14.5
4+
minecraft_version = 1.19
5+
yarn_mappings = 1.19+build.2
6+
loader_version = 0.14.7
77

88
#Mod properties
9-
mod_version = 1.4.4
9+
mod_version = 1.4.5
1010
maven_group = de.siphalor.amecs
1111
archives_base_name = amecs
1212
target_minecraft_major_version = multiversion

‎jitpack.yml

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
before_install:
2+
- sdk install java 17.0.1-open
3+
- sdk use java 17.0.1-open

‎src/main/java/de/siphalor/amecs/Amecs.java

+19-8
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
import org.apache.logging.log4j.Logger;
1212
import org.lwjgl.glfw.GLFW;
1313

14+
import de.klotzi111.fabricmultiversionhelper.api.text.TextWrapper;
15+
import de.siphalor.amecs.api.AmecsKeyBinding;
1416
import de.siphalor.amecs.api.KeyBindingUtils;
1517
import de.siphalor.amecs.api.KeyModifiers;
1618
import de.siphalor.amecs.api.input.InputEventHandler;
@@ -33,7 +35,6 @@
3335
import net.minecraft.entity.player.PlayerEntity;
3436
import net.minecraft.text.Text;
3537
import net.minecraft.text.TextColor;
36-
import net.minecraft.text.TranslatableText;
3738
import net.minecraft.util.Formatting;
3839

3940
/**
@@ -47,7 +48,7 @@ public class Amecs implements ClientModInitializer {
4748
public static final String MOD_ID = "amecs";
4849
public static final String MOD_NAME_SHORT = "Amecs";
4950

50-
private static final String LOGGER_PREFIX = "[" + MOD_NAME_SHORT + "] ";
51+
private static final String LOG_PREFIX = "[" + MOD_NAME_SHORT + "] ";
5152
private static final Logger LOGGER = LogManager.getLogger();
5253

5354
private static final String SKIN_LAYER_CATEGORY = MOD_ID + ".key.categories.skin_layers";
@@ -61,6 +62,7 @@ public class Amecs implements ClientModInitializer {
6162
public static List<KeyBinding> ALL_KEYBINDINGS = new ArrayList<>();
6263

6364
public static DropEntireStackKeyBinding KEYBINDING_DROP_STACK;
65+
public static KeyBinding ESCAPE_KEYBINDING;
6466
// -keybindings
6567

6668
private static String makeKeyID(String keyName) {
@@ -69,7 +71,7 @@ private static String makeKeyID(String keyName) {
6971

7072
@Override
7173
public void onInitializeClient() {
72-
VersionedLogicMethodHelper.initLogicMethodsForClasses(Arrays.asList(HotbarScrollKeyBinding.class, DropEntireStackKeyBinding.class));
74+
VersionedLogicMethodHelper.initLogicMethodsForClasses(Arrays.asList(HotbarScrollKeyBinding.class, DropEntireStackKeyBinding.class, ToggleAutoJumpKeyBinding.class));
7375

7476
createKeyBindings();
7577
}
@@ -91,13 +93,13 @@ private static <K extends KeyBinding & InputEventHandler> void registerKeyBindin
9193

9294
private static void createKeyBindings() {
9395
// auto jump
94-
registerKeyBinding(new ToggleAutoJumpKeyBinding(makeKeyID("toggle_auto_jump"), InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_B, MOVEMENT_CATEGORY, new KeyModifiers()));
96+
registerKeyBindingWithHandler(new ToggleAutoJumpKeyBinding(makeKeyID("toggle_auto_jump"), InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_B, MOVEMENT_CATEGORY, new KeyModifiers()));
9597

9698
// skin layers
9799
Arrays.stream(PlayerModelPart.values())
98100
.map(playerModelPart -> new SkinLayerKeyBinding(makeKeyID("toggle_" + playerModelPart.getName().toLowerCase(Locale.ENGLISH)), InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_UNKNOWN, SKIN_LAYER_CATEGORY,
99101
playerModelPart))
100-
.forEach(Amecs::registerKeyBinding);
102+
.forEach(Amecs::registerKeyBindingWithHandler);
101103

102104
// hotbar scroll
103105
registerKeyBindingWithHandler(new HotbarScrollKeyBinding(makeKeyID("hotbar.scroll.up"), InputUtil.Type.MOUSE, KeyBindingUtils.MOUSE_SCROLL_UP, INVENTORY_CATEGORY, new KeyModifiers(), true));
@@ -107,10 +109,15 @@ private static void createKeyBindings() {
107109
// we intentionally do not register the drop stack keybinding for input handling because it is called from MixinMinecraftClient
108110
KEYBINDING_DROP_STACK = new DropEntireStackKeyBinding(makeKeyID("drop.stack"), InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_Q, INVENTORY_CATEGORY, new KeyModifiers().setControl(true));
109111
registerKeyBinding(KEYBINDING_DROP_STACK);
112+
113+
// we intentionally do not register the escape keybinding for input handling because it is checked before normal input checking and is translated to GLFW_KEY_ESCAPE
114+
ESCAPE_KEYBINDING = new AmecsKeyBinding(makeKeyID("alternative_escape"), InputUtil.Type.KEYSYM, -1, "key.categories.ui", new KeyModifiers());
115+
registerKeyBinding(ESCAPE_KEYBINDING);
110116
}
117+
// -keybindings
111118

112119
public static void sendToggleMessage(PlayerEntity playerEntity, boolean value, Text option) {
113-
playerEntity.sendMessage(new TranslatableText("amecs.toggled." + (value ? "on" : "off"), option), true);
120+
playerEntity.sendMessage(TextWrapper.translatable("amecs.toggled." + (value ? "on" : "off"), option), true);
114121
}
115122

116123
// controls gui search
@@ -138,7 +145,7 @@ public static boolean entryMatches(ControlsListWidget.KeyBindingEntry entry, Key
138145
// because checking the quality is not really cheaper and check both if they are not equal is overhead
139146
boolean categoryContains = StringUtils.containsIgnoreCase(I18n.translate(binding.getCategory()), filterSettings.searchText);
140147

141-
String entryName = ((ControlsListWidgetKeyBindingEntryAccessor) entry).getBindingName().asString();
148+
String entryName = TextWrapper.getAsString(((de.siphalor.amecs.duck.IKeyBindingEntry) entry).amecs$getBindingName());
142149
// this fixes alternative keybindings from nmuk
143150
// without this they are searched by their untranslateable translation key
144151
// we could also search alternatives by thei parent but this way you can search for only alternatives
@@ -152,6 +159,10 @@ public static boolean entryMatches(ControlsListWidget.KeyBindingEntry entry, Key
152159
}
153160

154161
public static void log(Level level, String message) {
155-
LOGGER.log(level, LOGGER_PREFIX + message);
162+
LOGGER.log(level, LOG_PREFIX + message);
163+
}
164+
165+
public static void logException(Level level, Throwable e) {
166+
LOGGER.catching(level, e);
156167
}
157168
}

‎src/main/java/de/siphalor/amecs/VersionedLogicMethodHelper.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
import org.apache.logging.log4j.Level;
1313

14-
import de.siphalor.amecs.impl.version.MinecraftVersionHelper;
14+
import de.klotzi111.fabricmultiversionhelper.api.version.MinecraftVersionHelper;
1515
import net.fabricmc.api.EnvType;
1616
import net.fabricmc.api.Environment;
1717
import net.fabricmc.loader.api.SemanticVersion;
@@ -66,7 +66,7 @@ public static List<MethodFieldAndName> getMethodFieldAndNamesForClass(Class<?> c
6666
ret.add(new MethodFieldAndName(f, logicMethodPrefix));
6767
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
6868
Amecs.log(Level.WARN, "Found logic method search method in class \"" + clazz.getName() + "\" but no associated logic method name prefix");
69-
e.printStackTrace();
69+
Amecs.logException(Level.ERROR, e);
7070
continue;
7171
}
7272
}
@@ -125,7 +125,7 @@ public Object invoke(Object instance, Object... args) {
125125
return method.invoke(instance, args);
126126
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
127127
Amecs.log(Level.ERROR, "Error while executing: \"" + method.getName() + "\" in class: \"" + method.getDeclaringClass().getName() + "\"");
128-
e.printStackTrace();
128+
Amecs.logException(Level.ERROR, e);
129129
}
130130
return null;
131131
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package de.siphalor.amecs.duck;
2+
3+
import net.minecraft.text.Text;
4+
5+
public interface IKeyBindingEntry {
6+
Text amecs$getBindingName();
7+
8+
void amecs$setBindingName(Text bindingName);
9+
}

‎src/main/java/de/siphalor/amecs/gui/SearchFieldControlsListWidget.java

+21-36
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,33 @@
11
package de.siphalor.amecs.gui;
22

3-
import java.lang.reflect.Constructor;
4-
import java.lang.reflect.InvocationTargetException;
53
import java.util.*;
64

75
import org.apache.logging.log4j.Level;
86

7+
import de.klotzi111.fabricmultiversionhelper.api.text.IMutableText;
8+
import de.klotzi111.fabricmultiversionhelper.api.text.TextWrapper;
99
import de.siphalor.amecs.Amecs;
1010
import de.siphalor.amecs.KeyBindingEntryFilterSettings;
1111
import de.siphalor.amecs.compat.NMUKProxy;
1212
import de.siphalor.amecs.impl.duck.IKeyBindingEntry;
1313
import de.siphalor.amecs.impl.duck.IKeybindsScreen;
14+
import de.siphalor.amecs.version.KeyBindingEntryVersionHelper;
15+
import de.siphalor.amecs.version.ScreenVersionHelper;
16+
import de.siphalor.amecs.version.TextFieldWidgetVersionHelper;
1417
import net.minecraft.client.MinecraftClient;
1518
import net.minecraft.client.font.TextRenderer;
1619
import net.minecraft.client.gui.Element;
1720
import net.minecraft.client.gui.Selectable;
1821
import net.minecraft.client.gui.screen.option.ControlsListWidget;
22+
import net.minecraft.client.gui.screen.option.ControlsListWidget.KeyBindingEntry;
1923
import net.minecraft.client.gui.screen.option.GameOptionsScreen;
2024
import net.minecraft.client.gui.widget.TextFieldWidget;
2125
import net.minecraft.client.option.KeyBinding;
2226
import net.minecraft.client.resource.language.I18n;
2327
import net.minecraft.client.util.math.MatrixStack;
24-
import net.minecraft.text.BaseText;
25-
import net.minecraft.text.LiteralText;
26-
import net.minecraft.text.Text;
27-
import net.minecraft.text.TranslatableText;
2828
import net.minecraft.util.Formatting;
2929

3030
public class SearchFieldControlsListWidget extends ControlsListWidget.Entry {
31-
private static final Constructor<ControlsListWidget.KeyBindingEntry> KeyBindingEntry_contructor;
32-
33-
static {
34-
Constructor<ControlsListWidget.KeyBindingEntry> local_KeyBindingEntry_contructor = null;
35-
try {
36-
// noinspection JavaReflectionMemberAccess
37-
local_KeyBindingEntry_contructor = ControlsListWidget.KeyBindingEntry.class.getDeclaredConstructor(
38-
ControlsListWidget.class, KeyBinding.class, Text.class);
39-
local_KeyBindingEntry_contructor.setAccessible(true);
40-
} catch (NoSuchMethodException | SecurityException e) {
41-
Amecs.log(Level.ERROR, "Failed to load constructor from class \"KeyBindingEntry\" with reflection");
42-
e.printStackTrace();
43-
}
44-
45-
KeyBindingEntry_contructor = local_KeyBindingEntry_contructor;
46-
}
4731

4832
public final TextFieldWidget textFieldWidget;
4933

@@ -63,17 +47,17 @@ private void copyKeyBindingEntrysFromChildren(List<ControlsListWidget.Entry> chi
6347
}
6448

6549
private void recompileChildrenList(ControlsListWidget listWidget, MinecraftClient client) {
66-
try {
67-
entries.clear();
68-
KeyBinding[] keyBindings = client.options.allKeys.clone();
69-
Arrays.sort(keyBindings);
70-
for (KeyBinding keyBinding : keyBindings) {
71-
ControlsListWidget.KeyBindingEntry entry = KeyBindingEntry_contructor.newInstance(listWidget, keyBinding, new TranslatableText(keyBinding.getTranslationKey()));
72-
entries.add(entry);
50+
entries.clear();
51+
KeyBinding[] keyBindings = client.options.allKeys.clone();
52+
Arrays.sort(keyBindings);
53+
for (KeyBinding keyBinding : keyBindings) {
54+
ControlsListWidget.KeyBindingEntry entry = (KeyBindingEntry) KeyBindingEntryVersionHelper.createKeyBindingEntry(listWidget, keyBinding, TextWrapper.translatable(keyBinding.getTranslationKey()));
55+
if (entry == null) {
56+
Amecs.log(Level.ERROR, "An unexpected error occured during recompilation of controls list!");
57+
entries.clear();
58+
return;
7359
}
74-
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
75-
Amecs.log(Level.ERROR, "An unexpected exception occured during recompilation of controls list!");
76-
e.printStackTrace();
60+
entries.add(entry);
7761
}
7862
isFirstCompile = false;
7963
}
@@ -95,7 +79,7 @@ private void filterChildrenList(ControlsListWidget listWidget, List<ControlsList
9579
lastMatched = Amecs.entryMatches(entry, filterSettings);
9680
if (lastMatched) {
9781
if (!Objects.equals(cat, lastCat)) {
98-
children.add(listWidget.new CategoryEntry(new TranslatableText(cat)));
82+
children.add(listWidget.new CategoryEntry(TextWrapper.translatable(cat)));
9983
lastCat = cat;
10084
}
10185

@@ -137,7 +121,7 @@ public SearchFieldControlsListWidget(GameOptionsScreen parent, MinecraftClient c
137121
TextRenderer textRenderer = client.textRenderer;
138122
assert parent != null;
139123

140-
textFieldWidget = new TextFieldWidget(textRenderer, parent.width / 2 - 125, 0, 250, 20, new LiteralText(""));
124+
textFieldWidget = TextFieldWidgetVersionHelper.createTextFieldWidget(textRenderer, ScreenVersionHelper.getWidth(parent) / 2 - 125, 0, 250, 20, "");
141125
textFieldWidget.setSuggestion(I18n.translate("amecs.search.placeholder"));
142126
textFieldWidget.setChangedListener(inputText -> {
143127
ControlsListWidget listWidget = ((IKeybindsScreen) parent).amecs$getControlsList();
@@ -154,6 +138,7 @@ public SearchFieldControlsListWidget(GameOptionsScreen parent, MinecraftClient c
154138

155139
// controls list has more or less children than we had last time
156140
// TODO: this is not ideal. We might NOT update if for example some external source remove one and adds one entry
141+
// but external changes SHOULD NOT happen anyways
157142
if (children.size() != lastChildrenCount) {
158143
if (!isFirstCompile) {
159144
Amecs.log(Level.INFO, "Controls search results changed externally - recompiling the list!");
@@ -171,8 +156,8 @@ public SearchFieldControlsListWidget(GameOptionsScreen parent, MinecraftClient c
171156
lastChildrenCount = children.size();
172157

173158
if (lastChildrenCount <= 1) {
174-
BaseText noResultsText = new TranslatableText(Amecs.MOD_ID + ".search.no_results");
175-
noResultsText.setStyle(noResultsText.getStyle().withColor(Formatting.GRAY));
159+
IMutableText noResultsText = (IMutableText) TextWrapper.translatable(Amecs.MOD_ID + ".search.no_results");
160+
noResultsText.fmvh$setStyle(noResultsText.fmvh$getStyle().withColor(Formatting.GRAY));
176161
children.add(listWidget.new CategoryEntry(noResultsText));
177162
lastChildrenCount++;
178163
}

‎src/main/java/de/siphalor/amecs/keybinding/DropEntireStackKeyBinding.java

+16-31
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,19 @@
55

66
import org.apache.logging.log4j.Level;
77

8+
import de.klotzi111.fabricmultiversionhelper.api.mapping.MappingHelper;
9+
import de.klotzi111.fabricmultiversionhelper.api.version.MinecraftVersionHelper;
810
import de.siphalor.amecs.Amecs;
911
import de.siphalor.amecs.VersionedLogicMethodHelper.ReflectionExceptionProxiedMethod;
1012
import de.siphalor.amecs.api.AmecsKeyBinding;
1113
import de.siphalor.amecs.api.KeyModifiers;
1214
import net.fabricmc.api.EnvType;
1315
import net.fabricmc.api.Environment;
14-
import net.fabricmc.loader.api.FabricLoader;
15-
import net.fabricmc.loader.api.MappingResolver;
1616
import net.minecraft.client.MinecraftClient;
1717
import net.minecraft.client.network.ClientPlayerEntity;
1818
import net.minecraft.client.util.InputUtil;
19+
import net.minecraft.entity.ItemEntity;
20+
import net.minecraft.entity.player.PlayerEntity;
1921
import net.minecraft.util.Hand;
2022

2123
@Environment(EnvType.CLIENT)
@@ -26,29 +28,13 @@ public class DropEntireStackKeyBinding extends AmecsKeyBinding implements DropIt
2628

2729
private static final Method ClientPlayerEntity_dropSelectedItem;
2830

29-
private static final MappingResolver MAPPING_RESOLVER = FabricLoader.getInstance().getMappingResolver();
30-
31-
private static final String INTERMEDIARY_ClientPlayerEntity_dropSelectedItem = "method_7290";
32-
private static String REMAPPED_ClientPlayerEntity_dropSelectedItem;
33-
34-
private static void resolveIntermediaryNames() {
35-
String ClientPlayerEntity_class_unmapped = MAPPING_RESOLVER.unmapClassName("intermediary", ClientPlayerEntity.class.getName());
36-
REMAPPED_ClientPlayerEntity_dropSelectedItem = MAPPING_RESOLVER.mapMethodName("intermediary", ClientPlayerEntity_class_unmapped, INTERMEDIARY_ClientPlayerEntity_dropSelectedItem, "(Z)Z");
37-
}
38-
3931
static {
40-
resolveIntermediaryNames();
41-
42-
Method local_ClientPlayerEntity_dropSelectedItem = null;
43-
try {
44-
local_ClientPlayerEntity_dropSelectedItem = ClientPlayerEntity.class.getDeclaredMethod(REMAPPED_ClientPlayerEntity_dropSelectedItem, boolean.class);
45-
local_ClientPlayerEntity_dropSelectedItem.setAccessible(true);
46-
} catch (NoSuchMethodException | SecurityException e) {
47-
Amecs.log(Level.ERROR, "Failed to load method \"dropSelectedItem\" from class \"ClientPlayerEntity\" with reflection");
48-
e.printStackTrace();
32+
Class<?> CLASS_FOR_dropSelectedItem = PlayerEntity.class;
33+
if (MinecraftVersionHelper.isMCVersionAtLeast("1.17")) {
34+
CLASS_FOR_dropSelectedItem = ClientPlayerEntity.class;
4935
}
50-
51-
ClientPlayerEntity_dropSelectedItem = local_ClientPlayerEntity_dropSelectedItem;
36+
Class<?> returnType = MinecraftVersionHelper.isMCVersionAtLeast("1.15") ? boolean.class : ItemEntity.class;
37+
ClientPlayerEntity_dropSelectedItem = MappingHelper.mapAndGetMethod(CLASS_FOR_dropSelectedItem, "method_7290", returnType, boolean.class);
5238
}
5339

5440
public DropEntireStackKeyBinding(String id, InputUtil.Type type, int code, String category, KeyModifiers defaultModifiers) {
@@ -60,7 +46,7 @@ public DropEntireStackKeyBinding(String id, InputUtil.Type type, int code, Strin
6046
// only version 1.14 and above because we do not have mappings for lower versions of minecraft and fabric anyways
6147
// the logic changed in 1.14 (compared to the newer versions)
6248
@SuppressWarnings("unused") // used via reflection
63-
private boolean dropEntireStackLogic$1_14(MinecraftClient client) {
49+
private static boolean dropEntireStackLogic$1_14(MinecraftClient client) {
6450
ClientPlayerEntity player = client.player;
6551

6652
if (!player.isSpectator()) {
@@ -72,8 +58,8 @@ public DropEntireStackKeyBinding(String id, InputUtil.Type type, int code, Strin
7258
try {
7359
ClientPlayerEntity_dropSelectedItem.invoke(player, true);
7460
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
75-
Amecs.log(Level.ERROR, "Failed to call method \"dropSelectedItem\"");
76-
e.printStackTrace();
61+
Amecs.log(Level.ERROR, "Failed to call method \"ClientPlayerEntity::dropSelectedItem\"");
62+
Amecs.logException(Level.ERROR, e);
7763
return false;
7864
}
7965
return true;
@@ -82,7 +68,7 @@ public DropEntireStackKeyBinding(String id, InputUtil.Type type, int code, Strin
8268
}
8369

8470
@SuppressWarnings("unused") // used via reflection
85-
private boolean dropEntireStackLogic$1_15(MinecraftClient client) {
71+
private static boolean dropEntireStackLogic$1_15(MinecraftClient client) {
8672
ClientPlayerEntity player = client.player;
8773

8874
// true to always drop an entire stack
@@ -93,12 +79,11 @@ public DropEntireStackKeyBinding(String id, InputUtil.Type type, int code, Strin
9379
return false;
9480
}
9581

96-
// TODO: copy the byteCode from MinecraftClient in order to remove this version check
97-
private boolean dropEntireStackLogic_currentVersion(MinecraftClient client) {
98-
return (boolean) Method_dropEntireStackLogic.invoke(this, client);
82+
private static boolean dropEntireStackLogic_currentVersion(MinecraftClient client) {
83+
return (boolean) Method_dropEntireStackLogic.invoke(null, client);
9984
}
10085

101-
// this method is NOT called by the InputHandlerManger (because we do not register it there) it is called from MixinMinecraftClient
86+
// this method is NOT called by the InputHandlerManager (because we do not register it there) it is called from MixinMinecraftClient
10287
@Override
10388
public boolean handleDropItemStackEvent(MinecraftClient client) {
10489
boolean dropped = false;

‎src/main/java/de/siphalor/amecs/keybinding/HotbarScrollKeyBinding.java

+38-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
package de.siphalor.amecs.keybinding;
22

3+
import java.lang.reflect.Field;
4+
5+
import org.apache.logging.log4j.Level;
6+
7+
import de.klotzi111.fabricmultiversionhelper.api.mapping.MappingHelper;
8+
import de.klotzi111.fabricmultiversionhelper.api.version.MinecraftVersionHelper;
9+
import de.siphalor.amecs.Amecs;
310
import de.siphalor.amecs.VersionedLogicMethodHelper.ReflectionExceptionProxiedMethod;
411
import de.siphalor.amecs.api.AmecsKeyBinding;
512
import de.siphalor.amecs.api.KeyModifiers;
@@ -9,6 +16,8 @@
916
import net.fabricmc.api.Environment;
1017
import net.minecraft.client.MinecraftClient;
1118
import net.minecraft.client.util.InputUtil;
19+
import net.minecraft.entity.player.PlayerEntity;
20+
import net.minecraft.entity.player.PlayerInventory;
1221
import net.minecraft.util.math.MathHelper;
1322

1423
@Environment(EnvType.CLIENT)
@@ -29,11 +38,35 @@ public HotbarScrollKeyBinding(String id, InputUtil.Type type, int code, String c
2938
this.scrollUp = scrollUp;
3039
}
3140

41+
private static final Field PlayerEntity_inventory;
42+
43+
static {
44+
if (!MinecraftVersionHelper.isMCVersionAtLeast("1.17")) {
45+
PlayerEntity_inventory = MappingHelper.mapAndGetField(PlayerEntity.class, "field_7514", PlayerInventory.class);
46+
} else {
47+
PlayerEntity_inventory = null;
48+
}
49+
}
50+
51+
private static PlayerInventory getPlayerInventory(PlayerEntity player) {
52+
if (!MinecraftVersionHelper.isMCVersionAtLeast("1.17")) {
53+
try {
54+
return (PlayerInventory) PlayerEntity_inventory.get(player);
55+
} catch (IllegalAccessException | IllegalArgumentException e) {
56+
Amecs.log(Level.ERROR, "Failed to get field \"PlayerEntity::inventory\"");
57+
Amecs.logException(Level.ERROR, e);
58+
return null;
59+
}
60+
} else {
61+
return player.getInventory();
62+
}
63+
}
64+
3265
// from minecraft code: Mouse
3366

3467
// only version 1.14 and above because we do not have mappings for lower versions of minecraft and fabric anyways
3568
@SuppressWarnings("unused") // used via reflection
36-
private void scrollLogic$1_14(MinecraftClient client, int scrollCount) {
69+
private static void scrollLogic$1_14(MinecraftClient client, int scrollCount) {
3770
if (client.player.isSpectator()) {
3871
if (client.inGameHud.getSpectatorHud().isOpen()) {
3972
client.inGameHud.getSpectatorHud().cycleSlot(-scrollCount);
@@ -42,13 +75,14 @@ public HotbarScrollKeyBinding(String id, InputUtil.Type type, int code, String c
4275
client.player.getAbilities().setFlySpeed(h);
4376
}
4477
} else {
45-
client.player.getInventory().scrollInHotbar(scrollCount);
78+
PlayerInventory inventory = getPlayerInventory(client.player);
79+
inventory.scrollInHotbar(scrollCount);
4680
}
4781
}
4882

4983
// TODO: copy the byteCode from Mouse in order to remove this version check
50-
private void scrollLogic_currentVersion(MinecraftClient client, int scrollCount) {
51-
Method_scrollLogic.invoke(this, client, scrollCount);
84+
private static void scrollLogic_currentVersion(MinecraftClient client, int scrollCount) {
85+
Method_scrollLogic.invoke(null, client, scrollCount);
5286
}
5387

5488
@Override

‎src/main/java/de/siphalor/amecs/keybinding/SkinLayerKeyBinding.java

+7-5
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@
33
import de.siphalor.amecs.Amecs;
44
import de.siphalor.amecs.api.AmecsKeyBinding;
55
import de.siphalor.amecs.api.KeyModifiers;
6+
import de.siphalor.amecs.api.input.InputEventHandler;
67
import net.minecraft.client.MinecraftClient;
78
import net.minecraft.client.render.entity.PlayerModelPart;
89
import net.minecraft.client.util.InputUtil;
910

10-
public class SkinLayerKeyBinding extends AmecsKeyBinding {
11+
public class SkinLayerKeyBinding extends AmecsKeyBinding implements InputEventHandler {
1112
private final PlayerModelPart playerModelPart;
1213

1314
public SkinLayerKeyBinding(String id, InputUtil.Type type, int code, String category, PlayerModelPart playerModelPart) {
@@ -16,9 +17,10 @@ public SkinLayerKeyBinding(String id, InputUtil.Type type, int code, String cate
1617
}
1718

1819
@Override
19-
public void onPressed() {
20-
MinecraftClient client = MinecraftClient.getInstance();
21-
client.options.togglePlayerModelPart(playerModelPart, !client.options.isPlayerModelPartEnabled(playerModelPart));
22-
Amecs.sendToggleMessage(client.player, client.options.isPlayerModelPartEnabled(playerModelPart), playerModelPart.getOptionName());
20+
public void handleInput(MinecraftClient client) {
21+
while (wasPressed()) {
22+
client.options.togglePlayerModelPart(playerModelPart, !client.options.isPlayerModelPartEnabled(playerModelPart));
23+
Amecs.sendToggleMessage(client.player, client.options.isPlayerModelPartEnabled(playerModelPart), playerModelPart.getOptionName());
24+
}
2325
}
2426
}
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,72 @@
11
package de.siphalor.amecs.keybinding;
22

3+
import java.lang.reflect.Field;
4+
5+
import org.apache.logging.log4j.Level;
6+
7+
import de.klotzi111.fabricmultiversionhelper.api.mapping.MappingHelper;
8+
import de.klotzi111.fabricmultiversionhelper.api.text.TextWrapper;
9+
import de.klotzi111.fabricmultiversionhelper.api.version.MinecraftVersionHelper;
310
import de.siphalor.amecs.Amecs;
11+
import de.siphalor.amecs.VersionedLogicMethodHelper.ReflectionExceptionProxiedMethod;
412
import de.siphalor.amecs.api.AmecsKeyBinding;
513
import de.siphalor.amecs.api.KeyModifiers;
14+
import de.siphalor.amecs.api.input.InputEventHandler;
615
import net.minecraft.client.MinecraftClient;
16+
import net.minecraft.client.option.GameOptions;
17+
import net.minecraft.client.option.SimpleOption;
718
import net.minecraft.client.util.InputUtil;
8-
import net.minecraft.text.TranslatableText;
919

10-
public class ToggleAutoJumpKeyBinding extends AmecsKeyBinding {
20+
public class ToggleAutoJumpKeyBinding extends AmecsKeyBinding implements InputEventHandler {
21+
@SuppressWarnings("unused") // used via reflection
22+
private static final String Method_toggleAutoJump_PREFIX = "toggleAutoJump$";
23+
private static ReflectionExceptionProxiedMethod Method_toggleAutoJump;
24+
25+
private static final Field GameOptions_autoJump;
26+
27+
static {
28+
if (!MinecraftVersionHelper.isMCVersionAtLeast("1.19")) {
29+
GameOptions_autoJump = MappingHelper.mapAndGetField(GameOptions.class, "field_1848", boolean.class);
30+
} else {
31+
GameOptions_autoJump = null;
32+
}
33+
}
34+
1135
public ToggleAutoJumpKeyBinding(String id, InputUtil.Type type, int code, String category, KeyModifiers defaultModifiers) {
1236
super(id, type, code, category, defaultModifiers);
1337
}
1438

39+
// only version 1.14 and above because we do not have mappings for lower versions of minecraft and fabric anyways
40+
@SuppressWarnings("unused") // used via reflection
41+
private static boolean toggleAutoJump$1_14(GameOptions options) {
42+
try {
43+
boolean newValue = !GameOptions_autoJump.getBoolean(options);
44+
GameOptions_autoJump.setBoolean(options, newValue);
45+
return newValue;
46+
} catch (IllegalAccessException | IllegalArgumentException e) {
47+
Amecs.log(Level.ERROR, "Failed to get field \"GameOptions::autoJump\"");
48+
Amecs.logException(Level.ERROR, e);
49+
return false;
50+
}
51+
}
52+
53+
@SuppressWarnings("unused") // used via reflection
54+
private static boolean toggleAutoJump$1_19(GameOptions options) {
55+
SimpleOption<Boolean> autoJump = options.getAutoJump();
56+
boolean newValue = !autoJump.getValue();
57+
autoJump.setValue(newValue);
58+
return newValue;
59+
}
60+
61+
private static boolean toggleAutoJump_currentVersion(GameOptions options) {
62+
return (boolean) Method_toggleAutoJump.invoke(null, options);
63+
}
64+
1565
@Override
16-
public void onPressed() {
17-
MinecraftClient client = MinecraftClient.getInstance();
18-
client.options.autoJump = !client.options.autoJump;
19-
Amecs.sendToggleMessage(client.player, client.options.autoJump, new TranslatableText("amecs.toggled.auto_jump"));
66+
public void handleInput(MinecraftClient client) {
67+
while (wasPressed()) {
68+
boolean autoJumpEnabled = toggleAutoJump_currentVersion(client.options);
69+
Amecs.sendToggleMessage(client.player, autoJumpEnabled, TextWrapper.translatable("amecs.toggled.auto_jump"));
70+
}
2071
}
2172
}

‎src/main/java/de/siphalor/amecs/mixin/AmecsMixinConfig.java

+13-38
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,34 @@
11
package de.siphalor.amecs.mixin;
22

3-
import static de.siphalor.amecs.impl.mixin.AmecsAPIMixinConfig.MIXIN_VERSIONED_PACKAGE;
4-
import static de.siphalor.amecs.impl.mixin.AmecsAPIMixinConfig.prependMixinPackages;
5-
6-
import java.util.ArrayList;
7-
import java.util.Collections;
3+
import java.util.HashMap;
84
import java.util.List;
95
import java.util.Set;
106

117
import org.objectweb.asm.tree.ClassNode;
128
import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
139
import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
1410

15-
import de.siphalor.amecs.impl.version.MinecraftVersionHelper;
11+
import de.klotzi111.fabricmultiversionhelper.api.mixinselect.MixinSelectConfig;
12+
import de.klotzi111.fabricmultiversionhelper.impl.mixinselect.ModVersionHelper;
1613
import net.fabricmc.api.EnvType;
1714
import net.fabricmc.api.Environment;
15+
import net.fabricmc.loader.api.FabricLoader;
16+
import net.fabricmc.loader.api.ModContainer;
17+
import net.fabricmc.loader.api.Version;
1818

1919
@Environment(EnvType.CLIENT)
2020
public class AmecsMixinConfig implements IMixinConfigPlugin {
2121

22-
private List<String> finalAdditionalMixinClasses = new ArrayList<>();
23-
24-
private List<String> additionalMixinClasses = new ArrayList<>();
22+
// we can NOT use MOD_ID field because that would cause all statically class references in that class to be loaded to early
23+
private static final ModContainer MOD_CONTAINER = FabricLoader.getInstance().getModContainer("amecs").get();
2524

26-
private void addMixins(String... mixinNames) {
27-
Collections.addAll(additionalMixinClasses, mixinNames);
28-
}
29-
30-
private void pushMixinsToFinal() {
31-
finalAdditionalMixinClasses.addAll(additionalMixinClasses);
32-
additionalMixinClasses.clear();
33-
}
25+
private List<String> mixinClasses = null;
3426

3527
@Override
3628
public void onLoad(String mixinPackage) {
37-
// TODO: add a json config file where for each mixinClassName a modID requirement can be made. Like in the fabric.mod.json#depends.
38-
// for now doing it in here
39-
40-
// the order of the if statements is important. The highest version must be checked first
41-
// Mouse changes
42-
if (MinecraftVersionHelper.IS_AT_LEAST_V1_18_2) {
43-
addMixins("MixinMouse_1_18_2");
44-
} else {
45-
addMixins("MixinMouse_1_14");
46-
}
47-
48-
if (MinecraftVersionHelper.IS_AT_LEAST_V1_18) {
49-
addMixins("MixinKeybindsScreen");
50-
} else {
51-
// Minecraft 1.17 and below
52-
addMixins("MixinControlsOptionsScreen");
53-
}
54-
55-
additionalMixinClasses = prependMixinPackages(additionalMixinClasses, MIXIN_VERSIONED_PACKAGE);
56-
pushMixinsToFinal();
29+
MixinSelectConfig selectConfig = MixinSelectConfig.loadMixinSelectConfig(MOD_CONTAINER);
30+
HashMap<String, Version> modsWithVersion = ModVersionHelper.getAllModsWithVersion(FabricLoader.getInstance(), true);
31+
mixinClasses = selectConfig.getAllowedMixins(mixinPackage, this.getClass().getClassLoader(), modsWithVersion);
5732
}
5833

5934
@Override
@@ -73,7 +48,7 @@ public void acceptTargets(Set<String> myTargets, Set<String> otherTargets) {
7348

7449
@Override
7550
public List<String> getMixins() {
76-
return finalAdditionalMixinClasses == null ? null : (finalAdditionalMixinClasses.isEmpty() ? null : finalAdditionalMixinClasses);
51+
return mixinClasses == null ? null : (mixinClasses.isEmpty() ? null : mixinClasses);
7752
}
7853

7954
@Override

‎src/main/java/de/siphalor/amecs/mixin/ControlsListWidgetKeyBindingEntryAccessor.java

-4
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,9 @@
55

66
import net.minecraft.client.gui.screen.option.ControlsListWidget;
77
import net.minecraft.client.gui.widget.ButtonWidget;
8-
import net.minecraft.text.Text;
98

109
@Mixin(ControlsListWidget.KeyBindingEntry.class)
1110
public interface ControlsListWidgetKeyBindingEntryAccessor {
1211
@Accessor
1312
ButtonWidget getEditButton();
14-
15-
@Accessor
16-
Text getBindingName();
1713
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package de.siphalor.amecs.mixin;
2+
3+
import org.lwjgl.glfw.GLFW;
4+
import org.spongepowered.asm.mixin.Mixin;
5+
import org.spongepowered.asm.mixin.injection.At;
6+
import org.spongepowered.asm.mixin.injection.ModifyVariable;
7+
8+
import de.siphalor.amecs.Amecs;
9+
import net.minecraft.client.Keyboard;
10+
11+
@Mixin(Keyboard.class)
12+
public class MixinKeyboard {
13+
14+
@ModifyVariable(
15+
method = "onKey",
16+
argsOnly = true,
17+
ordinal = 0,
18+
at = @At(value = "FIELD", target = "Lnet/minecraft/client/Keyboard;debugCrashStartTime:J"))
19+
public int modifyPressedKey(int key, long window, int key_, int scancode) {
20+
if (Amecs.ESCAPE_KEYBINDING.matchesKey(key, scancode)) {
21+
return GLFW.GLFW_KEY_ESCAPE;
22+
}
23+
return key;
24+
}
25+
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package de.siphalor.amecs.mixin.versioned;
2+
3+
import org.objectweb.asm.Opcodes;
4+
import org.spongepowered.asm.mixin.Mixin;
5+
import org.spongepowered.asm.mixin.injection.At;
6+
import org.spongepowered.asm.mixin.injection.At.Shift;
7+
import org.spongepowered.asm.mixin.injection.Inject;
8+
import org.spongepowered.asm.mixin.injection.Slice;
9+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
10+
11+
import net.minecraft.client.gui.screen.Screen;
12+
import net.minecraft.client.gui.screen.option.ControlsOptionsScreen;
13+
import net.minecraft.client.gui.screen.option.GameOptionsScreen;
14+
import net.minecraft.client.option.GameOptions;
15+
import net.minecraft.text.Text;
16+
17+
@Mixin(ControlsOptionsScreen.class)
18+
public abstract class MixinControlsOptionsScreen_1_14 extends GameOptionsScreen {
19+
20+
// ignored
21+
public MixinControlsOptionsScreen_1_14(Screen parent, GameOptions gameOptions, Text title) {
22+
super(parent, gameOptions, title);
23+
}
24+
25+
@Inject(
26+
method = "init()V",
27+
remap = false,
28+
slice = @Slice(
29+
from = @At(
30+
value = "NEW",
31+
target = "(Lnet/minecraft/class_458;Lnet/minecraft/class_310;)Lnet/minecraft/class_459;" // (Lnet/minecraft/client/gui/screen/options/ControlsOptionsScreen;Lnet/minecraft/client/MinecraftClient;)Lnet/minecraft/client/gui/screen/options/ControlsListWidget;
32+
)),
33+
at = @At(
34+
value = "FIELD",
35+
opcode = Opcodes.PUTFIELD,
36+
shift = Shift.AFTER,
37+
target = "Lnet/minecraft/class_458;field_2728:Lnet/minecraft/class_459;" // Lnet/minecraft/client/gui/screen/option/ControlsOptionsScreen;keyBindingListWidget:Lnet/minecraft/client/gui/screen/option/ControlsListWidget;
38+
))
39+
public void init_afterConstructListWidget(CallbackInfo ci) {
40+
// FIXME: This does not work because methods inside SearchFieldControlsListWidget implement the methods from the interface and so have intermediary names
41+
// but mc < 1.16 has plain intermediary names and does not match. Also the method signatures are different
42+
// we need multiple source sets that compile a class with 2 different mc mappings
43+
44+
// MixinControlsOptionsScreenImpl.addSearchEntry((ControlsOptionsScreen) (Object) this);
45+
}
46+
47+
}

‎src/main/java/de/siphalor/amecs/mixin/versioned/MixinControlsOptionsScreen.java ‎src/main/java/de/siphalor/amecs/mixin/versioned/MixinControlsOptionsScreen_1_16.java

+4-10
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,18 @@
88
import org.spongepowered.asm.mixin.injection.Slice;
99
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
1010

11-
import de.siphalor.amecs.gui.SearchFieldControlsListWidget;
12-
import de.siphalor.amecs.impl.duck.IKeybindsScreen;
11+
import de.siphalor.amecs.mixinimpl.MixinControlsOptionsScreenImpl;
1312
import net.minecraft.client.gui.screen.Screen;
14-
import net.minecraft.client.gui.screen.option.ControlsListWidget;
1513
import net.minecraft.client.gui.screen.option.ControlsOptionsScreen;
1614
import net.minecraft.client.gui.screen.option.GameOptionsScreen;
1715
import net.minecraft.client.option.GameOptions;
1816
import net.minecraft.text.Text;
1917

2018
@Mixin(ControlsOptionsScreen.class)
21-
public abstract class MixinControlsOptionsScreen extends GameOptionsScreen {
19+
public abstract class MixinControlsOptionsScreen_1_16 extends GameOptionsScreen {
2220

2321
// ignored
24-
public MixinControlsOptionsScreen(Screen parent, GameOptions gameOptions, Text title) {
22+
public MixinControlsOptionsScreen_1_16(Screen parent, GameOptions gameOptions, Text title) {
2523
super(parent, gameOptions, title);
2624
}
2725

@@ -37,11 +35,7 @@ public MixinControlsOptionsScreen(Screen parent, GameOptions gameOptions, Text t
3735
shift = Shift.AFTER,
3836
target = "Lnet/minecraft/client/gui/screen/option/ControlsOptionsScreen;keyBindingListWidget:Lnet/minecraft/client/gui/screen/option/ControlsListWidget;"))
3937
public void init_afterConstructListWidget(CallbackInfo ci) {
40-
ControlsListWidget listWidget = ((IKeybindsScreen) this).amecs$getControlsList();
41-
42-
SearchFieldControlsListWidget searchEntry = new SearchFieldControlsListWidget(this, client);
43-
44-
listWidget.children().add(0, searchEntry);
38+
MixinControlsOptionsScreenImpl.addSearchEntry((ControlsOptionsScreen) (Object) this);
4539
}
4640

4741
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package de.siphalor.amecs.mixin.versioned;
2+
3+
import org.spongepowered.asm.mixin.Final;
4+
import org.spongepowered.asm.mixin.Mixin;
5+
import org.spongepowered.asm.mixin.Mutable;
6+
import org.spongepowered.asm.mixin.Shadow;
7+
8+
import de.klotzi111.fabricmultiversionhelper.api.text.TextWrapper;
9+
import de.siphalor.amecs.duck.IKeyBindingEntry;
10+
import net.minecraft.client.gui.screen.option.ControlsListWidget.KeyBindingEntry;
11+
import net.minecraft.text.Text;
12+
13+
@Mixin(KeyBindingEntry.class)
14+
public abstract class MixinKeyBindingEntry_1_14 implements IKeyBindingEntry {
15+
16+
@Mutable
17+
@Shadow(aliases = {"field_2741", "bindingName"}, remap = false)
18+
@Final
19+
private String bindingName;
20+
21+
@Override
22+
public void amecs$setBindingName(Text bindingName) {
23+
this.bindingName = bindingName.getString();
24+
}
25+
26+
@Override
27+
public Text amecs$getBindingName() {
28+
return TextWrapper.literal(bindingName);
29+
}
30+
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package de.siphalor.amecs.mixin.versioned;
2+
3+
import org.spongepowered.asm.mixin.Final;
4+
import org.spongepowered.asm.mixin.Mixin;
5+
import org.spongepowered.asm.mixin.Mutable;
6+
import org.spongepowered.asm.mixin.Shadow;
7+
8+
import de.siphalor.amecs.duck.IKeyBindingEntry;
9+
import net.minecraft.client.gui.screen.option.ControlsListWidget.KeyBindingEntry;
10+
import net.minecraft.text.Text;
11+
12+
@Mixin(KeyBindingEntry.class)
13+
public abstract class MixinKeyBindingEntry_1_16 implements IKeyBindingEntry {
14+
15+
@Mutable
16+
@Shadow
17+
@Final
18+
private Text bindingName;
19+
20+
@Override
21+
public void amecs$setBindingName(Text bindingName) {
22+
this.bindingName = bindingName;
23+
}
24+
25+
@Override
26+
public Text amecs$getBindingName() {
27+
return bindingName;
28+
}
29+
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package de.siphalor.amecs.mixin.versioned;
2+
3+
import org.spongepowered.asm.mixin.Mixin;
4+
import org.spongepowered.asm.mixin.injection.At;
5+
import org.spongepowered.asm.mixin.injection.Redirect;
6+
7+
import de.siphalor.amecs.mixinimpl.MixinScreenImpl;
8+
import net.minecraft.client.MinecraftClient;
9+
import net.minecraft.client.gui.screen.Screen;
10+
11+
@Mixin(Screen.class)
12+
public abstract class MixinScreen_1_14 {
13+
14+
@Redirect(
15+
method = "resize(Lnet/minecraft/client/MinecraftClient;II)V",
16+
remap = false,
17+
at = @At(
18+
value = "INVOKE",
19+
target = "Lnet/minecraft/class_437;init(Lnet/minecraft/class_310;II)V",
20+
remap = false))
21+
public void resize(Screen screen, MinecraftClient client, int width, int height) {
22+
MixinScreenImpl.resize(screen, client, width, height);
23+
}
24+
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package de.siphalor.amecs.mixin.versioned;
2+
3+
import org.spongepowered.asm.mixin.Mixin;
4+
import org.spongepowered.asm.mixin.injection.At;
5+
import org.spongepowered.asm.mixin.injection.Redirect;
6+
7+
import de.siphalor.amecs.mixinimpl.MixinScreenImpl;
8+
import net.minecraft.client.MinecraftClient;
9+
import net.minecraft.client.gui.screen.Screen;
10+
11+
@Mixin(Screen.class)
12+
public abstract class MixinScreen_1_16 {
13+
14+
@Redirect(
15+
method = "resize(Lnet/minecraft/client/MinecraftClient;II)V",
16+
at = @At(
17+
value = "INVOKE",
18+
target = "Lnet/minecraft/client/gui/screen/Screen;init(Lnet/minecraft/client/MinecraftClient;II)V"))
19+
public void resize(Screen screen, MinecraftClient client, int width, int height) {
20+
MixinScreenImpl.resize(screen, client, width, height);
21+
}
22+
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package de.siphalor.amecs.mixinimpl;
2+
3+
import de.siphalor.amecs.gui.SearchFieldControlsListWidget;
4+
import de.siphalor.amecs.impl.duck.IKeybindsScreen;
5+
import de.siphalor.amecs.version.ParentElementVersionHelper;
6+
import de.siphalor.amecs.version.ScreenVersionHelper;
7+
import net.minecraft.client.gui.screen.option.ControlsListWidget;
8+
import net.minecraft.client.gui.screen.option.ControlsOptionsScreen;
9+
10+
public class MixinControlsOptionsScreenImpl {
11+
12+
public static void addSearchEntry(ControlsOptionsScreen screen) {
13+
ControlsListWidget listWidget = ((IKeybindsScreen) screen).amecs$getControlsList();
14+
15+
@SuppressWarnings("resource")
16+
SearchFieldControlsListWidget searchEntry = new SearchFieldControlsListWidget(screen, ScreenVersionHelper.getClient(screen));
17+
18+
ParentElementVersionHelper.getChildren(listWidget).add(0, searchEntry);
19+
}
20+
21+
}
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,40 @@
1-
package de.siphalor.amecs.mixin;
1+
package de.siphalor.amecs.mixinimpl;
22

3-
import org.spongepowered.asm.mixin.Mixin;
4-
import org.spongepowered.asm.mixin.injection.At;
5-
import org.spongepowered.asm.mixin.injection.Redirect;
3+
import java.util.List;
64

75
import de.siphalor.amecs.gui.SearchFieldControlsListWidget;
86
import de.siphalor.amecs.impl.duck.IKeybindsScreen;
97
import de.siphalor.amecs.impl.version.KeybindsScreenVersionHelper;
8+
import de.siphalor.amecs.version.ParentElementVersionHelper;
9+
import de.siphalor.amecs.version.ScreenVersionHelper;
1010
import net.minecraft.client.MinecraftClient;
11+
import net.minecraft.client.gui.Element;
1112
import net.minecraft.client.gui.screen.Screen;
1213
import net.minecraft.client.gui.screen.option.ControlsListWidget;
1314
import net.minecraft.client.gui.screen.option.ControlsListWidget.Entry;
1415

15-
@Mixin(Screen.class)
16-
public abstract class MixinScreen {
16+
public class MixinScreenImpl {
1717

18-
@Redirect(
19-
method = "resize(Lnet/minecraft/client/MinecraftClient;II)V",
20-
at = @At(
21-
value = "INVOKE",
22-
target = "Lnet/minecraft/client/gui/screen/Screen;init(Lnet/minecraft/client/MinecraftClient;II)V"))
23-
public void resize(Screen screen, MinecraftClient client, int width, int height) {
18+
public static void resize(Screen screen, MinecraftClient client, int width, int height) {
2419
// screen can only be non-null here
2520
if (KeybindsScreenVersionHelper.ACTUAL_KEYBINDS_SCREEN_CLASS.isAssignableFrom(screen.getClass())) {
2621
ControlsListWidget listWidget = ((IKeybindsScreen) screen).amecs$getControlsList();
2722
// the first element might not be the search widget
2823
// for example when controlling (mod) is loaded and it uses its own KeybindsScreen but we do (and want) not add it there
29-
Entry entry = listWidget.children().size() > 0 ? listWidget.children().get(0) : null;
24+
25+
List<? extends Element> children = ParentElementVersionHelper.getChildren(listWidget);
26+
Entry entry = children.size() > 0 ? (Entry) children.get(0) : null;
3027
if (entry instanceof SearchFieldControlsListWidget) {
3128
SearchFieldControlsListWidget searchWidget = (SearchFieldControlsListWidget) entry;
3229
String oldSearchText = searchWidget.textFieldWidget.getText();
33-
screen.init(client, width, height);
30+
ScreenVersionHelper.init(screen, client, width, height);
3431
listWidget = ((IKeybindsScreen) screen).amecs$getControlsList();
35-
searchWidget = (SearchFieldControlsListWidget) listWidget.children().get(0);
32+
searchWidget = (SearchFieldControlsListWidget) children.get(0);
3633
searchWidget.textFieldWidget.setText(oldSearchText);
3734
return;
3835
}
3936
}
40-
screen.init(client, width, height);
37+
ScreenVersionHelper.init(screen, client, width, height);
4138
}
39+
4240
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package de.siphalor.amecs.version;
2+
3+
import java.lang.reflect.Constructor;
4+
import java.lang.reflect.InvocationTargetException;
5+
6+
import org.apache.logging.log4j.Level;
7+
8+
import de.klotzi111.fabricmultiversionhelper.api.mapping.MappingHelper;
9+
import de.klotzi111.fabricmultiversionhelper.api.version.MinecraftVersionHelper;
10+
import de.siphalor.amecs.Amecs;
11+
import de.siphalor.amecs.duck.IKeyBindingEntry;
12+
import net.minecraft.client.gui.screen.option.ControlsListWidget;
13+
import net.minecraft.client.gui.screen.option.ControlsListWidget.KeyBindingEntry;
14+
import net.minecraft.client.option.KeyBinding;
15+
import net.minecraft.text.Text;
16+
17+
public class KeyBindingEntryVersionHelper {
18+
19+
private static final Constructor<KeyBindingEntry> KeyBindingEntry_contructor;
20+
21+
static {
22+
String SIGNATURE_KeyBindingEntry_contructor = null;
23+
if (MinecraftVersionHelper.isMCVersionAtLeast("1.16")) {
24+
SIGNATURE_KeyBindingEntry_contructor = MappingHelper.createSignature("(%s%s%s)V", ControlsListWidget.class, KeyBinding.class, Text.class);
25+
} else {
26+
SIGNATURE_KeyBindingEntry_contructor = MappingHelper.createSignature("(%s%s)V", ControlsListWidget.class, KeyBinding.class);
27+
}
28+
KeyBindingEntry_contructor = (Constructor<KeyBindingEntry>) MappingHelper.getConstructor(KeyBindingEntry.class, SIGNATURE_KeyBindingEntry_contructor);
29+
}
30+
31+
public static IKeyBindingEntry createKeyBindingEntry(ControlsListWidget listWidget, KeyBinding binding, Text bindingName) {
32+
try {
33+
Object[] instanceArgs = null;
34+
if (MinecraftVersionHelper.isMCVersionAtLeast("1.16")) {
35+
instanceArgs = new Object[] {listWidget, binding, bindingName};
36+
} else {
37+
// if we are below minecraft 1.16 we can not parse the text to the constructors ...
38+
instanceArgs = new Object[] {listWidget, binding};
39+
}
40+
IKeyBindingEntry ret = (IKeyBindingEntry) KeyBindingEntry_contructor.newInstance(instanceArgs);
41+
if (!MinecraftVersionHelper.isMCVersionAtLeast("1.16")) {
42+
// ... so we set it afterwards and hope for the best
43+
ret.amecs$setBindingName(bindingName);
44+
}
45+
return ret;
46+
} catch (IllegalAccessException | InstantiationException | InvocationTargetException e) {
47+
Amecs.log(Level.ERROR, "Failed to create new instance of \"KeyBindingEntry\"");
48+
Amecs.logException(Level.ERROR, e);
49+
}
50+
return null;
51+
}
52+
53+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package de.siphalor.amecs.version;
2+
3+
import java.lang.reflect.InvocationTargetException;
4+
import java.lang.reflect.Method;
5+
import java.util.List;
6+
7+
import org.apache.logging.log4j.Level;
8+
9+
import de.klotzi111.fabricmultiversionhelper.api.mapping.MappingHelper;
10+
import de.klotzi111.fabricmultiversionhelper.api.version.MinecraftVersionHelper;
11+
import de.siphalor.amecs.Amecs;
12+
import net.minecraft.client.gui.Element;
13+
import net.minecraft.client.gui.ParentElement;
14+
import net.minecraft.client.gui.widget.EntryListWidget;
15+
16+
public class ParentElementVersionHelper {
17+
18+
private static final Method ParentElement_children;
19+
20+
static {
21+
String INTERMEDIARY_Screen_children = MinecraftVersionHelper.isMCVersionAtLeast("1.16") ? "method_25396" : "children";
22+
ParentElement_children = MappingHelper.mapAndGetMethod(ParentElement.class, INTERMEDIARY_Screen_children, List.class);
23+
}
24+
25+
@SuppressWarnings("unchecked")
26+
public static List<Element> getChildren(EntryListWidget<?> listWidget) {
27+
try {
28+
return (List<Element>) ParentElement_children.invoke(listWidget);
29+
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
30+
Amecs.log(Level.ERROR, "Failed to call method \"ParentElement::children\"");
31+
Amecs.logException(Level.ERROR, e);
32+
return null;
33+
}
34+
}
35+
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package de.siphalor.amecs.version;
2+
3+
import java.lang.reflect.Field;
4+
import java.lang.reflect.InvocationTargetException;
5+
import java.lang.reflect.Method;
6+
7+
import org.apache.logging.log4j.Level;
8+
9+
import de.klotzi111.fabricmultiversionhelper.api.mapping.MappingHelper;
10+
import de.klotzi111.fabricmultiversionhelper.api.version.MinecraftVersionHelper;
11+
import de.siphalor.amecs.Amecs;
12+
import net.minecraft.client.MinecraftClient;
13+
import net.minecraft.client.gui.screen.Screen;
14+
15+
public class ScreenVersionHelper {
16+
17+
private static final Field Screen_client;
18+
private static final Field Screen_width;
19+
private static final Method Screen_init;
20+
21+
static {
22+
String INTERMEDIARY_Screen_client = MinecraftVersionHelper.isMCVersionAtLeast("1.16") ? "field_22787" : "minecraft";
23+
Screen_client = MappingHelper.mapAndGetField(Screen.class, INTERMEDIARY_Screen_client, MinecraftClient.class);
24+
String INTERMEDIARY_Screen_width = MinecraftVersionHelper.isMCVersionAtLeast("1.16") ? "field_22789" : "width";
25+
Screen_width = MappingHelper.mapAndGetField(Screen.class, INTERMEDIARY_Screen_width, int.class);
26+
String INTERMEDIARY_Screen_init = MinecraftVersionHelper.isMCVersionAtLeast("1.16") ? "method_25423" : "init";
27+
Screen_init = MappingHelper.mapAndGetMethod(Screen.class, INTERMEDIARY_Screen_init, void.class, MinecraftClient.class, int.class, int.class);
28+
}
29+
30+
public static MinecraftClient getClient(Screen screen) {
31+
try {
32+
return (MinecraftClient) Screen_client.get(screen);
33+
} catch (IllegalAccessException | IllegalArgumentException e) {
34+
Amecs.log(Level.ERROR, "Failed to get field \"Screen::client\"");
35+
Amecs.logException(Level.ERROR, e);
36+
return null;
37+
}
38+
}
39+
40+
public static int getWidth(Screen screen) {
41+
try {
42+
return Screen_width.getInt(screen);
43+
} catch (IllegalAccessException | IllegalArgumentException e) {
44+
Amecs.log(Level.ERROR, "Failed to get field \"Screen::width\"");
45+
Amecs.logException(Level.ERROR, e);
46+
return 0;
47+
}
48+
}
49+
50+
public static void init(Screen screen, MinecraftClient client, int width, int height) {
51+
try {
52+
Screen_init.invoke(screen, client, width, height);
53+
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
54+
Amecs.log(Level.ERROR, "Failed to call method \"Screen::init\"");
55+
Amecs.logException(Level.ERROR, e);
56+
}
57+
}
58+
59+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package de.siphalor.amecs.version;
2+
3+
import java.lang.reflect.Constructor;
4+
import java.lang.reflect.InvocationTargetException;
5+
6+
import org.apache.logging.log4j.Level;
7+
8+
import de.klotzi111.fabricmultiversionhelper.api.mapping.MappingHelper;
9+
import de.klotzi111.fabricmultiversionhelper.api.text.TextWrapper;
10+
import de.klotzi111.fabricmultiversionhelper.api.version.MinecraftVersionHelper;
11+
import de.siphalor.amecs.Amecs;
12+
import net.minecraft.client.font.TextRenderer;
13+
import net.minecraft.client.gui.widget.TextFieldWidget;
14+
import net.minecraft.text.Text;
15+
16+
public class TextFieldWidgetVersionHelper {
17+
18+
private static final Constructor<TextFieldWidget> TextFieldWidget_contructor;
19+
20+
static {
21+
Class<?> argumentClass = MinecraftVersionHelper.isMCVersionAtLeast("1.16") ? Text.class : String.class;
22+
String SIGNATURE_TextFieldWidget_contructor = MappingHelper.createSignature("(%sIIII%s)V", TextRenderer.class, argumentClass);
23+
TextFieldWidget_contructor = (Constructor<TextFieldWidget>) MappingHelper.getConstructor(TextFieldWidget.class, SIGNATURE_TextFieldWidget_contructor);
24+
}
25+
26+
public static TextFieldWidget createTextFieldWidget(TextRenderer textRenderer, int x, int y, int width, int height, String text) {
27+
try {
28+
Object[] instanceArgs = new Object[] {textRenderer, x, y, width, height, MinecraftVersionHelper.isMCVersionAtLeast("1.16") ? TextWrapper.literal(text) : text};
29+
return TextFieldWidget_contructor.newInstance(instanceArgs);
30+
} catch (IllegalAccessException | InstantiationException | InvocationTargetException e) {
31+
Amecs.log(Level.ERROR, "Failed to create new instance of \"TextFieldWidget\"");
32+
Amecs.logException(Level.ERROR, e);
33+
}
34+
return null;
35+
}
36+
37+
}

‎src/main/resources/amecs.mixins.json

+1-7
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,7 @@
44
"compatibilityLevel": "JAVA_8",
55
"plugin": "de.siphalor.amecs.mixin.AmecsMixinConfig",
66
"mixins": [],
7-
"client": [
8-
"ControlsListWidgetKeyBindingEntryAccessor",
9-
"MixinMinecraftClient",
10-
"MixinMouse",
11-
"MixinPlayerInventory",
12-
"MixinScreen"
13-
],
7+
"client": [],
148
"server": [],
159
"injectors": {
1610
"defaultRequire": 1

‎src/main/resources/assets/amecs/lang/de_de.json

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"key.amecs.toggle_left_sleeve": "Linker Ärmel umschalten",
1313
"key.amecs.toggle_right_pants_leg": "Rechtes Hosenbein umschalten",
1414
"key.amecs.toggle_right_sleeve": "Rechter Ärmel umschalten",
15+
"key.amecs.alternative_escape": "Escape (Alternative Taste)",
1516

1617
"amecs.toggled.on": "%s aktiviert",
1718
"amecs.toggled.off": "%s deaktiviert",

‎src/main/resources/assets/amecs/lang/en_us.json

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"key.amecs.toggle_left_sleeve": "Toggle Left Sleeve",
1313
"key.amecs.toggle_right_pants_leg": "Toggle Right Pants Leg",
1414
"key.amecs.toggle_right_sleeve": "Toggle Right Sleeve",
15+
"key.amecs.alternative_escape": "Escape (Alternative Key)",
1516

1617
"amecs.toggled.on": "Enabled %s",
1718
"amecs.toggled.off": "Disabled %s",

‎src/main/resources/fabric.mod.json

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"modmenu:clientsideOnly": true
3232
},
3333
"depends": {
34+
"minecraft": ">=1.14",
3435
"fabric": "*",
3536
"fabricloader": ">=0.4.0",
3637
"fabric-api-base": ">=0.1.2+de26dc4942",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
{
2+
"defaultConditions": {
3+
"ENVIRONMENT": [
4+
"CLIENT"
5+
]
6+
},
7+
"mixinConditions": {
8+
"ControlsListWidgetKeyBindingEntryAccessor": [
9+
{}
10+
],
11+
"MixinMinecraftClient": [
12+
{}
13+
],
14+
"MixinMouse": [
15+
{}
16+
],
17+
"MixinPlayerInventory": [
18+
{}
19+
],
20+
"MixinKeyboard": [
21+
{}
22+
],
23+
"versioned.MixinKeyBindingEntry_1_16": [
24+
{
25+
"minecraft": [
26+
">=1.16"
27+
]
28+
}
29+
],
30+
"versioned.MixinKeyBindingEntry_1_14": [
31+
{
32+
"minecraft": [
33+
"<1.16"
34+
]
35+
}
36+
],
37+
"versioned.MixinScreen_1_16": [
38+
{
39+
"minecraft": [
40+
">=1.16"
41+
]
42+
}
43+
],
44+
"versioned.MixinScreen_1_14": [
45+
{
46+
"minecraft": [
47+
"<1.16"
48+
]
49+
}
50+
],
51+
"versioned.MixinKeybindsScreen": [
52+
{
53+
"minecraft": [
54+
">=1.18"
55+
]
56+
}
57+
],
58+
"versioned.MixinControlsOptionsScreen_1_16": [
59+
{
60+
"minecraft": [
61+
"<1.18 >=1.16"
62+
]
63+
}
64+
],
65+
"versioned.MixinControlsOptionsScreen_1_14": [
66+
{
67+
"minecraft": [
68+
"<1.16"
69+
]
70+
}
71+
],
72+
"versioned.MixinMouse_1_18_2": [
73+
{
74+
"minecraft": [
75+
">=1.18.2"
76+
]
77+
}
78+
],
79+
"versioned.MixinMouse_1_14": [
80+
{
81+
"minecraft": [
82+
"<1.18.2"
83+
]
84+
}
85+
]
86+
}
87+
}

‎src/testmod/java/de/siphalor/amecstestmod/AmecsTestMod.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,6 @@ public class AmecsTestMod implements ClientModInitializer {
1515
@Override
1616
public void onInitializeClient() {
1717
KeyBinding kbd = KeyBindingHelper.registerKeyBinding(new AmecsKeyBinding(new Identifier(MOD_ID, "kbd"), InputUtil.Type.KEYSYM, 86, "key.categories.movement", new KeyModifiers(false, false, false)));
18-
NMUKAlternatives.create(kbd, new AmecsKeyBinding(new Identifier(MOD_ID, ""), InputUtil.Type.KEYSYM, 87, "", new KeyModifiers(true, false, false)));
18+
NMUKAlternatives.createAndGet(kbd, new AmecsKeyBinding(new Identifier(MOD_ID, ""), InputUtil.Type.KEYSYM, 87, "", new KeyModifiers(true, false, false)));
1919
}
2020
}

0 commit comments

Comments
 (0)
Please sign in to comment.