commit 0b30af9b60588267e2a43904a594c15ddc9d0448 Author: candle Date: Sun Sep 28 00:54:47 2025 -0400 initial commit, everything should be working diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0666267 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +build/ +run/ +.idea/ +.c* +.gradle/ +gradle/ +.architectury-transformer/ \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..5bbee78 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2025 candle + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..3a9c124 --- /dev/null +++ b/build.gradle @@ -0,0 +1,79 @@ +plugins { + id 'dev.architectury.loom' version '1.11-SNAPSHOT' apply false + id 'architectury-plugin' version '3.4-SNAPSHOT' + id 'com.github.johnrengelman.shadow' version '8.1.1' apply false +} + +architectury { + minecraft = project.minecraft_version +} + +allprojects { + group = rootProject.maven_group + version = rootProject.mod_version +} + +subprojects { + apply plugin: 'dev.architectury.loom' + apply plugin: 'architectury-plugin' + apply plugin: 'maven-publish' + + base { + // Set up a suffixed format for the mod jar names, e.g. `example-fabric`. + archivesName = "$rootProject.archives_name-$project.name" + } + + repositories { + // Add repositories to retrieve artifacts from in here. + // You should only use this when depending on other mods because + // Loom adds the essential maven repositories to download Minecraft and libraries from automatically. + // See https://docs.gradle.org/current/userguide/declaring_repositories.html + // for more information about repositories. + + maven { + name = "TerraformersMC" + url = "https://maven.terraformersmc.com/" + } + maven { + name = "Shedaniel" + url = "https://maven.shedaniel.me/" + } + } + + dependencies { + minecraft "net.minecraft:minecraft:$rootProject.minecraft_version" + mappings loom.officialMojangMappings() + } + + java { + // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task + // if it is present. + // If you remove this line, sources will not be generated. + withSourcesJar() + + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + tasks.withType(JavaCompile).configureEach { + it.options.release = 17 + } + + // Configure Maven publishing. + publishing { + publications { + mavenJava(MavenPublication) { + artifactId = base.archivesName.get() + from components.java + } + } + + // See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing. + repositories { + // Add repositories to publish to here. + // Notice: This block does NOT have the same function as the block in the top level. + // The repositories here will be used for publishing your artifact, not for + // retrieving dependencies. + } + } +} diff --git a/common/build.gradle b/common/build.gradle new file mode 100644 index 0000000..c034916 --- /dev/null +++ b/common/build.gradle @@ -0,0 +1,9 @@ +architectury { + common rootProject.enabled_platforms.split(',') +} + +dependencies { + modImplementation "net.fabricmc:fabric-loader:$rootProject.fabric_loader_version" + + modImplementation "dev.architectury:architectury:$rootProject.architectury_api_version" +} diff --git a/common/src/main/java/io/lampnet/betterlodestones/BetterLodestones.java b/common/src/main/java/io/lampnet/betterlodestones/BetterLodestones.java new file mode 100644 index 0000000..f52af64 --- /dev/null +++ b/common/src/main/java/io/lampnet/betterlodestones/BetterLodestones.java @@ -0,0 +1,26 @@ +package io.lampnet.betterlodestones; + +import io.lampnet.betterlodestones.config.ConfigManager; +import io.lampnet.betterlodestones.registry.BetterLodestonesRegistry; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class BetterLodestones { + public static final String MOD_ID = "better_lodestones"; + public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID); + + public static void init() { + BetterLodestonesRegistry.init(); + + LOGGER.info("Better Lodestones initialized."); + + if (ConfigManager.hasLimit()) { + LOGGER.info("Max Lodestones per compass: {}", ConfigManager.getMaxLodestones()); + } else { + LOGGER.info("No limit on Lodestones per compass"); + } + + LOGGER.info("Using {} lodestone recipe", + ConfigManager.useModernLodestoneRecipe() ? "modern (iron)" : "legacy (netherite)"); + } +} diff --git a/common/src/main/java/io/lampnet/betterlodestones/CompassDataHandler.java b/common/src/main/java/io/lampnet/betterlodestones/CompassDataHandler.java new file mode 100644 index 0000000..7e6a527 --- /dev/null +++ b/common/src/main/java/io/lampnet/betterlodestones/CompassDataHandler.java @@ -0,0 +1,421 @@ +package io.lampnet.betterlodestones; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.GlobalPos; +import net.minecraft.core.registries.Registries; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.Tag; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.CompassItem; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; + +public class CompassDataHandler { + private static final String LODESTONES_KEY = "BetterLodestones"; + private static final String CURRENT_INDEX_KEY = "CurrentLodestoneIndex"; + private static final String POS_X_KEY = "PosX"; + private static final String POS_Y_KEY = "PosY"; + private static final String POS_Z_KEY = "PosZ"; + private static final String DIMENSION_KEY = "Dimension"; + private static final String BROKEN_LODESTONE_FLAG = "BrokenLodestone"; + + public static List getLodestones(ItemStack compass) { + List lodestones = new ArrayList<>(); + CompoundTag tag = compass.getTag(); + if (tag == null || !tag.contains(LODESTONES_KEY)) { + return lodestones; + } + + ListTag lodestoneList = tag.getList(LODESTONES_KEY, Tag.TAG_COMPOUND); + for (int i = 0; i < lodestoneList.size(); i++) { + CompoundTag lodestoneTag = lodestoneList.getCompound(i); + + int x = lodestoneTag.getInt(POS_X_KEY); + int y = lodestoneTag.getInt(POS_Y_KEY); + int z = lodestoneTag.getInt(POS_Z_KEY); + String dimensionString = lodestoneTag.getString(DIMENSION_KEY); + + try { + ResourceKey dimension = ResourceKey.create(Registries.DIMENSION, + new ResourceLocation(dimensionString)); + BlockPos pos = new BlockPos(x, y, z); + lodestones.add(GlobalPos.of(dimension, pos)); + } catch (Exception e) { + // Skip invalid lodestone entries + } + } + + return lodestones; + } + + public static void addLodestone(ItemStack compass, GlobalPos lodestone) { + CompoundTag tag = compass.getOrCreateTag(); + ListTag lodestoneList = tag.getList(LODESTONES_KEY, Tag.TAG_COMPOUND); + + for (int i = 0; i < lodestoneList.size(); i++) { + CompoundTag existing = lodestoneList.getCompound(i); + if (isSameLodestone(existing, lodestone)) { + return; + } + } + + CompoundTag lodestoneTag = new CompoundTag(); + lodestoneTag.putInt(POS_X_KEY, lodestone.pos().getX()); + lodestoneTag.putInt(POS_Y_KEY, lodestone.pos().getY()); + lodestoneTag.putInt(POS_Z_KEY, lodestone.pos().getZ()); + lodestoneTag.putString(DIMENSION_KEY, lodestone.dimension().location().toString()); + + lodestoneList.add(lodestoneTag); + tag.put(LODESTONES_KEY, lodestoneList); + + if (lodestoneList.size() == 1) { + tag.putInt(CURRENT_INDEX_KEY, 0); + } + } + + public static void removeLodestone(ItemStack compass, GlobalPos lodestone) { + CompoundTag tag = compass.getTag(); + if (tag == null || !tag.contains(LODESTONES_KEY)) { + return; + } + + ListTag lodestoneList = tag.getList(LODESTONES_KEY, Tag.TAG_COMPOUND); + int currentIndex = getCurrentLodestoneIndex(compass); + + for (int i = lodestoneList.size() - 1; i >= 0; i--) { + CompoundTag existing = lodestoneList.getCompound(i); + if (isSameLodestone(existing, lodestone)) { + lodestoneList.remove(i); + + if (i < currentIndex) { + currentIndex--; + } else if (i == currentIndex && currentIndex >= lodestoneList.size()) { + currentIndex = lodestoneList.size() - 1; + } + break; + } + } + + if (lodestoneList.isEmpty()) { + tag.remove(CURRENT_INDEX_KEY); + tag.remove(LODESTONES_KEY); + } else { + tag.putInt(CURRENT_INDEX_KEY, Math.max(0, currentIndex)); + } + } + + public static int getCurrentLodestoneIndex(ItemStack compass) { + CompoundTag tag = compass.getTag(); + if (tag == null) { + return 0; + } + return tag.getInt(CURRENT_INDEX_KEY); + } + + public static Optional getCurrentLodestone(ItemStack compass) { + List lodestones = getLodestones(compass); + if (lodestones.isEmpty()) { + return Optional.empty(); + } + + int index = getCurrentLodestoneIndex(compass); + if (index < 0 || index >= lodestones.size()) { + return Optional.empty(); + } + + return Optional.of(lodestones.get(index)); + } + + public static void cycleToNextLodestone(ItemStack compass, Level level) { + List lodestones = getLodestones(compass); + if (lodestones.size() <= 1) { + validateCurrentLodestone(compass, level); + return; + } + + int currentIndex = getCurrentLodestoneIndex(compass); + int nextIndex = (currentIndex + 1) % lodestones.size(); + + CompoundTag tag = compass.getOrCreateTag(); + tag.putInt(CURRENT_INDEX_KEY, nextIndex); + + GlobalPos newLodestone = lodestones.get(nextIndex); + if (isLodestoneValid(level, newLodestone)) { + clearBrokenLodestoneFlag(compass); + } else if (isLodestoneDestroyed(level, newLodestone)) { + markLodestoneAsBroken(compass); + } else { + clearBrokenLodestoneFlag(compass); + } + } + + public static int getLodestoneCount(ItemStack compass) { + return getLodestones(compass).size(); + } + + public static void clearAllLodestones(ItemStack compass) { + CompoundTag tag = compass.getTag(); + if (tag != null) { + tag.remove(LODESTONES_KEY); + tag.remove(CURRENT_INDEX_KEY); + tag.remove(BROKEN_LODESTONE_FLAG); + } + } + + public static void removeCurrentLodestone(ItemStack compass) { + Optional currentLodestone = getCurrentLodestone(compass); + currentLodestone.ifPresent(globalPos -> removeLodestone(compass, globalPos)); + } + + public static boolean isLodestoneAlreadyBound(ItemStack compass, GlobalPos lodestone) { + CompoundTag tag = compass.getTag(); + if (tag == null || !tag.contains(LODESTONES_KEY)) { + return false; + } + + ListTag lodestoneList = tag.getList(LODESTONES_KEY, Tag.TAG_COMPOUND); + for (int i = 0; i < lodestoneList.size(); i++) { + CompoundTag existing = lodestoneList.getCompound(i); + if (isSameLodestone(existing, lodestone)) { + return true; + } + } + return false; + } + + public static boolean isLodestoneValid(Level level, GlobalPos lodestone) { + if (level == null || lodestone == null) { + return false; + } + + if (!level.dimension().equals(lodestone.dimension())) { + return false; + } + + return level.getBlockState(lodestone.pos()).is(Blocks.LODESTONE); + } + + public static boolean isLodestoneInDifferentDimension(Level level, GlobalPos lodestone) { + if (level == null || lodestone == null) { + return false; + } + + return !level.dimension().equals(lodestone.dimension()); + } + + public static boolean isLodestoneDestroyed(Level level, GlobalPos lodestone) { + if (level == null || lodestone == null) { + return true; + } + + if (!level.dimension().equals(lodestone.dimension())) { + return false; + } + + return !level.getBlockState(lodestone.pos()).is(Blocks.LODESTONE); + } + + public static Optional getCurrentValidLodestone(ItemStack compass, Level level) { + Optional currentLodestone = getCurrentLodestone(compass); + if (currentLodestone.isEmpty()) { + return Optional.empty(); + } + + GlobalPos lodestone = currentLodestone.get(); + if (isLodestoneValid(level, lodestone)) { + clearBrokenLodestoneFlag(compass); + return currentLodestone; + } else { + markLodestoneAsBroken(compass); + return Optional.empty(); + } + } + + public static void validateCurrentLodestone(ItemStack compass, Level level) { + Optional currentLodestone = getCurrentLodestone(compass); + if (currentLodestone.isEmpty()) { + clearBrokenLodestoneFlag(compass); + return; + } + + GlobalPos lodestone = currentLodestone.get(); + if (isLodestoneValid(level, lodestone)) { + clearBrokenLodestoneFlag(compass); + } else if (isLodestoneDestroyed(level, lodestone)) { + markLodestoneAsBroken(compass); + } else { + clearBrokenLodestoneFlag(compass); + } + } + + public static void markLodestoneAsBroken(ItemStack compass) { + CompoundTag tag = compass.getOrCreateTag(); + tag.putBoolean(BROKEN_LODESTONE_FLAG, true); + } + + public static boolean hasBrokenLodestone(ItemStack compass) { + CompoundTag tag = compass.getTag(); + return tag != null && tag.getBoolean(BROKEN_LODESTONE_FLAG); + } + + public static void clearBrokenLodestoneFlag(ItemStack compass) { + CompoundTag tag = compass.getTag(); + if (tag != null) { + tag.remove(BROKEN_LODESTONE_FLAG); + } + } + + public static void onLodestoneRemoved(Level level, BlockPos removedPos) { + if (level == null || removedPos == null) { + return; + } + + GlobalPos removedGlobalPos = GlobalPos.of(level.dimension(), removedPos); + + for (Player player : level.players()) { + iteratePlayerCompasses(player, stack -> handleCompassOnRemoval(stack, removedGlobalPos)); + } + } + + public static void onLodestoneRestored(Level level, BlockPos restoredPos) { + if (level == null || restoredPos == null) { + return; + } + + GlobalPos restoredGlobalPos = GlobalPos.of(level.dimension(), restoredPos); + + for (Player player : level.players()) { + iteratePlayerCompasses(player, stack -> handleCompassOnRestoration(stack, restoredGlobalPos)); + } + } + + private static void handleCompassOnRemoval(ItemStack stack, GlobalPos removedGlobalPos) { + Optional currentLodestone = getCurrentLodestone(stack); + if (currentLodestone.isEmpty()) { + return; + } + + GlobalPos targeted = currentLodestone.get(); + if (targeted.dimension().equals(removedGlobalPos.dimension()) && targeted.pos().equals(removedGlobalPos.pos())) { + markLodestoneAsBroken(stack); + } + } + + private static void handleCompassOnRestoration(ItemStack stack, GlobalPos restoredGlobalPos) { + if (!hasBrokenLodestone(stack)) { + return; + } + + Optional currentLodestone = getCurrentLodestone(stack); + if (currentLodestone.isEmpty()) { + return; + } + + GlobalPos targeted = currentLodestone.get(); + if (targeted.dimension().equals(restoredGlobalPos.dimension()) && targeted.pos().equals(restoredGlobalPos.pos())) { + clearBrokenLodestoneFlag(stack); + } + } + + private static boolean isSameLodestone(CompoundTag tag, GlobalPos lodestone) { + int x = tag.getInt(POS_X_KEY); + int y = tag.getInt(POS_Y_KEY); + int z = tag.getInt(POS_Z_KEY); + String dimension = tag.getString(DIMENSION_KEY); + + return x == lodestone.pos().getX() && + y == lodestone.pos().getY() && + z == lodestone.pos().getZ() && + dimension.equals(lodestone.dimension().location().toString()); + } + + public static boolean convertVanillaLodestoneCompass(ItemStack compass) { + if (compass == null || !(compass.getItem() instanceof CompassItem)) { + return false; + } + + CompoundTag tag = compass.getTag(); + if (tag == null) { + return false; + } + + boolean hasVanillaLodestone = tag.contains("LodestonePos") && tag.contains("LodestoneDimension"); + if (!hasVanillaLodestone) { + return false; + } + + try { + CompoundTag lodestonePos = tag.getCompound("LodestonePos"); + String dimensionString = tag.getString("LodestoneDimension"); + + int x = lodestonePos.getInt("X"); + int y = lodestonePos.getInt("Y"); + int z = lodestonePos.getInt("Z"); + + ResourceKey dimension = ResourceKey.create(Registries.DIMENSION, + new ResourceLocation(dimensionString)); + BlockPos pos = new BlockPos(x, y, z); + GlobalPos vanillaLodestone = GlobalPos.of(dimension, pos); + + addLodestone(compass, vanillaLodestone); + + tag.remove("LodestonePos"); + tag.remove("LodestoneDimension"); + tag.remove("LodestoneTracked"); + + return true; + } catch (Exception e) { + // If conversion fails, skip this compass + return false; + } + } + + public static int convertPlayerLodestoneCompasses(Player player) { + if (player == null) { + return 0; + } + + AtomicInteger convertedCount = new AtomicInteger(0); + iteratePlayerCompasses(player, stack -> { + if (convertVanillaLodestoneCompass(stack)) { + convertedCount.incrementAndGet(); + } + }); + + return convertedCount.get(); + } + + private static void iteratePlayerCompasses(Player player, Consumer compassConsumer) { + if (player == null) { + return; + } + + for (ItemStack stack : player.getInventory().items) { + if (!stack.isEmpty() && stack.getItem() instanceof CompassItem) { + compassConsumer.accept(stack); + } + } + + for (ItemStack stack : player.getInventory().armor) { + if (!stack.isEmpty() && stack.getItem() instanceof CompassItem) { + compassConsumer.accept(stack); + } + } + + for (ItemStack stack : player.getInventory().offhand) { + if (!stack.isEmpty() && stack.getItem() instanceof CompassItem) { + compassConsumer.accept(stack); + } + } + } +} diff --git a/common/src/main/java/io/lampnet/betterlodestones/advancement/DimensionalCompassBindingTrigger.java b/common/src/main/java/io/lampnet/betterlodestones/advancement/DimensionalCompassBindingTrigger.java new file mode 100644 index 0000000..152a579 --- /dev/null +++ b/common/src/main/java/io/lampnet/betterlodestones/advancement/DimensionalCompassBindingTrigger.java @@ -0,0 +1,66 @@ +package io.lampnet.betterlodestones.advancement; + +import com.google.gson.JsonObject; +import io.lampnet.betterlodestones.BetterLodestones; +import io.lampnet.betterlodestones.CompassDataHandler; +import net.minecraft.advancements.critereon.*; +import net.minecraft.core.GlobalPos; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.util.GsonHelper; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +public final class DimensionalCompassBindingTrigger extends SimpleCriterionTrigger { + static final ResourceLocation ID = new ResourceLocation(BetterLodestones.MOD_ID, "dimensional_compass_binding"); + + @Override + public @NotNull ResourceLocation getId() { + return ID; + } + + @Override + public @NotNull TriggerInstance createInstance(JsonObject json, ContextAwarePredicate player, DeserializationContext context) { + int minDimensions = GsonHelper.getAsInt(json, "min_dimensions", 1); + return new TriggerInstance(player, minDimensions); + } + + public void trigger(ServerPlayer player, ItemStack compass) { + this.trigger(player, (triggerInstance) -> triggerInstance.matches(compass)); + } + + public static class TriggerInstance extends AbstractCriterionTriggerInstance { + private final int minDimensions; + + public TriggerInstance(ContextAwarePredicate player, int minDimensions) { + super(ID, player); + this.minDimensions = minDimensions; + } + + public boolean matches(ItemStack compass) { + List lodestones = CompassDataHandler.getLodestones(compass); + if (lodestones.isEmpty()) { + return false; + } + + Set> uniqueDimensions = lodestones.stream() + .map(GlobalPos::dimension) + .collect(Collectors.toSet()); + + return uniqueDimensions.size() >= this.minDimensions; + } + + @Override + public @NotNull JsonObject serializeToJson(SerializationContext context) { + JsonObject json = super.serializeToJson(context); + json.addProperty("min_dimensions", this.minDimensions); + return json; + } + } +} diff --git a/common/src/main/java/io/lampnet/betterlodestones/config/ConfigManager.java b/common/src/main/java/io/lampnet/betterlodestones/config/ConfigManager.java new file mode 100644 index 0000000..24dcfba --- /dev/null +++ b/common/src/main/java/io/lampnet/betterlodestones/config/ConfigManager.java @@ -0,0 +1,30 @@ +package io.lampnet.betterlodestones.config; + +public class ConfigManager { + private static int maxLodestones = 0; + private static boolean useModernLodestoneRecipe = true; + + public static int getMaxLodestones() { + return maxLodestones; + } + + public static void setMaxLodestones(int maxLodestones) { + ConfigManager.maxLodestones = Math.max(0, maxLodestones); + } + + public static boolean useModernLodestoneRecipe() { + return useModernLodestoneRecipe; + } + + public static void setUseModernLodestoneRecipe(boolean useModernLodestoneRecipe) { + ConfigManager.useModernLodestoneRecipe = useModernLodestoneRecipe; + } + + public static boolean hasLimit() { + return getMaxLodestones() > 0; + } + + public static boolean isAtLimit(int currentCount) { + return hasLimit() && currentCount >= getMaxLodestones(); + } +} diff --git a/common/src/main/java/io/lampnet/betterlodestones/mixin/CompassItemMixin.java b/common/src/main/java/io/lampnet/betterlodestones/mixin/CompassItemMixin.java new file mode 100644 index 0000000..56a0004 --- /dev/null +++ b/common/src/main/java/io/lampnet/betterlodestones/mixin/CompassItemMixin.java @@ -0,0 +1,95 @@ +package io.lampnet.betterlodestones.mixin; + +import io.lampnet.betterlodestones.CompassDataHandler; +import io.lampnet.betterlodestones.config.ConfigManager; +import io.lampnet.betterlodestones.registry.BetterLodestonesRegistry; +import net.minecraft.core.BlockPos; +import net.minecraft.core.GlobalPos; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.sounds.SoundSource; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.CompassItem; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.context.UseOnContext; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Blocks; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(CompassItem.class) +public class CompassItemMixin { + + @Inject(method = "useOn", at = @At("HEAD"), cancellable = true) + private void onUseOnBlock(UseOnContext context, CallbackInfoReturnable cir) { + Level level = context.getLevel(); + BlockPos pos = context.getClickedPos(); + ItemStack compass = context.getItemInHand(); + Player player = context.getPlayer(); + + if (!level.isClientSide()) { + CompassDataHandler.convertVanillaLodestoneCompass(compass); + } + + if (level.getBlockState(pos).is(Blocks.LODESTONE)) { + if (!level.isClientSide()) { + CompassDataHandler.validateCurrentLodestone(compass, level); + GlobalPos lodestonePos = GlobalPos.of(level.dimension(), pos); + + if (CompassDataHandler.isLodestoneAlreadyBound(compass, lodestonePos)) { + CompassDataHandler.removeLodestone(compass, lodestonePos); + int remainingCount = CompassDataHandler.getLodestoneCount(compass); + + level.playSound(null, pos, SoundEvents.LODESTONE_COMPASS_LOCK, SoundSource.PLAYERS, 1.0F, 1.0F); + + if (player != null) { + player.displayClientMessage(Component.literal( + "§cLodestone unbound: §f" + pos.getX() + ", " + pos.getY() + ", " + pos.getZ() + + (remainingCount > 0 ? " §7(" + remainingCount + " remaining)" : "")), true); + } + } else { + int currentCount = CompassDataHandler.getLodestoneCount(compass); + + if (ConfigManager.isAtLimit(currentCount)) { + if (player != null) { + int maxLodestones = ConfigManager.getMaxLodestones(); + if (maxLodestones == 1) { + player.displayClientMessage(Component.literal( + "§cCompass already bound to a lodestone §7(vanilla mode)"), true); + } else { + player.displayClientMessage(Component.literal( + "§cCompass limit reached: §f" + maxLodestones + " lodestones"), true); + } + } + } else { + CompassDataHandler.addLodestone(compass, lodestonePos); + int lodestoneCount = CompassDataHandler.getLodestoneCount(compass); + + level.playSound(null, pos, SoundEvents.LODESTONE_COMPASS_LOCK, SoundSource.PLAYERS, 1.0F, 1.0F); + + if (player instanceof ServerPlayer serverPlayer) { + BetterLodestonesRegistry.DIMENSIONAL_COMPASS_BINDING.trigger(serverPlayer, compass); + } + + if (player != null) { + String limitInfo = ""; + if (ConfigManager.hasLimit()) { + limitInfo = " §7(" + lodestoneCount + "/" + ConfigManager.getMaxLodestones() + ")"; + } + + player.displayClientMessage(Component.literal( + "§6Lodestone " + lodestoneCount + " bound: §f" + + pos.getX() + ", " + pos.getY() + ", " + pos.getZ() + limitInfo), true); + } + } + } + } + cir.setReturnValue(InteractionResult.sidedSuccess(level.isClientSide())); + } + } + +} diff --git a/common/src/main/java/io/lampnet/betterlodestones/mixin/CompassItemMixinPointing.java b/common/src/main/java/io/lampnet/betterlodestones/mixin/CompassItemMixinPointing.java new file mode 100644 index 0000000..6a79564 --- /dev/null +++ b/common/src/main/java/io/lampnet/betterlodestones/mixin/CompassItemMixinPointing.java @@ -0,0 +1,41 @@ +package io.lampnet.betterlodestones.mixin; + +import io.lampnet.betterlodestones.CompassDataHandler; +import net.minecraft.core.GlobalPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.item.CompassItem; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.Optional; + +@Mixin(CompassItem.class) +public class CompassItemMixinPointing { + + @Inject(method = "getLodestonePosition", at = @At("HEAD"), cancellable = true) + private static void getCustomLodestonePosition(CompoundTag tag, + CallbackInfoReturnable cir) { + ItemStack tempStack = new ItemStack(Items.COMPASS); + tempStack.setTag(tag); + + if (CompassDataHandler.hasBrokenLodestone(tempStack)) { + cir.setReturnValue(null); + return; + } + + Optional currentLodestone = CompassDataHandler.getCurrentLodestone(tempStack); + currentLodestone.ifPresent(cir::setReturnValue); + } + + @Inject(method = "isLodestoneCompass", at = @At("HEAD"), cancellable = true) + private static void isCustomLodestoneCompass(ItemStack stack, + CallbackInfoReturnable cir) { + if (CompassDataHandler.getLodestoneCount(stack) > 0) { + cir.setReturnValue(true); + } + } +} diff --git a/common/src/main/java/io/lampnet/betterlodestones/mixin/CompassTooltipMixin.java b/common/src/main/java/io/lampnet/betterlodestones/mixin/CompassTooltipMixin.java new file mode 100644 index 0000000..4d3cbb4 --- /dev/null +++ b/common/src/main/java/io/lampnet/betterlodestones/mixin/CompassTooltipMixin.java @@ -0,0 +1,117 @@ +package io.lampnet.betterlodestones.mixin; + +import io.lampnet.betterlodestones.CompassDataHandler; +import io.lampnet.betterlodestones.config.ConfigManager; +import net.minecraft.core.BlockPos; +import net.minecraft.core.GlobalPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.item.CompassItem; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.TooltipFlag; +import net.minecraft.world.level.Level; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.List; +import java.util.Optional; + +@Mixin(Item.class) +public class CompassTooltipMixin { + + @Unique + private static final int SELECTED_TICK_INTERVAL = 5; + @Unique + private static final int UNSELECTED_TICK_INTERVAL = 40; + + @Inject(method = "appendHoverText", at = @At("TAIL")) + private void addCustomTooltip(ItemStack stack, Level level, List tooltip, TooltipFlag flag, CallbackInfo ci) { + if (!(stack.getItem() instanceof CompassItem)) { + return; + } + + if (level != null && !level.isClientSide()) { + CompassDataHandler.convertVanillaLodestoneCompass(stack); + } + + if (level != null) { + CompassDataHandler.validateCurrentLodestone(stack, level); + } + + int lodestoneCount = CompassDataHandler.getLodestoneCount(stack); + + if (lodestoneCount > 0) { + String countText = "§7Bound Lodestones: §f" + lodestoneCount; + if (ConfigManager.hasLimit()) { + countText += "/" + ConfigManager.getMaxLodestones(); + } + tooltip.add(Component.literal(countText)); + + Optional currentLodestone = CompassDataHandler.getCurrentLodestone(stack); + if (currentLodestone.isPresent()) { + GlobalPos lodestone = currentLodestone.get(); + BlockPos pos = lodestone.pos(); + String dimensionName = lodestone.dimension().location().getPath(); + int currentIndex = CompassDataHandler.getCurrentLodestoneIndex(stack) + 1; // 1-based for display + + tooltip.add(Component.literal("§7Current: §f" + currentIndex + "/" + lodestoneCount + + " §7at §f" + pos.getX() + ", " + pos.getY() + ", " + pos.getZ())); + + if (CompassDataHandler.isLodestoneInDifferentDimension(level, lodestone)) { + tooltip.add(Component.literal("§7Dimension: §c" + better_lodestones$formatDimensionName(dimensionName))); + } else if (CompassDataHandler.hasBrokenLodestone(stack)) { + tooltip.add(Component.literal("§cLodestone destroyed!")); + } else { + tooltip.add(Component.literal("§7Dimension: §f" + better_lodestones$formatDimensionName(dimensionName))); + } + } else if (CompassDataHandler.hasBrokenLodestone(stack)) { + tooltip.add(Component.literal("§cLodestone destroyed!")); + } + } + } + + @Inject(method = "inventoryTick", at = @At("HEAD")) + private void onInventoryTick(ItemStack stack, Level level, Entity entity, int slotId, boolean selected, CallbackInfo ci) { + if (!(stack.getItem() instanceof CompassItem)) { + return; + } + + if (level != null && !level.isClientSide()) { + int checkInterval = selected ? SELECTED_TICK_INTERVAL : UNSELECTED_TICK_INTERVAL; + if (entity.tickCount % checkInterval == 0) { + CompassDataHandler.convertVanillaLodestoneCompass(stack); + CompassDataHandler.validateCurrentLodestone(stack, level); + } + } + } + + @Unique + private String better_lodestones$formatDimensionName(String dimensionName) { + if (dimensionName == null || dimensionName.isEmpty()) { + return "Unknown"; + } + + String[] words = dimensionName.replace("_", " ").split(" "); + StringBuilder formatted = new StringBuilder(); + + for (int i = 0; i < words.length; i++) { + if (i > 0) { + formatted.append(" "); + } + + String word = words[i]; + if (!word.isEmpty()) { + formatted.append(Character.toUpperCase(word.charAt(0))); + if (word.length() > 1) { + formatted.append(word.substring(1).toLowerCase()); + } + } + } + + return formatted.toString(); + } +} diff --git a/common/src/main/java/io/lampnet/betterlodestones/mixin/InventoryInteractionMixin.java b/common/src/main/java/io/lampnet/betterlodestones/mixin/InventoryInteractionMixin.java new file mode 100644 index 0000000..3727b37 --- /dev/null +++ b/common/src/main/java/io/lampnet/betterlodestones/mixin/InventoryInteractionMixin.java @@ -0,0 +1,41 @@ +package io.lampnet.betterlodestones.mixin; + +import io.lampnet.betterlodestones.CompassDataHandler; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ClickType; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.CompassItem; +import net.minecraft.world.item.ItemStack; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(AbstractContainerMenu.class) +public class InventoryInteractionMixin { + + @Inject(method = "clicked", at = @At("TAIL")) + private void onInventoryClick(int slotId, int button, ClickType clickType, Player player, CallbackInfo ci) { + if (player == null || player.level().isClientSide()) { + return; + } + + AbstractContainerMenu menu = (AbstractContainerMenu) (Object) this; + + ItemStack carriedItem = menu.getCarried(); + if (carriedItem != null && carriedItem.getItem() instanceof CompassItem) { + CompassDataHandler.convertVanillaLodestoneCompass(carriedItem); + } + + if (slotId >= 0 && slotId < menu.slots.size()) { + Slot slot = menu.slots.get(slotId); + if (slot != null && slot.hasItem()) { + ItemStack slotItem = slot.getItem(); + if (slotItem.getItem() instanceof CompassItem) { + CompassDataHandler.convertVanillaLodestoneCompass(slotItem); + } + } + } + } +} diff --git a/common/src/main/java/io/lampnet/betterlodestones/mixin/ItemPickupMixin.java b/common/src/main/java/io/lampnet/betterlodestones/mixin/ItemPickupMixin.java new file mode 100644 index 0000000..3754f47 --- /dev/null +++ b/common/src/main/java/io/lampnet/betterlodestones/mixin/ItemPickupMixin.java @@ -0,0 +1,32 @@ +package io.lampnet.betterlodestones.mixin; + +import io.lampnet.betterlodestones.CompassDataHandler; +import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.CompassItem; +import net.minecraft.world.item.ItemStack; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(ItemEntity.class) +public class ItemPickupMixin { + + @Inject(method = "playerTouch", at = @At("HEAD")) + private void onPlayerTouchItem(Player player, CallbackInfo ci) { + if (player == null || player.level().isClientSide()) { + return; + } + + ItemEntity itemEntity = (ItemEntity) (Object) this; + if (itemEntity.isRemoved()) { + return; + } + + ItemStack item = itemEntity.getItem(); + if (item != null && item.getItem() instanceof CompassItem) { + CompassDataHandler.convertVanillaLodestoneCompass(item); + } + } +} diff --git a/common/src/main/java/io/lampnet/betterlodestones/mixin/ItemUseMixin.java b/common/src/main/java/io/lampnet/betterlodestones/mixin/ItemUseMixin.java new file mode 100644 index 0000000..3c8a66d --- /dev/null +++ b/common/src/main/java/io/lampnet/betterlodestones/mixin/ItemUseMixin.java @@ -0,0 +1,75 @@ +package io.lampnet.betterlodestones.mixin; + +import io.lampnet.betterlodestones.CompassDataHandler; +import net.minecraft.core.BlockPos; +import net.minecraft.core.GlobalPos; +import net.minecraft.network.chat.Component; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.sounds.SoundSource; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResultHolder; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.CompassItem; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.Optional; + +@Mixin(Item.class) +public class ItemUseMixin { + + @Inject(method = "use", at = @At("HEAD"), cancellable = true) + private void onUseInAir(Level level, Player player, InteractionHand hand, + CallbackInfoReturnable> cir) { + ItemStack itemStack = player.getItemInHand(hand); + + if (!(itemStack.getItem() instanceof CompassItem)) { + return; + } + + if (!level.isClientSide()) { + CompassDataHandler.convertVanillaLodestoneCompass(itemStack); + } + + if (!level.isClientSide() && player.isShiftKeyDown()) { + CompassDataHandler.validateCurrentLodestone(itemStack, level); + int lodestoneCount = CompassDataHandler.getLodestoneCount(itemStack); + + if (lodestoneCount > 1) { + CompassDataHandler.cycleToNextLodestone(itemStack, level); + + level.playSound(null, player.blockPosition(), SoundEvents.LODESTONE_COMPASS_LOCK, SoundSource.PLAYERS, 1.0F, 1.2F); + + Optional currentLodestone = CompassDataHandler.getCurrentLodestone(itemStack); + int currentIndex = CompassDataHandler.getCurrentLodestoneIndex(itemStack) + 1; // 1-based for display + + if (currentLodestone.isPresent()) { + BlockPos pos = currentLodestone.get().pos(); + + player.displayClientMessage(Component.literal( + "§6Lodestone " + currentIndex + "/" + lodestoneCount + ": §f" + + pos.getX() + ", " + pos.getY() + ", " + pos.getZ()), true); + } else if (CompassDataHandler.hasBrokenLodestone(itemStack)) { + player.displayClientMessage(Component.literal("§cAll lodestones destroyed!"), true); + } + + cir.setReturnValue(InteractionResultHolder.sidedSuccess(itemStack, level.isClientSide())); + } else if (lodestoneCount == 1) { + if (CompassDataHandler.hasBrokenLodestone(itemStack)) { + player.displayClientMessage(Component.literal("§cLodestone destroyed!"), true); + } else { + player.displayClientMessage(Component.literal("§eOnly one lodestone bound"), true); + } + cir.setReturnValue(InteractionResultHolder.sidedSuccess(itemStack, level.isClientSide())); + } else { + player.displayClientMessage(Component.literal("§cNo lodestones bound"), true); + cir.setReturnValue(InteractionResultHolder.sidedSuccess(itemStack, level.isClientSide())); + } + } + } +} diff --git a/common/src/main/java/io/lampnet/betterlodestones/mixin/LevelBlockChangeMixin.java b/common/src/main/java/io/lampnet/betterlodestones/mixin/LevelBlockChangeMixin.java new file mode 100644 index 0000000..aa83be8 --- /dev/null +++ b/common/src/main/java/io/lampnet/betterlodestones/mixin/LevelBlockChangeMixin.java @@ -0,0 +1,35 @@ +package io.lampnet.betterlodestones.mixin; + +import io.lampnet.betterlodestones.CompassDataHandler; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(Level.class) +public class LevelBlockChangeMixin { + + @Inject(method = "setBlock(Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;I)Z", at = @At("HEAD")) + private void onBlockChanged(BlockPos pos, BlockState newState, int flags, CallbackInfoReturnable cir) { + Level level = (Level) (Object) this; + if (level == null || level.isClientSide()) { + return; + } + + BlockState oldState = level.getBlockState(pos); + + if (oldState.is(Blocks.LODESTONE) && !newState.is(Blocks.LODESTONE)) { + CompassDataHandler.onLodestoneRemoved(level, pos); + } + + if (!oldState.is(Blocks.LODESTONE) && newState.is(Blocks.LODESTONE)) { + CompassDataHandler.onLodestoneRestored(level, pos); + } + } +} + + diff --git a/common/src/main/java/io/lampnet/betterlodestones/mixin/PlayerJoinMixin.java b/common/src/main/java/io/lampnet/betterlodestones/mixin/PlayerJoinMixin.java new file mode 100644 index 0000000..ba05ff7 --- /dev/null +++ b/common/src/main/java/io/lampnet/betterlodestones/mixin/PlayerJoinMixin.java @@ -0,0 +1,25 @@ +package io.lampnet.betterlodestones.mixin; + +import io.lampnet.betterlodestones.CompassDataHandler; +import io.lampnet.betterlodestones.BetterLodestones; +import net.minecraft.network.Connection; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.players.PlayerList; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(PlayerList.class) +public class PlayerJoinMixin { + + @Inject(method = "placeNewPlayer", at = @At("TAIL")) + private void onPlayerJoin(Connection connection, ServerPlayer player, CallbackInfo ci) { + if (player != null) { + int convertedCount = CompassDataHandler.convertPlayerLodestoneCompasses(player); + if (convertedCount > 0) { + BetterLodestones.LOGGER.info("Converted {} vanilla lodestone compasses for player {}", convertedCount, player.getName().getString()); + } + } + } +} diff --git a/common/src/main/java/io/lampnet/betterlodestones/recipe/CompassUnbindingRecipe.java b/common/src/main/java/io/lampnet/betterlodestones/recipe/CompassUnbindingRecipe.java new file mode 100644 index 0000000..ff2ef9c --- /dev/null +++ b/common/src/main/java/io/lampnet/betterlodestones/recipe/CompassUnbindingRecipe.java @@ -0,0 +1,104 @@ +package io.lampnet.betterlodestones.recipe; + +import com.google.gson.JsonObject; +import io.lampnet.betterlodestones.CompassDataHandler; +import net.minecraft.core.RegistryAccess; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.inventory.CraftingContainer; +import net.minecraft.world.item.CompassItem; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.crafting.CraftingBookCategory; +import net.minecraft.world.item.crafting.CustomRecipe; +import net.minecraft.world.item.crafting.RecipeSerializer; +import net.minecraft.world.level.Level; +import org.jetbrains.annotations.NotNull; + +public class CompassUnbindingRecipe extends CustomRecipe { + public CompassUnbindingRecipe(ResourceLocation id, CraftingBookCategory category) { + super(id, category); + } + + @Override + public boolean matches(CraftingContainer craftingContainer, Level level) { + ItemStack compass = ItemStack.EMPTY; + + for (int i = 0; i < craftingContainer.getContainerSize(); ++i) { + ItemStack currentStack = craftingContainer.getItem(i); + if (!currentStack.isEmpty()) { + if (currentStack.getItem() instanceof CompassItem) { + if (!compass.isEmpty()) { + return false; // More than one compass + } + compass = currentStack; + } else { + return false; // Other items in the grid + } + } + } + + if (compass.isEmpty() || !compass.hasTag()) { + return false; + } + + CompoundTag tag = compass.getTag(); + return tag != null && (tag.contains("LodestonePos") || tag.contains("BetterLodestones")); + } + + @Override + public @NotNull ItemStack assemble(CraftingContainer craftingContainer, RegistryAccess registryAccess) { + ItemStack compass = ItemStack.EMPTY; + + for (int i = 0; i < craftingContainer.getContainerSize(); i++) { + ItemStack currentStack = craftingContainer.getItem(i); + if (!currentStack.isEmpty() && currentStack.getItem() instanceof CompassItem) { + compass = currentStack.copy(); + compass.setCount(1); + break; + } + } + + if (compass.isEmpty()) { + return ItemStack.EMPTY; + } + + CompassDataHandler.convertVanillaLodestoneCompass(compass); + CompassDataHandler.removeCurrentLodestone(compass); + + if (CompassDataHandler.getLodestoneCount(compass) == 0) { + return new ItemStack(Items.COMPASS); + } + + return compass; + } + + @Override + public boolean canCraftInDimensions(int width, int height) { + return width >= 1 && height >= 1; + } + + @Override + public @NotNull RecipeSerializer getSerializer() { + return CompassUnbindingRecipeSerializer.INSTANCE; + } + + public static class CompassUnbindingRecipeSerializer implements RecipeSerializer { + public static final CompassUnbindingRecipeSerializer INSTANCE = new CompassUnbindingRecipeSerializer(); + + @Override + public @NotNull CompassUnbindingRecipe fromJson(ResourceLocation recipeId, JsonObject json) { + return new CompassUnbindingRecipe(recipeId, CraftingBookCategory.MISC); + } + + @Override + public @NotNull CompassUnbindingRecipe fromNetwork(ResourceLocation recipeId, FriendlyByteBuf buffer) { + return new CompassUnbindingRecipe(recipeId, CraftingBookCategory.MISC); + } + + @Override + public void toNetwork(FriendlyByteBuf buffer, CompassUnbindingRecipe recipe) { + } + } +} diff --git a/common/src/main/java/io/lampnet/betterlodestones/recipe/ConfigurableLodestoneRecipe.java b/common/src/main/java/io/lampnet/betterlodestones/recipe/ConfigurableLodestoneRecipe.java new file mode 100644 index 0000000..c7304cc --- /dev/null +++ b/common/src/main/java/io/lampnet/betterlodestones/recipe/ConfigurableLodestoneRecipe.java @@ -0,0 +1,77 @@ +package io.lampnet.betterlodestones.recipe; + +import com.google.gson.JsonObject; +import io.lampnet.betterlodestones.config.ConfigManager; +import net.minecraft.core.RegistryAccess; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.inventory.CraftingContainer; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.crafting.CraftingBookCategory; +import net.minecraft.world.item.crafting.CustomRecipe; +import net.minecraft.world.item.crafting.RecipeSerializer; +import net.minecraft.world.level.Level; +import org.jetbrains.annotations.NotNull; + +public class ConfigurableLodestoneRecipe extends CustomRecipe { + public ConfigurableLodestoneRecipe(ResourceLocation id, CraftingBookCategory category) { + super(id, category); + } + + @Override + public boolean matches(CraftingContainer craftingContainer, Level level) { + if (craftingContainer.getWidth() != 3 || craftingContainer.getHeight() != 3) { + return false; + } + + Item expectedCenterItem = ConfigManager.useModernLodestoneRecipe() ? Items.IRON_INGOT : Items.NETHERITE_INGOT; + if (!craftingContainer.getItem(4).is(expectedCenterItem)) { + return false; + } + + for (int i = 0; i < 9; i++) { + if (i == 4) { + continue; + } + if (!craftingContainer.getItem(i).is(Items.CHISELED_STONE_BRICKS)) { + return false; + } + } + return true; + } + + @Override + public @NotNull ItemStack assemble(@NotNull CraftingContainer craftingContainer, RegistryAccess registryAccess) { + return new ItemStack(Items.LODESTONE); + } + + @Override + public boolean canCraftInDimensions(int width, int height) { + return width >= 3 && height >= 3; + } + + @Override + public @NotNull RecipeSerializer getSerializer() { + return Serializer.INSTANCE; + } + + public static class Serializer implements RecipeSerializer { + public static final Serializer INSTANCE = new Serializer(); + + @Override + public @NotNull ConfigurableLodestoneRecipe fromJson(ResourceLocation recipeId, JsonObject json) { + return new ConfigurableLodestoneRecipe(recipeId, CraftingBookCategory.MISC); + } + + @Override + public @NotNull ConfigurableLodestoneRecipe fromNetwork(ResourceLocation recipeId, FriendlyByteBuf buffer) { + return new ConfigurableLodestoneRecipe(recipeId, CraftingBookCategory.MISC); + } + + @Override + public void toNetwork(FriendlyByteBuf buffer, ConfigurableLodestoneRecipe recipe) { + } + } +} diff --git a/common/src/main/java/io/lampnet/betterlodestones/registry/BetterLodestonesRegistry.java b/common/src/main/java/io/lampnet/betterlodestones/registry/BetterLodestonesRegistry.java new file mode 100644 index 0000000..7d3fab6 --- /dev/null +++ b/common/src/main/java/io/lampnet/betterlodestones/registry/BetterLodestonesRegistry.java @@ -0,0 +1,29 @@ +package io.lampnet.betterlodestones.registry; + +import dev.architectury.registry.registries.DeferredRegister; +import dev.architectury.registry.registries.RegistrySupplier; +import io.lampnet.betterlodestones.BetterLodestones; +import io.lampnet.betterlodestones.advancement.DimensionalCompassBindingTrigger; +import io.lampnet.betterlodestones.recipe.CompassUnbindingRecipe; +import io.lampnet.betterlodestones.recipe.ConfigurableLodestoneRecipe; +import net.minecraft.advancements.CriteriaTriggers; +import net.minecraft.core.registries.Registries; +import net.minecraft.world.item.crafting.RecipeSerializer; + +public final class BetterLodestonesRegistry { + public static final DeferredRegister> RECIPE_SERIALIZERS = + DeferredRegister.create(BetterLodestones.MOD_ID, Registries.RECIPE_SERIALIZER); + + public static final RegistrySupplier> CONFIGURABLE_LODESTONE = + RECIPE_SERIALIZERS.register("configurable_lodestone", () -> ConfigurableLodestoneRecipe.Serializer.INSTANCE); + + public static final RegistrySupplier> COMPASS_UNBINDING = + RECIPE_SERIALIZERS.register("compass_unbinding", () -> CompassUnbindingRecipe.CompassUnbindingRecipeSerializer.INSTANCE); + + public static final DimensionalCompassBindingTrigger DIMENSIONAL_COMPASS_BINDING = new DimensionalCompassBindingTrigger(); + + public static void init() { + RECIPE_SERIALIZERS.register(); + CriteriaTriggers.register(DIMENSIONAL_COMPASS_BINDING); + } +} diff --git a/common/src/main/resources/assets/better_lodestones/lang/en_us.json b/common/src/main/resources/assets/better_lodestones/lang/en_us.json new file mode 100644 index 0000000..7954814 --- /dev/null +++ b/common/src/main/resources/assets/better_lodestones/lang/en_us.json @@ -0,0 +1,4 @@ +{ + "advancements.better_lodestones.dimensional_lodestone_master.title": "3D Navigator", + "advancements.better_lodestones.dimensional_lodestone_master.description": "Bind a compass to Lodestones in 3 different dimensions" +} diff --git a/common/src/main/resources/better_lodestones.mixins.json b/common/src/main/resources/better_lodestones.mixins.json new file mode 100644 index 0000000..7cd059e --- /dev/null +++ b/common/src/main/resources/better_lodestones.mixins.json @@ -0,0 +1,21 @@ +{ + "required": true, + "package": "io.lampnet.betterlodestones.mixin", + "compatibilityLevel": "JAVA_17", + "minVersion": "0.8", + "client": [ + ], + "mixins": [ + "CompassItemMixin", + "CompassItemMixinPointing", + "CompassTooltipMixin", + "InventoryInteractionMixin", + "ItemPickupMixin", + "ItemUseMixin", + "LevelBlockChangeMixin", + "PlayerJoinMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/common/src/main/resources/data/better_lodestones/advancements/adventure/dimensional_lodestone_master.json b/common/src/main/resources/data/better_lodestones/advancements/adventure/dimensional_lodestone_master.json new file mode 100644 index 0000000..c561576 --- /dev/null +++ b/common/src/main/resources/data/better_lodestones/advancements/adventure/dimensional_lodestone_master.json @@ -0,0 +1,27 @@ +{ + "parent": "minecraft:nether/use_lodestone", + "display": { + "icon": { + "item": "minecraft:compass", + "nbt": "{CustomModelData:1}" + }, + "title": { + "translate": "advancements.better_lodestones.dimensional_lodestone_master.title" + }, + "description": { + "translate": "advancements.better_lodestones.dimensional_lodestone_master.description" + }, + "frame": "challenge", + "show_toast": true, + "announce_to_chat": true, + "hidden": false + }, + "criteria": { + "dimensional_mastery": { + "trigger": "better_lodestones:dimensional_compass_binding", + "conditions": { + "min_dimensions": 3 + } + } + } +} diff --git a/common/src/main/resources/data/better_lodestones/recipes/compass_unbinding.json b/common/src/main/resources/data/better_lodestones/recipes/compass_unbinding.json new file mode 100644 index 0000000..03b07d5 --- /dev/null +++ b/common/src/main/resources/data/better_lodestones/recipes/compass_unbinding.json @@ -0,0 +1,3 @@ +{ + "type": "better_lodestones:compass_unbinding" +} diff --git a/common/src/main/resources/data/better_lodestones/recipes/configurable_lodestone.json b/common/src/main/resources/data/better_lodestones/recipes/configurable_lodestone.json new file mode 100644 index 0000000..9f0196c --- /dev/null +++ b/common/src/main/resources/data/better_lodestones/recipes/configurable_lodestone.json @@ -0,0 +1,3 @@ +{ + "type": "better_lodestones:configurable_lodestone" +} diff --git a/common/src/main/resources/data/minecraft/advancements/nether/use_lodestone.json b/common/src/main/resources/data/minecraft/advancements/nether/use_lodestone.json new file mode 100644 index 0000000..cf8a3e8 --- /dev/null +++ b/common/src/main/resources/data/minecraft/advancements/nether/use_lodestone.json @@ -0,0 +1,41 @@ +{ + "parent": "minecraft:adventure/root", + "display": { + "icon": { + "item": "minecraft:lodestone" + }, + "title": { + "translate": "advancements.nether.use_lodestone.title" + }, + "description": { + "translate": "advancements.nether.use_lodestone.description" + }, + "frame": "task", + "show_toast": true, + "announce_to_chat": true, + "hidden": false + }, + "criteria": { + "use_lodestone": { + "trigger": "minecraft:item_used_on_block", + "conditions": { + "location": [ + { + "condition": "minecraft:match_tool", + "predicate": { + "items": [ + "minecraft:compass" + ] + } + } + ], + "item": { + "items": [ + "minecraft:compass" + ] + }, + "block": "minecraft:lodestone" + } + } + } +} diff --git a/common/src/main/resources/data/minecraft/recipes/lodestone.json b/common/src/main/resources/data/minecraft/recipes/lodestone.json new file mode 100644 index 0000000..9f0196c --- /dev/null +++ b/common/src/main/resources/data/minecraft/recipes/lodestone.json @@ -0,0 +1,3 @@ +{ + "type": "better_lodestones:configurable_lodestone" +} diff --git a/fabric/build.gradle b/fabric/build.gradle new file mode 100644 index 0000000..f612834 --- /dev/null +++ b/fabric/build.gradle @@ -0,0 +1,54 @@ +plugins { + id 'com.github.johnrengelman.shadow' +} + +architectury { + platformSetupLoomIde() + fabric() +} + +configurations { + common { + canBeResolved = true + canBeConsumed = false + } + compileClasspath.extendsFrom common + runtimeClasspath.extendsFrom common + developmentFabric.extendsFrom common + + // Files in this configuration will be bundled into your mod using the Shadow plugin. + // Don't use the `shadow` configuration from the plugin itself as it's meant for excluding files. + shadowBundle { + canBeResolved = true + canBeConsumed = false + } +} + +dependencies { + modImplementation "net.fabricmc:fabric-loader:$rootProject.fabric_loader_version" + + modImplementation "net.fabricmc.fabric-api:fabric-api:$rootProject.fabric_api_version" + + modImplementation "dev.architectury:architectury-fabric:$rootProject.architectury_api_version" + + + common(project(path: ':common', configuration: 'namedElements')) { transitive false } + shadowBundle project(path: ':common', configuration: 'transformProductionFabric') +} + +processResources { + inputs.property 'version', project.version + + filesMatching('fabric.mod.json') { + expand version: project.version + } +} + +shadowJar { + configurations = [project.configurations.shadowBundle] + archiveClassifier = 'dev-shadow' +} + +remapJar { + input.set shadowJar.archiveFile +} diff --git a/fabric/src/main/java/io/lampnet/betterlodestones/fabric/BetterLodestonesFabric.java b/fabric/src/main/java/io/lampnet/betterlodestones/fabric/BetterLodestonesFabric.java new file mode 100644 index 0000000..5ba8dab --- /dev/null +++ b/fabric/src/main/java/io/lampnet/betterlodestones/fabric/BetterLodestonesFabric.java @@ -0,0 +1,13 @@ +package io.lampnet.betterlodestones.fabric; + +import io.lampnet.betterlodestones.BetterLodestones; +import io.lampnet.betterlodestones.fabric.config.BetterLodestonesFabricConfig; +import net.fabricmc.api.ModInitializer; + +public final class BetterLodestonesFabric implements ModInitializer { + @Override + public void onInitialize() { + BetterLodestonesFabricConfig.load(); + BetterLodestones.init(); + } +} diff --git a/fabric/src/main/java/io/lampnet/betterlodestones/fabric/config/BetterLodestonesFabricConfig.java b/fabric/src/main/java/io/lampnet/betterlodestones/fabric/config/BetterLodestonesFabricConfig.java new file mode 100644 index 0000000..8944248 --- /dev/null +++ b/fabric/src/main/java/io/lampnet/betterlodestones/fabric/config/BetterLodestonesFabricConfig.java @@ -0,0 +1,64 @@ +package io.lampnet.betterlodestones.fabric.config; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import io.lampnet.betterlodestones.BetterLodestones; +import io.lampnet.betterlodestones.config.ConfigManager; +import net.fabricmc.loader.api.FabricLoader; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +public class BetterLodestonesFabricConfig { + private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); + private static final Path CONFIG_PATH = FabricLoader.getInstance().getConfigDir().resolve("better-lodestones.json"); + + private static ConfigData configData = new ConfigData(); + + public static class ConfigData { + public int maxLodestones = 0; + public boolean useModernLodestoneRecipe = true; + + public ConfigData() {} + } + + public static void load() { + if (Files.exists(CONFIG_PATH)) { + try { + String json = Files.readString(CONFIG_PATH); + configData = GSON.fromJson(json, ConfigData.class); + if (configData == null) { + configData = new ConfigData(); + } + if (configData.maxLodestones < 0) { + configData.maxLodestones = 0; + } + ConfigManager.setMaxLodestones(configData.maxLodestones); + ConfigManager.setUseModernLodestoneRecipe(configData.useModernLodestoneRecipe); + BetterLodestones.LOGGER.info("Loaded Fabric config: maxLodestones = {}, useModernLodestoneRecipe = {}", + configData.maxLodestones, configData.useModernLodestoneRecipe); + } catch (Exception e) { + BetterLodestones.LOGGER.error("Failed to load Fabric config, using defaults", e); + configData = new ConfigData(); + ConfigManager.setMaxLodestones(configData.maxLodestones); + ConfigManager.setUseModernLodestoneRecipe(configData.useModernLodestoneRecipe); + } + } else { + save(); + BetterLodestones.LOGGER.info("Created default Fabric config"); + } + } + + public static void save() { + try { + configData.maxLodestones = ConfigManager.getMaxLodestones(); + configData.useModernLodestoneRecipe = ConfigManager.useModernLodestoneRecipe(); + Files.createDirectories(CONFIG_PATH.getParent()); + String json = GSON.toJson(configData); + Files.writeString(CONFIG_PATH, json); + } catch (IOException e) { + BetterLodestones.LOGGER.error("Failed to save Fabric config", e); + } + } +} diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json new file mode 100644 index 0000000..af46f82 --- /dev/null +++ b/fabric/src/main/resources/fabric.mod.json @@ -0,0 +1,31 @@ +{ + "schemaVersion": 1, + "id": "better_lodestones", + "version": "${version}", + "name": "Better Lodestones", + "description": "", + "authors": [ + "candle" + ], + "contact": { + "homepage": "https://lampnet.io" + }, + "license": "MIT", + "icon": "assets/betterlodestones/icon.png", + "environment": "*", + "entrypoints": { + "main": [ + "io.lampnet.betterlodestones.fabric.BetterLodestonesFabric" + ] + }, + "mixins": [ + "better_lodestones.mixins.json" + ], + "depends": { + "fabricloader": ">=0.17.2", + "minecraft": "~1.20.1", + "java": ">=17", + "architectury": ">=9.2.14", + "fabric-api": "*" + } +} diff --git a/forge/build.gradle b/forge/build.gradle new file mode 100644 index 0000000..a0427f0 --- /dev/null +++ b/forge/build.gradle @@ -0,0 +1,57 @@ +plugins { + id 'com.github.johnrengelman.shadow' +} + +loom { + forge { + mixinConfig "better_lodestones.mixins.json" + } +} + +architectury { + platformSetupLoomIde() + forge() +} + +configurations { + common { + canBeResolved = true + canBeConsumed = false + } + compileClasspath.extendsFrom common + runtimeClasspath.extendsFrom common + developmentForge.extendsFrom common + + // Files in this configuration will be bundled into your mod using the Shadow plugin. + // Don't use the `shadow` configuration from the plugin itself as it's meant for excluding files. + shadowBundle { + canBeResolved = true + canBeConsumed = false + } +} + +dependencies { + forge "net.minecraftforge:forge:$rootProject.forge_version" + + modImplementation "dev.architectury:architectury-forge:$rootProject.architectury_api_version" + + common(project(path: ':common', configuration: 'namedElements')) { transitive false } + shadowBundle project(path: ':common', configuration: 'transformProductionForge') +} + +processResources { + inputs.property 'version', project.version + + filesMatching('META-INF/mods.toml') { + expand version: project.version + } +} + +shadowJar { + configurations = [project.configurations.shadowBundle] + archiveClassifier = 'dev-shadow' +} + +remapJar { + input.set shadowJar.archiveFile +} diff --git a/forge/gradle.properties b/forge/gradle.properties new file mode 100644 index 0000000..8242585 --- /dev/null +++ b/forge/gradle.properties @@ -0,0 +1 @@ +loom.platform=forge diff --git a/forge/src/main/java/io/lampnet/betterlodestones/forge/BetterLodestonesForge.java b/forge/src/main/java/io/lampnet/betterlodestones/forge/BetterLodestonesForge.java new file mode 100644 index 0000000..389191c --- /dev/null +++ b/forge/src/main/java/io/lampnet/betterlodestones/forge/BetterLodestonesForge.java @@ -0,0 +1,32 @@ +package io.lampnet.betterlodestones.forge; + +import io.lampnet.betterlodestones.BetterLodestones; +import io.lampnet.betterlodestones.config.ConfigManager; +import io.lampnet.betterlodestones.forge.config.BetterLodestonesForgeConfig; +import dev.architectury.platform.forge.EventBuses; +import net.minecraftforge.fml.ModLoadingContext; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.config.ModConfig; +import net.minecraftforge.fml.event.config.ModConfigEvent; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; + +@Mod(BetterLodestones.MOD_ID) +public final class BetterLodestonesForge { + public BetterLodestonesForge() { + var modEventBus = FMLJavaModLoadingContext.get().getModEventBus(); + EventBuses.registerModEventBus(BetterLodestones.MOD_ID, modEventBus); + ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, BetterLodestonesForgeConfig.SPEC, "better-lodestones.toml"); + + modEventBus.addListener(this::onConfigLoad); + + BetterLodestones.init(); + } + + private void onConfigLoad(final ModConfigEvent event) { + if (event.getConfig().getSpec() == BetterLodestonesForgeConfig.SPEC) { + ConfigManager.setMaxLodestones(BetterLodestonesForgeConfig.getMaxLodestones()); + ConfigManager.setUseModernLodestoneRecipe(BetterLodestonesForgeConfig.useModernLodestoneRecipe()); + BetterLodestones.LOGGER.info("Loaded Forge config and updated ConfigManager."); + } + } +} diff --git a/forge/src/main/java/io/lampnet/betterlodestones/forge/config/BetterLodestonesForgeConfig.java b/forge/src/main/java/io/lampnet/betterlodestones/forge/config/BetterLodestonesForgeConfig.java new file mode 100644 index 0000000..e3d45a9 --- /dev/null +++ b/forge/src/main/java/io/lampnet/betterlodestones/forge/config/BetterLodestonesForgeConfig.java @@ -0,0 +1,35 @@ +package io.lampnet.betterlodestones.forge.config; + +import net.minecraftforge.common.ForgeConfigSpec; + +public class BetterLodestonesForgeConfig { + public static final ForgeConfigSpec SPEC; + public static final ForgeConfigSpec.IntValue MAX_LODESTONES; + public static final ForgeConfigSpec.BooleanValue USE_MODERN_LODESTONE_RECIPE; + + static { + ForgeConfigSpec.Builder builder = new ForgeConfigSpec.Builder(); + + builder.comment("Maximum number of Lodestones that can be bound to a single compass") + .comment("0 = No limit (default)") + .comment("1 = Vanilla behavior") + .comment("2-" + Integer.MAX_VALUE + " = Custom limit"); + + MAX_LODESTONES = builder + .defineInRange("maxLodestones", 0, 0, Integer.MAX_VALUE); + + builder.comment("Use modern Lodestone recipe, Iron Ingot instead of Netherite Ingot"); + USE_MODERN_LODESTONE_RECIPE = builder + .define("useModernLodestoneRecipe", true); + + SPEC = builder.build(); + } + + public static int getMaxLodestones() { + return MAX_LODESTONES.get(); + } + + public static boolean useModernLodestoneRecipe() { + return USE_MODERN_LODESTONE_RECIPE.get(); + } +} diff --git a/forge/src/main/resources/META-INF/mods.toml b/forge/src/main/resources/META-INF/mods.toml new file mode 100644 index 0000000..c96da5e --- /dev/null +++ b/forge/src/main/resources/META-INF/mods.toml @@ -0,0 +1,35 @@ +modLoader = "javafml" +loaderVersion = "[47,)" +#issueTrackerURL = "" +license = "MIT" + +[[mods]] +modId = "better_lodestones" +version = "${version}" +displayName = "Better Lodestones" +authors = "candle" +description = ''' + +''' +#logoFile = "" + +[[dependencies.better_lodestones]] +modId = "forge" +mandatory = true +versionRange = "[47,)" +ordering = "NONE" +side = "BOTH" + +[[dependencies.better_lodestones]] +modId = "minecraft" +mandatory = true +versionRange = "[1.20.1,)" +ordering = "NONE" +side = "BOTH" + +[[dependencies.better_lodestones]] +modId = "architectury" +mandatory = true +versionRange = "[9.2.14,)" +ordering = "AFTER" +side = "BOTH" diff --git a/forge/src/main/resources/pack.mcmeta b/forge/src/main/resources/pack.mcmeta new file mode 100644 index 0000000..dd6bdbc --- /dev/null +++ b/forge/src/main/resources/pack.mcmeta @@ -0,0 +1,6 @@ +{ + "pack": { + "description": "better_lodestones resources", + "pack_format": 15 + } +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..2167b69 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,15 @@ +# Done to increase the memory available to Gradle. +org.gradle.jvmargs=-Xmx2G +org.gradle.parallel=true +# Mod properties +mod_version=0.1 +maven_group=io.lampnet +archives_name=better-lodestones +enabled_platforms=fabric,forge +# Minecraft properties +minecraft_version=1.20.1 +# Dependencies +architectury_api_version=9.2.14 +fabric_loader_version=0.17.2 +fabric_api_version=0.92.6+1.20.1 +forge_version=1.20.1-47.4.9 diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..f3b75f3 --- /dev/null +++ b/gradlew @@ -0,0 +1,251 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..9b42019 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..f6e212d --- /dev/null +++ b/settings.gradle @@ -0,0 +1,14 @@ +pluginManagement { + repositories { + maven { url "https://maven.fabricmc.net/" } + maven { url "https://maven.architectury.dev/" } + maven { url "https://files.minecraftforge.net/maven/" } + gradlePluginPortal() + } +} + +rootProject.name = 'better_lodestones' + +include 'common' +include 'fabric' +include 'forge'