diff --git a/src/main/java/com/daniking/backtools/BackToolFeatureRenderer.java b/src/main/java/com/daniking/backtools/BackToolFeatureRenderer.java index 0b916ac..c7d468c 100644 --- a/src/main/java/com/daniking/backtools/BackToolFeatureRenderer.java +++ b/src/main/java/com/daniking/backtools/BackToolFeatureRenderer.java @@ -18,7 +18,7 @@ import net.minecraft.util.math.RotationAxis; @Environment(EnvType.CLIENT) -public class BackToolFeatureRenderer extends PlayerHeldItemFeatureRenderer { +public class BackToolFeatureRenderer extends PlayerHeldItemFeatureRenderer { public ItemStack mainStack = ItemStack.EMPTY; public ItemStack offStack = ItemStack.EMPTY; public Arm mainArm = Arm.RIGHT; @@ -42,36 +42,38 @@ public void render(MatrixStack matrixStack, VertexConsumerProvider vertexConsume matrixStack.push(); this.getContextModel().body.rotate(matrixStack); boolean isHelicopterMode = ConfigHandler.isHelicopterModeOn() && (playerRenderState.isSwimming || playerRenderState.isGliding); - this.renderItem(!playerRenderState.equippedChestStack.isEmpty() ? 1.0F : playerRenderState.jacketVisible ? 0.5F : 0F, matrixStack, vertexConsumerProvider, light, isHelicopterMode ? playerRenderState.age : 0); + this.renderItem(!playerRenderState.equippedChestStack.isEmpty() ? 1.0F : playerRenderState.jacketVisible ? 0.5F : 0F, + matrixStack, vertexConsumerProvider, light, isHelicopterMode ? playerRenderState.age : 0); matrixStack.pop(); } } private void renderItem(float offset, MatrixStack matrices, VertexConsumerProvider provider, int light, final float age) { - matrices.translate(0F, 4F/16F, 1.91F/16F + (offset / 16F)); + matrices.translate(0F, 4F / 16F, 1.91F / 16F + (offset / 16F)); matrices.translate(0F, 0F, 0.025F); if (!this.mainStack.isEmpty()) { if (this.mainArm == Arm.RIGHT) { matrices.scale(-1F, 1F, -1F); } - boolean bl = this.mainStack.getItem() instanceof ShieldItem; - if (bl) { + if (this.mainStack.getItem() instanceof ShieldItem) { float scale = 1.5F; matrices.scale(scale, scale, scale); if (this.mainArm == Arm.LEFT) { matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(180F)); matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(-25F)); - matrices.translate(-2.5F/16F, 2F/16F, 1.25F/16F); + matrices.translate(-2.5F / 16F, 2F / 16F, 1.25F / 16F); } else { matrices.translate(-1F / 16F, 0.25F / 16F, 1.0F / 16F); matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(25F)); } + } else { + TransformationSetting config = ConfigHandler.getToolOrientation(this.mainStack); + matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(config.getX())); + matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(config.getY())); + matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(config.getZ())); } - if (!bl) { - final float i = ConfigHandler.getToolOrientation(this.mainStack.getItem()); - matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(i)); - } + if (ConfigHandler.isBeltTool(this.mainStack.getItem())) { float swordScale = 0.8F; matrices.scale(swordScale, swordScale, swordScale); @@ -83,7 +85,13 @@ private void renderItem(float offset, MatrixStack matrices, VertexConsumerProvid matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(270F)); matrices.translate(0.19F, 0.6F, 0.33F); } + TransformationSetting config = ConfigHandler.getToolOffset(this.mainStack); + matrices.translate(config.getX(), config.getY(), config.getZ()); + } else { + TransformationSetting config = ConfigHandler.getToolOffset(this.mainStack); + matrices.translate(config.getX(), config.getY(), -config.getZ()); } + if (age > 0) { matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(age * 40F)); } @@ -93,23 +101,24 @@ private void renderItem(float offset, MatrixStack matrices, VertexConsumerProvid if (this.mainArm == Arm.LEFT) { matrices.scale(-1F, 1F, -1F); } - boolean isShield = this.offStack.getItem() instanceof ShieldItem; - if (isShield) { + if (this.offStack.getItem() instanceof ShieldItem) { float scale = 1.5F; matrices.scale(scale, scale, scale); if (this.mainArm == Arm.RIGHT) { matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(180F)); - matrices.translate(-2.5F/16F, 2F/16F, 1.25F/16F); + matrices.translate(-2.5F / 16F, 2F / 16F, 1.25F / 16F); matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(-25F)); } else { matrices.translate(-1F / 16F, 0.25F / 16F, 1.0F / 16F); matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(25F)); } + } else { + TransformationSetting config = ConfigHandler.getToolOrientation(this.mainStack); + matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(config.getX())); + matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(config.getY())); + matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(config.getZ())); } - if (!isShield) { - final float i = ConfigHandler.getToolOrientation(this.mainStack.getItem()); - matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(i)); - } + if (age > 0) { matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(age * 40F)); } diff --git a/src/main/java/com/daniking/backtools/BackToolsConfig.java b/src/main/java/com/daniking/backtools/BackToolsConfig.java index 5312cc3..6cdb743 100644 --- a/src/main/java/com/daniking/backtools/BackToolsConfig.java +++ b/src/main/java/com/daniking/backtools/BackToolsConfig.java @@ -15,13 +15,13 @@ public class BackToolsConfig implements ConfigData { @Comment(value = "\nThese options affect only the client that loads the mod.\nIt is not possible to override the environment of the mod.") public final String environment = EnvType.CLIENT.name(); - @Comment(value = "What items should render on your belt.") + @Comment(value = "What items should render on your belt by their resource name. Eg: minecraft:diamond_hoe") public List beltTools = new ArrayList<>(); @Comment(value = "Enabled tools, by their resource name. Eg: minecraft:diamond_hoe. Putting any entry in here converts BackTools to a whitelist-only mod. Disabled Tools will be ignored.") public List enabledTools = new ArrayList<>(); @Comment(value = "Disabled tools, by their resource name. Eg: minecraft:diamond_hoe") public List disabledTools = new ArrayList<>(); - @Comment(value = "Tool orientation, by class file and degrees. Separate with \":\" . See defaults for examples.") + @Comment(value = "Tool orientation, by class file, component data and degrees. Separate with \":\" . See defaults for examples.") public List toolOrientation = Arrays.asList( "net.minecraft.item.MiningToolItem" + ":0", "net.minecraft.item.HoeItem" + ":0", @@ -29,6 +29,10 @@ public class BackToolsConfig implements ConfigData { "net.minecraft.item.TridentItem" + ":0", "net.minecraft.item.MaceItem" + ":-22.5", "net.minecraft.item.RangedWeaponItem" + ":90"); + @Comment(value = "Tool translation; by class file, component data and degrees. Separate with \":\" . See defaults for examples.") + public List toolOffset = Arrays.asList( + "net.minecraft.item.HoeItem" + "{\"minecraft:custom_name\":'\"translation test1\"'}}" + ":0.3:0:0.3", + "net.minecraft.item.HoeItem" + "{\"minecraft:custom_name\":'\"translation test2\"'}}" + ":0.3"); @Comment(value = "Get in swimming position and your tools go \"Weeee\"") public boolean helicopterMode = false; @Comment(value = "If true, tools render with capes") diff --git a/src/main/java/com/daniking/backtools/ConfigHandler.java b/src/main/java/com/daniking/backtools/ConfigHandler.java index 9a48ba4..b82b5f7 100644 --- a/src/main/java/com/daniking/backtools/ConfigHandler.java +++ b/src/main/java/com/daniking/backtools/ConfigHandler.java @@ -1,39 +1,86 @@ package com.daniking.backtools; +import com.mojang.brigadier.exceptions.CommandSyntaxException; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.api.MappingResolver; +import net.minecraft.component.ComponentMap; import net.minecraft.item.*; +import net.minecraft.nbt.NbtOps; +import net.minecraft.nbt.StringNbtReader; import net.minecraft.registry.Registries; import net.minecraft.util.Identifier; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Set; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; @Environment(EnvType.CLIENT) public class ConfigHandler { - private static final HashMap, Float> TOOL_ORIENTATIONS = new HashMap<>(); + // looks scary but really only matches --> class name {optional components} : optional float x : optional float y : float z + private static final Pattern TOOL_CONFIG_PATTERN = Pattern.compile("\\A(?.*?)(?\\{.*})?(?::(?[+-]?(?:\\d*[.])?\\d+):(?[+-]?(?:\\d*[.])?\\d+))?:(?[+-]?(?:\\d*[.])?\\d+)\\z"); + + private static final HashSet BELT_TOOLS = new HashSet<>(); + private static final HashMap, List> TOOL_ORIENTATIONS = new HashMap<>(); + private static final HashMap, List> TOOL_OFFSETS = new HashMap<>(); private static final HashSet ENABLED_TOOLS = new HashSet<>(); private static final Set DISABLED_TOOLS = new HashSet<>(); private static boolean HELICOPTER_MODE = false; private static boolean RENDER_WITH_CAPES = true; - public static final HashSet BELT_TOOLS = new HashSet<>(); - public static float getToolOrientation(@NotNull Item item) { - return getToolOrientation(item.getClass()); + public static TransformationSetting getToolOrientation(@NotNull ItemStack itemStack) { + return getToolOrientation(itemStack.getComponents(), itemStack.getItem().getClass()); } - public static float getToolOrientation(@NotNull Class object) { + public static TransformationSetting getToolOrientation(@Nullable ComponentMap components, @NotNull Class object) { if (object.equals(Item.class)) { - return 0; + return TransformationSetting.empty(); } + + // add all super classes until we match or hit Item.class if (!TOOL_ORIENTATIONS.containsKey(object)) { - TOOL_ORIENTATIONS.put(object, getToolOrientation(object.getSuperclass())); + List list = new ArrayList<>(); + list.add(getToolOrientation(components, object.getSuperclass())); + TOOL_ORIENTATIONS.put(object, list); + } + + for (TransformationSetting toolConfig : TOOL_ORIENTATIONS.get(object)) { + if (toolConfig.doComponentsMatch(components)) { + return toolConfig; + } + } + + // no match + return TransformationSetting.empty(); + } + + public static TransformationSetting getToolOffset(@NotNull ItemStack itemStack) { + return getToolOffset(itemStack.getComponents(), itemStack.getItem().getClass()); + } + + public static TransformationSetting getToolOffset(@Nullable ComponentMap components, @NotNull Class object) { + if (object.equals(Item.class)) { + return TransformationSetting.empty(); + } + + // add all super class until we match or hit Item.class + if (!TOOL_OFFSETS.containsKey(object)) { + List list = new ArrayList<>(); + list.add(getToolOffset(components, object.getSuperclass())); + TOOL_OFFSETS.put(object, list); } - return TOOL_ORIENTATIONS.get(object); + + for (TransformationSetting toolConfig : TOOL_OFFSETS.get(object)) { + if (toolConfig.doComponentsMatch(components)) { + return toolConfig; + } + } + + // no match + return TransformationSetting.empty(); } public static boolean isItemEnabled(final Item item) { @@ -56,13 +103,10 @@ public static boolean isItemEnabled(final Item item) { item instanceof MiningToolItem || item instanceof ShearsItem || item instanceof FishingRodItem; - } - public static boolean isBeltTool(final Item item) { - var itemId = Registries.ITEM.getId(item); - ClientSetup.config.beltTools.forEach(beltTool -> BELT_TOOLS.add(Identifier.of(beltTool))); - return BELT_TOOLS.contains(itemId); + public static boolean isBeltTool(final Item item) { + return BELT_TOOLS.contains(Registries.ITEM.getId(item)); } public static void init() { @@ -74,7 +118,12 @@ public static void init() { DISABLED_TOOLS.clear(); ClientSetup.config.disabledTools.forEach(disabledTool -> DISABLED_TOOLS.add(Identifier.of(disabledTool))); } + ConfigHandler.parseOrientation(); + ConfigHandler.parseOffset(); + + BELT_TOOLS.clear(); + ClientSetup.config.beltTools.forEach(beltTool -> BELT_TOOLS.add(Identifier.of(beltTool))); // load easter egg setting HELICOPTER_MODE = ClientSetup.config.helicopterMode; @@ -82,43 +131,121 @@ public static void init() { RENDER_WITH_CAPES = ClientSetup.config.renderWithCapes; } + /** + * tries to resolve a class by its mapped name + */ + private static @Nullable Class getClass(@NotNull String className) { + MappingResolver resolver = FabricLoader.getInstance().getMappingResolver(); + Class result = null; + for (String namespace : resolver.getNamespaces()) { + try { + result = Class.forName(resolver.unmapClassName(namespace, className)); + + // if no error was thrown, we were successful! + break; + } catch (ClassNotFoundException ignored) { + } + } + + if (result != null) { + if (Item.class.isAssignableFrom(result)) { + return result; + } else { + BackTools.LOGGER.error("[CONFIG_FILE]: Invalid Tool class file: {}", className); + } + } else { + BackTools.LOGGER.error("[CONFIG_FILE]: Could not find class: {}", className); + } + + return null; + } + + /** + * Tries to parse component data, may return null if invalid + */ + private static @Nullable ComponentMap getComponents(@NotNull String text) { + try { + return ComponentMap.CODEC.decode(NbtOps.INSTANCE, StringNbtReader.parse(text)).result().get().getFirst(); + } catch (CommandSyntaxException | NoSuchElementException e) { + BackTools.LOGGER.error("[CONFIG_FILE]: Could not read component data for {}. Ignoring it for now!", text, e); + return null; + } + } + private static void parseOrientation() { TOOL_ORIENTATIONS.clear(); - MappingResolver resolver = FabricLoader.getInstance().getMappingResolver(); for (String configText : ClientSetup.config.toolOrientation) { - final String[] split = new String[2]; - final int i = configText.indexOf(':'); - if (i == -1) { - BackTools.LOGGER.error("[CONFIG_FILE]: Tool orientation class file and degrees must be separated with \":\"!"); + Matcher matcher = TOOL_CONFIG_PATTERN.matcher(configText); + + Class matchedClass; + ComponentMap components = null; + float xOrientation = 0.0f, yOrientation = 0.0f, zOrientation; + + if (matcher.matches()) { + //required for match + matchedClass = getClass(matcher.group("class")); + zOrientation = Float.parseFloat(matcher.group("z")); + + // optional + String nullcheck = matcher.group("components"); + if (nullcheck != null) { + components = getComponents(nullcheck); + } + nullcheck = matcher.group("x"); + if (nullcheck != null) { + xOrientation = Float.parseFloat(nullcheck); + } + nullcheck = matcher.group("y"); + if (nullcheck != null) { + yOrientation = Float.parseFloat(nullcheck); + } + + if (matchedClass != null) { + TOOL_ORIENTATIONS.computeIfAbsent(matchedClass, k -> new ArrayList<>()); + TOOL_ORIENTATIONS.get(matchedClass).add(new TransformationSetting(components, xOrientation, yOrientation, zOrientation)); + } } else { - split[0] = configText.substring(0, i);//chunk of the text, contains the file class. - split[1] = configText.substring(i + 1);//orientation + BackTools.LOGGER.error("[CONFIG_FILE]: Invalid Tool orientation setting: {}. Ignoring.", configText); } + } + } + + private static void parseOffset() { + TOOL_OFFSETS.clear(); - Class path = null; - for (String namespace : resolver.getNamespaces()) { - try { - path = Class.forName(resolver.unmapClassName(namespace, split[0])); + for (String configText : ClientSetup.config.toolOffset) { + Matcher matcher = TOOL_CONFIG_PATTERN.matcher(configText); - // if no error was thrown, we were successful! - break; - } catch (ClassNotFoundException ignored) { + Class matchedClass; + ComponentMap components = null; + float xOffset = 0.0f, yOffset = 0.0f, zOffset; + + if (matcher.matches()) { + //required for match + matchedClass = getClass(matcher.group("class")); + zOffset = Float.parseFloat(matcher.group("z")); + + // optional + String nullcheck = matcher.group("components"); + if (nullcheck != null) { + components = getComponents(nullcheck); + } + nullcheck = matcher.group("x"); + if (nullcheck != null) { + xOffset = Float.parseFloat(nullcheck); + } + nullcheck = matcher.group("y"); + if (nullcheck != null) { + yOffset = Float.parseFloat(nullcheck); } - } - if (path != null) { - try { - if (Item.class.isAssignableFrom(path)) { - TOOL_ORIENTATIONS.put(path, Float.parseFloat(split[1])); - } else { - BackTools.LOGGER.error("[CONFIG_FILE]: Invalid Tool class file: {}", split[0]); - } - } catch (NumberFormatException e) { - BackTools.LOGGER.error("[CONFIG_FILE]: Could not parse text: {}", configText); + if (matchedClass != null) { + TOOL_OFFSETS.computeIfAbsent(matchedClass, k -> new ArrayList<>()); + TOOL_OFFSETS.get(matchedClass).add(new TransformationSetting(components, xOffset, yOffset, zOffset)); } } else { - BackTools.LOGGER.error("[CONFIG_FILE]: Could not find class to add orientation: {}", split[0]); + BackTools.LOGGER.error("[CONFIG_FILE]: Invalid Tool offset setting: {}. Ignoring.", configText); } } } @@ -126,5 +253,6 @@ private static void parseOrientation() { public static boolean isHelicopterModeOn() { return HELICOPTER_MODE; } + public static boolean isRenderWithCapesTrue() { return RENDER_WITH_CAPES; } } diff --git a/src/main/java/com/daniking/backtools/HeldItemContext.java b/src/main/java/com/daniking/backtools/HeldItemContext.java index 9183f4f..618d848 100644 --- a/src/main/java/com/daniking/backtools/HeldItemContext.java +++ b/src/main/java/com/daniking/backtools/HeldItemContext.java @@ -11,9 +11,7 @@ public class HeldItemContext { public ItemStack activeMain = ItemStack.EMPTY; public ItemStack activeOff = ItemStack.EMPTY; - public void tick(ItemStack main, ItemStack off) { - if (droppedEntity != null && !droppedEntity.getStack().isEmpty()) { this.reset(droppedEntity.getStack()); droppedEntity = null; @@ -21,15 +19,15 @@ public void tick(ItemStack main, ItemStack off) { } //check to see if we should remove the main hand back tool - if(areStacksEqual(main, previousMain) || areStacksEqual(off, previousMain)) { + if (areStacksEqual(main, previousMain) || areStacksEqual(off, previousMain)) { previousMain = ItemStack.EMPTY; } - if(areStacksEqual(main, previousOff) || areStacksEqual(off, previousOff)) { + if (areStacksEqual(main, previousOff) || areStacksEqual(off, previousOff)) { previousOff = ItemStack.EMPTY; } //set back tool if main tool was an item, and we don't see that item anymore. - if(!activeMain.isEmpty() && !areStacksEqual(main, activeMain) && !areStacksEqual(off, activeMain)) { + if (!activeMain.isEmpty() && !areStacksEqual(main, activeMain) && !areStacksEqual(off, activeMain)) { previousMain = activeMain; activeMain = ItemStack.EMPTY; } @@ -37,20 +35,20 @@ public void tick(ItemStack main, ItemStack off) { // //set back tool if offhand tool was an item, and we don't see that item anymore. // this.updatePreviousStacks(main, off); - if(!activeOff.isEmpty() && !areStacksEqual(main, activeOff) && !areStacksEqual(off, activeOff)) { + if (!activeOff.isEmpty() && !areStacksEqual(main, activeOff) && !areStacksEqual(off, activeOff)) { previousOff = activeOff; activeOff = ItemStack.EMPTY; } - if(ConfigHandler.isItemEnabled(main.getItem())) { + if (ConfigHandler.isItemEnabled(main.getItem())) { activeMain = main; - if(areStacksEqual(activeMain, activeOff)) { + if (areStacksEqual(activeMain, activeOff)) { activeOff = ItemStack.EMPTY; } } - if(ConfigHandler.isItemEnabled(off.getItem())) { + if (ConfigHandler.isItemEnabled(off.getItem())) { activeOff = off; - if(areStacksEqual(activeOff, activeMain)) { + if (areStacksEqual(activeOff, activeMain)) { activeMain = ItemStack.EMPTY; } } diff --git a/src/main/java/com/daniking/backtools/TransformationSetting.java b/src/main/java/com/daniking/backtools/TransformationSetting.java new file mode 100644 index 0000000..5df36e8 --- /dev/null +++ b/src/main/java/com/daniking/backtools/TransformationSetting.java @@ -0,0 +1,41 @@ +package com.daniking.backtools; + +import net.minecraft.component.ComponentMap; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; + +public class TransformationSetting { + private final static TransformationSetting EMPTY = new TransformationSetting(null, 0, 0, 0); + + private final float x, y, z; + private final @Nullable ComponentMap components; + + public TransformationSetting(@Nullable ComponentMap componentsToMatch, float x, float y, float z) { + this.components = componentsToMatch; + + this.x = x; + this.y = y; + this.z = z; + } + + public static TransformationSetting empty() { + return EMPTY; + } + + public boolean doComponentsMatch(@Nullable ComponentMap toCheck) { + return Objects.equals(components, toCheck); + } + + public float getX() { + return x; + } + + public float getY() { + return y; + } + + public float getZ() { + return z; + } +}