From 1316686d85a55d25c4cbe624334aee40e70dc8d6 Mon Sep 17 00:00:00 2001 From: Oth3r Date: Thu, 11 Jul 2024 13:43:54 -0500 Subject: [PATCH] complete rewrite --- gradle.properties | 2 +- src/main/java/one/oth3r/sit/Events.java | 270 ------------- src/main/java/one/oth3r/sit/ModMenu.java | 194 --------- src/main/java/one/oth3r/sit/Sit.java | 37 +- src/main/java/one/oth3r/sit/SitClient.java | 37 +- src/main/java/one/oth3r/sit/Utl.java | 74 ---- .../oth3r/sit/{ => command}/SitCommand.java | 52 +-- src/main/java/one/oth3r/sit/file/Config.java | 256 ------------ .../java/one/oth3r/sit/file/CustomBlock.java | 92 +++++ src/main/java/one/oth3r/sit/file/Data.java | 102 +++++ .../java/one/oth3r/sit/file/HandConfig.java | 97 +++++ .../java/one/oth3r/sit/file/HandSetting.java | 79 ++++ .../one/oth3r/sit/{ => file}/LangReader.java | 26 +- .../java/one/oth3r/sit/file/ServerConfig.java | 177 +++++++++ src/main/java/one/oth3r/sit/file/Updater.java | 288 ++++++++++++++ .../sit/mixin/TextDisplayDismountMixin.java | 74 ++++ .../{CustomPayloads.java => SitPayloads.java} | 9 +- .../java/one/oth3r/sit/screen/ModMenu.java | 185 +++++++++ src/main/java/one/oth3r/sit/utl/Events.java | 69 ++++ src/main/java/one/oth3r/sit/utl/Logic.java | 117 ++++++ .../java/one/oth3r/sit/utl/LoopManager.java | 56 +++ src/main/java/one/oth3r/sit/utl/Utl.java | 367 ++++++++++++++++++ src/main/resources/fabric.mod.json | 7 +- src/main/resources/sit!.mixins.json | 12 + 24 files changed, 1808 insertions(+), 871 deletions(-) delete mode 100644 src/main/java/one/oth3r/sit/Events.java delete mode 100644 src/main/java/one/oth3r/sit/ModMenu.java delete mode 100644 src/main/java/one/oth3r/sit/Utl.java rename src/main/java/one/oth3r/sit/{ => command}/SitCommand.java (64%) delete mode 100644 src/main/java/one/oth3r/sit/file/Config.java create mode 100644 src/main/java/one/oth3r/sit/file/CustomBlock.java create mode 100644 src/main/java/one/oth3r/sit/file/Data.java create mode 100644 src/main/java/one/oth3r/sit/file/HandConfig.java create mode 100644 src/main/java/one/oth3r/sit/file/HandSetting.java rename src/main/java/one/oth3r/sit/{ => file}/LangReader.java (85%) create mode 100644 src/main/java/one/oth3r/sit/file/ServerConfig.java create mode 100644 src/main/java/one/oth3r/sit/file/Updater.java create mode 100644 src/main/java/one/oth3r/sit/mixin/TextDisplayDismountMixin.java rename src/main/java/one/oth3r/sit/packet/{CustomPayloads.java => SitPayloads.java} (81%) create mode 100644 src/main/java/one/oth3r/sit/screen/ModMenu.java create mode 100644 src/main/java/one/oth3r/sit/utl/Events.java create mode 100644 src/main/java/one/oth3r/sit/utl/Logic.java create mode 100644 src/main/java/one/oth3r/sit/utl/LoopManager.java create mode 100644 src/main/java/one/oth3r/sit/utl/Utl.java create mode 100644 src/main/resources/sit!.mixins.json diff --git a/gradle.properties b/gradle.properties index 9063459..7928001 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,7 +9,7 @@ yarn_mappings=1.21+build.1 loader_version=0.15.11 # Mod Properties -mod_version=1.1.6+1.21 +mod_version=1.2.0.0+1.21 maven_group=one.oth3r archives_base_name=sit! diff --git a/src/main/java/one/oth3r/sit/Events.java b/src/main/java/one/oth3r/sit/Events.java deleted file mode 100644 index 9ed4bc3..0000000 --- a/src/main/java/one/oth3r/sit/Events.java +++ /dev/null @@ -1,270 +0,0 @@ -package one.oth3r.sit; - -import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; -import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; -import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; -import net.fabricmc.fabric.api.event.player.UseBlockCallback; -import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; -import net.minecraft.block.*; -import net.minecraft.block.enums.BlockHalf; -import net.minecraft.block.enums.SlabType; -import net.minecraft.entity.Entity; -import net.minecraft.entity.EntityType; -import net.minecraft.entity.decoration.DisplayEntity; -import net.minecraft.item.BlockItem; -import net.minecraft.item.ItemStack; -import net.minecraft.registry.Registries; -import net.minecraft.server.network.ServerPlayerEntity; -import net.minecraft.text.Text; -import net.minecraft.util.ActionResult; -import net.minecraft.util.UseAction; -import net.minecraft.util.hit.HitResult; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.Box; -import net.minecraft.util.math.Vec3d; -import net.minecraft.world.World; -import one.oth3r.sit.Utl.HandType; -import one.oth3r.sit.Utl.PlayerSettings; -import one.oth3r.sit.Utl.PlayerSettings.Setting; - -import java.util.*; - -public class Events { - private static int tick; - public static HashMap entities = new HashMap<>(); - public static HashMap checkPlayers = new HashMap<>(); - public static boolean checkLogic(ServerPlayerEntity player) { - ArrayList food = new ArrayList<>(); - food.add(UseAction.EAT); - food.add(UseAction.DRINK); - ArrayList notUsable = new ArrayList<>(food); - notUsable.add(UseAction.NONE); - HashMap itemMap = new HashMap<>(); - itemMap.put(HandType.main,player.getMainHandStack()); - itemMap.put(HandType.off,player.getOffHandStack()); - // if sneaking cant sit - if (player.isSneaking()) return false; - // for both hands - for (HandType type:HandType.values()) { - ItemStack targetStack = itemMap.get(type); - // if req is empty and the item isn't empty, false - if (PlayerSettings.getRequirement(player,type).equals(config.HandRequirement.empty) && !targetStack.isEmpty()) return false; - // if req is restrictive - if (PlayerSettings.getRequirement(player,type).equals(config.HandRequirement.restrictive)) { - // if item is in blacklist, false - if (checkList(PlayerSettings.getList(player,type,Setting.blacklist),targetStack)) return false; - // if item is NOT in whitelist - if (!checkList(PlayerSettings.getList(player,type,Setting.whitelist),targetStack)) { - // if block is restricted and items is block, false, ect - if (PlayerSettings.getToggle(player,type,Setting.block) && (targetStack.getItem() instanceof BlockItem)) return false; - if (PlayerSettings.getToggle(player,type,Setting.food) && food.contains(targetStack.getUseAction())) return false; - if (PlayerSettings.getToggle(player,type,Setting.usable) && !notUsable.contains(targetStack.getUseAction())) return false; - } - } - } - // else true - return true; - } - public static boolean checkList(List list, ItemStack itemStack) { - // check if a list has an item - String itemID = Registries.ITEM.getId(itemStack.getItem()).toString(); - return list.contains(itemID); - } - public static HashMap> getCustomBlocks() { - // get a hashmap of custom blocks - HashMap> map = new HashMap<>(); - int i = 1; - for (String s:config.customBlocks) { - String[] split = s.split("\\|"); - HashMap data = new HashMap<>(); - data.put("block",split[0]); - data.put("height",split[1]); - data.put("hitbox",split[2]); - if (split.length==4) data.put("state",split[3]); - map.put(String.valueOf(i),data); - i++; - } - return map; - } - public static boolean isSitSafe(Block block) { - // check if the block is sit safe (like a sign in the way) - return block instanceof WallSignBlock || block instanceof TrapdoorBlock || - block instanceof WallBannerBlock || block instanceof AirBlock; - } - public static boolean checkBlocks(BlockPos pos, World world, boolean isAbove) { - BlockState blockState = world.getBlockState(pos); - Block block = blockState.getBlock(); - // make sure the block above the chair is safe - if (!isSitSafe(world.getBlockState(pos.add(0,1,0)).getBlock())) return false; - // if the player is above the block, (taller) check the next block above - if (isAbove && !isSitSafe(world.getBlockState(pos.add(0,2,0)).getBlock())) return false; - //if there's already an entity at the block location or one above it - for (Entity entity:entities.values()) if (entity.getBlockPos().equals(pos) || entity.getBlockPos().add(0,1,0).equals(pos)) return false; - - // return for the 4 default types - if (block instanceof StairsBlock && config.stairsOn) return blockState.get(StairsBlock.HALF) == BlockHalf.BOTTOM; - if (block instanceof SlabBlock && config.slabsOn) return blockState.get(SlabBlock.TYPE) == SlabType.BOTTOM; - if (block instanceof CarpetBlock && config.carpetsOn) return true; - if (blockState.isFullCube(world,pos.add(0,1,0)) && config.fullBlocksOn) return true; - // custom checker - if (config.customOn && !config.customBlocks.isEmpty()) { - for (HashMap map:getCustomBlocks().values()) { - String blockID = Registries.BLOCK.getId(block).toString(); - if (map.get("block").equals(blockID)) { - if (!map.containsKey("state")) return true; - String[] states = ((String) map.get("state")).split(",\\s*"); - boolean matching = true; - for (String state:states) { - if (state.charAt(0) == '!') { - if (blockState.toString().contains(state.substring(1))) matching = false; - } else if (!blockState.toString().contains(state)) matching = false; - } - return matching; - } - } - } - return false; - } - public static boolean isAboveBlockheight(Entity entity) { - return entity.getPitch()<0; - } - public static void setEntity(BlockPos pos, World world, Entity entity) { - Block block = world.getBlockState(pos).getBlock(); - entity.setCustomName(Text.of(Sit.ENTITY_NAME)); - entity.setCustomNameVisible(false); - double hitBoxY = 0.5; - entity.updatePositionAndAngles(pos.getX() + 0.5, pos.getY()+.47, pos.getZ() + 0.5, 0, 0); - entity.setInvulnerable(true); - if (block instanceof StairsBlock) { - entity.updatePositionAndAngles(pos.getX() + 0.5, pos.getY()+.27, pos.getZ() + 0.5, 0, 0); - hitBoxY = 2; - } - if (block instanceof SlabBlock) { - entity.updatePositionAndAngles(pos.getX() + 0.5, pos.getY()+.27, pos.getZ() + 0.5, 0, 0); - hitBoxY = 1; - } - if (block instanceof CarpetBlock) { - entity.updatePositionAndAngles(pos.getX() + 0.5, pos.getY()-.17, pos.getZ() + 0.5, 0, 0); - hitBoxY = 0.125; - } - if (world.getBlockState(pos).isFullCube(world,pos.add(0,1,0))) { - entity.updatePositionAndAngles(pos.getX() + 0.5, pos.getY()+.78, pos.getZ() + 0.5, 0, 0); - hitBoxY = 2; - } - if (config.customOn && !config.customBlocks.isEmpty()) { - for (HashMap map:getCustomBlocks().values()) { - String blockID = Registries.BLOCK.getId(block).toString(); - if (map.get("block").equals(blockID)) { - double input = Math.max(Math.min(Double.parseDouble((String) map.get("height")),1),0); - entity.updatePositionAndAngles(pos.getX() + 0.5, pos.getY()+input-.22, pos.getZ() + 0.5, 0, 0); - hitBoxY = Double.parseDouble((String) map.get("hitbox")); - } - } - } - //1.20.2 mounting pos change (shifts everything down by .25) - double oneTwentyTwo = .25; - entity.updatePositionAndAngles(entity.getX(),entity.getY()+oneTwentyTwo,entity.getZ(),0,0); - entity.setBoundingBox(Box.of(Vec3d.of(pos),1.5,hitBoxY,1.5)); - //change pitch based on if player is sitting below block height or not - if (entity.getY() <= pos.getY()+.35+oneTwentyTwo) entity.setPitch(90); // below - else entity.setPitch(-90); // above - } - public static void register() { - ServerTickEvents.END_SERVER_TICK.register(minecraftServer -> minecraftServer.execute(Events::cleanUp)); - // PLAYER JOIN - ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> { - ServerPlayerEntity player = handler.player; - checkPlayers.put(player,2); - // put server settings in the player settings - Sit.playerSettings.put(player, PlayerSettings.getHandSettings()); - }); - ServerPlayConnectionEvents.DISCONNECT.register((handler, server) -> { - ServerPlayerEntity player = handler.player; - if (entities.containsKey(player)) { - if (!config.keepActive) { - player.dismountVehicle(); - entities.get(player).setRemoved(Entity.RemovalReason.DISCARDED); - } - entities.remove(player); - } - checkPlayers.remove(player); - Sit.playerSettings.remove(player); - }); - ServerLifecycleEvents.SERVER_STARTED.register(s -> { - Sit.server = s; - Sit.commandManager = s.getCommandManager(); - UseBlockCallback.EVENT.register((pl, world, hand, hitResult) -> { - ServerPlayerEntity player = Sit.server.getPlayerManager().getPlayer(pl.getUuid()); - if (player == null) return ActionResult.PASS; - if (hand == net.minecraft.util.Hand.MAIN_HAND && hitResult.getType() == HitResult.Type.BLOCK) { - BlockPos pos = hitResult.getBlockPos(); - if (!checkLogic(player)) return ActionResult.PASS; - // todo interactions entity to make the hitbox? - // make the entity first before checking to make sure the blocks around are fine - DisplayEntity.TextDisplayEntity entity = new DisplayEntity.TextDisplayEntity(EntityType.TEXT_DISPLAY,player.getServerWorld()); - setEntity(pos,world,entity); - if (checkBlocks(pos,world,isAboveBlockheight(entity))) { - if (entities.containsKey(player)) { - if (!config.sitWhileSeated) return ActionResult.PASS; - entities.get(player).setRemoved(Entity.RemovalReason.DISCARDED); - entities.remove(player); - } - player.getServerWorld().spawnEntity(entity); - player.startRiding(entity); - entities.put(player,entity); - return ActionResult.FAIL; - } - } - return ActionResult.PASS; - }); - }); - CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> SitCommand.register(dispatcher)); - } - public static void cleanUp() { - tick++; - if (tick >= 5) { - tick = 0; - Iterator> entityLoop = entities.entrySet().iterator(); - while (entityLoop.hasNext()) { - Map.Entry entry = entityLoop.next(); - ServerPlayerEntity player = entry.getKey(); - Entity entity = entry.getValue(); - if (player.getVehicle() == null || !player.getVehicle().equals(entity)) { - entity.setRemoved(Entity.RemovalReason.DISCARDED); - entityLoop.remove(); - } else { - // if below the blockheight, round up the player pos - BlockPos pos = new BlockPos(entity.getBlockX(),(int)Math.ceil(player.getY()),entity.getBlockZ()); - // if above the block, round down the player pos - if (isAboveBlockheight(entity)) pos = new BlockPos(entity.getBlockX(),(int)Math.floor(player.getY()),entity.getBlockZ()); - BlockState blockState = player.getWorld().getBlockState(pos); - // check if said block is still there - if (blockState.isAir()) { - player.teleport(player.getX(),player.getBlockY()+1,player.getZ()); - entity.setRemoved(Entity.RemovalReason.DISCARDED); - entityLoop.remove(); - } - } - } - Iterator> playerCheckLoop = checkPlayers.entrySet().iterator(); - while (playerCheckLoop.hasNext()) { - Map.Entry entry = playerCheckLoop.next(); - ServerPlayerEntity player = entry.getKey(); - int i = entry.getValue(); - checkPlayers.put(player,i-1); - if (i<0) { - playerCheckLoop.remove(); - continue; - } - if (player.getVehicle() != null) { - Entity entity = player.getVehicle(); - if (entity.getName().getString().equals(Sit.ENTITY_NAME)) { - setEntity(player.getBlockPos().add(0,1,0),player.getServerWorld(),entity); - entities.put(player,entity); - playerCheckLoop.remove(); - } - } - } - } - } -} diff --git a/src/main/java/one/oth3r/sit/ModMenu.java b/src/main/java/one/oth3r/sit/ModMenu.java deleted file mode 100644 index c61cd40..0000000 --- a/src/main/java/one/oth3r/sit/ModMenu.java +++ /dev/null @@ -1,194 +0,0 @@ -package one.oth3r.sit; - -import com.terraformersmc.modmenu.api.ConfigScreenFactory; -import com.terraformersmc.modmenu.api.ModMenuApi; -import dev.isxander.yacl3.api.*; -import dev.isxander.yacl3.api.controller.BooleanControllerBuilder; -import dev.isxander.yacl3.api.controller.EnumControllerBuilder; -import dev.isxander.yacl3.api.controller.StringControllerBuilder; -import net.minecraft.text.MutableText; -import net.minecraft.text.Text; -import net.minecraft.text.TextColor; -import net.minecraft.util.Formatting; - -public class ModMenu implements ModMenuApi { - private static MutableText lang(String key, Object... args) { - return Utl.lang("config."+key,args); - } - @Override - public ConfigScreenFactory getModConfigScreenFactory() { - return parent -> YetAnotherConfigLib.createBuilder().save(config::save) - .title(Text.of("Sit!")) - .category(ConfigCategory.createBuilder() - .name(lang("category.general")) - .option(Option.createBuilder() - .name(lang("general.keep_active")) - .description(OptionDescription.of(lang("general.keep_active.description"))) - .binding(config.defaults.keepActive, () -> config.keepActive, n -> config.keepActive = n) - .controller(opt -> BooleanControllerBuilder.create(opt).trueFalseFormatter()) - .build()) - .option(Option.createBuilder() - .name(lang("general.sit_while_seated")) - .description(OptionDescription.of(lang("general.sit_while_seated.description"))) - .binding(config.defaults.sitWhileSeated, () -> config.sitWhileSeated, n -> config.sitWhileSeated = n) - .controller(opt -> BooleanControllerBuilder.create(opt).trueFalseFormatter()) - .build()) - .group(OptionGroup.createBuilder() - .name(lang("general.sittable")) - .description(OptionDescription.of(lang("general.sittable.description"))) - .option(Option.createBuilder() - .name(lang("general.sittable.stairs")) - .binding(config.defaults.stairsOn, () -> config.stairsOn, n -> config.stairsOn = n) - .controller(opt -> BooleanControllerBuilder.create(opt).onOffFormatter()) - .build()) - .option(Option.createBuilder() - .name(lang("general.sittable.slabs")) - .binding(config.defaults.slabsOn, () -> config.slabsOn, n -> config.slabsOn = n) - .controller(opt -> BooleanControllerBuilder.create(opt).onOffFormatter()) - .build()) - .option(Option.createBuilder() - .name(lang("general.sittable.carpets")) - .binding(config.defaults.carpetsOn, () -> config.carpetsOn, n -> config.carpetsOn = n) - .controller(opt -> BooleanControllerBuilder.create(opt).onOffFormatter()) - .build()) - .option(Option.createBuilder() - .name(lang("general.sittable.full_blocks")) - .binding(config.defaults.fullBlocksOn, () -> config.fullBlocksOn, n -> config.fullBlocksOn = n) - .controller(opt -> BooleanControllerBuilder.create(opt).onOffFormatter()) - .build()) - .option(Option.createBuilder() - .name(lang("general.sittable.custom")) - .description(OptionDescription.of(lang("general.sittable.custom.description"))) - .binding(config.defaults.customOn, () -> config.customOn, n -> config.customOn = n) - .controller(opt -> BooleanControllerBuilder.create(opt).onOffFormatter()) - .build()) - .build()) - .group(ListOption.createBuilder() - .name(lang("general.sittable_blocks")) - .description(OptionDescription.of( - lang("general.sittable_blocks.description") - .append("\n\n").append(lang("example", - Text.literal("\"") - .append(Text.literal("minecraft:campfire").styled(style -> style.withColor(TextColor.fromFormatting(Formatting.AQUA)))) - .append("|") - .append(Text.literal("0.255").styled(style -> style.withColor(TextColor.fromFormatting(Formatting.RED)))) - .append("|") - .append(Text.literal("1").styled(style -> style.withColor(TextColor.fromFormatting(Formatting.GREEN)))) - .append("|") - .append(Text.literal("lit=false").styled(style -> style.withColor(TextColor.fromFormatting(Formatting.GOLD)))) - .append("\"").styled(style -> style.withColor(TextColor.fromFormatting(Formatting.GRAY))))) - .append("\n\n").append(lang("general.sittable_blocks.description.2").styled(style -> style.withColor(TextColor.fromFormatting(Formatting.AQUA)))) - .append("\n").append(lang("general.sittable_blocks.description.3").styled(style -> style.withColor(TextColor.fromFormatting(Formatting.RED)))) - .append("\n").append(lang("general.sittable_blocks.description.4").styled(style -> style.withColor(TextColor.fromFormatting(Formatting.GREEN)))) - .append("\n").append(lang("general.sittable_blocks.description.5").styled(style -> style.withColor(TextColor.fromFormatting(Formatting.GOLD)))) - .append("\n\n").append(lang("general.sittable_blocks.description.6").styled(style -> style.withColor(TextColor.fromFormatting(Formatting.YELLOW)))))) - .binding(config.defaults.customBlocks, () -> config.customBlocks, n -> config.customBlocks = n) - .controller(StringControllerBuilder::create) - .initial("") - .build()) - .build()) - .category(ConfigCategory.createBuilder() - .name(lang("category.main_hand")) - .option(Option.createBuilder() - .name(lang("hand.requirement")) - .description(OptionDescription.of(lang("hand.requirement.description") - .append("\n\n").append(lang("hand.requirement.description.2",lang("hand.requirement.empty")).styled(style -> style.withColor(TextColor.fromFormatting(Formatting.AQUA)))) - .append("\n").append(lang("hand.requirement.description.3",lang("hand.requirement.restrictive")).styled(style -> style.withColor(TextColor.fromFormatting(Formatting.GREEN)))) - .append("\n").append(lang("hand.requirement.description.4",lang("hand.requirement.none")).styled(style -> style.withColor(TextColor.fromFormatting(Formatting.RED)))))) - .binding(config.defaults.mainReq, () -> config.mainReq, n -> config.mainReq = n) - .controller(opt -> EnumControllerBuilder.create(opt).enumClass(config.HandRequirement.class) - .formatValue(v -> lang("hand.requirement."+v.toString()))) - .build()) - .group(OptionGroup.createBuilder() - .name(lang("hand.restriction")) - .description(OptionDescription.of(lang("hand.restriction.description"))) - .option(Option.createBuilder() - .name(lang("hand.restriction.blocks")) - .binding(config.defaults.mainBlock,()-> config.mainBlock,n -> config.mainBlock = n) - .controller(opt -> BooleanControllerBuilder.create(opt).trueFalseFormatter()) - .build()) - .option(Option.createBuilder() - .name(lang("hand.restriction.food")) - .binding(config.defaults.mainFood,()-> config.mainFood,n -> config.mainFood = n) - .controller(opt -> BooleanControllerBuilder.create(opt).trueFalseFormatter()) - .build()) - .option(Option.createBuilder() - .name(lang("hand.restriction.usable")) - .description(OptionDescription.of(lang("hand.restriction.usable.description"))) - .binding(config.defaults.mainUsable,()-> config.mainUsable,n -> config.mainUsable = n) - .controller(opt -> BooleanControllerBuilder.create(opt).trueFalseFormatter()) - .build()) - .build()) - .group(ListOption.createBuilder() - .name(lang("hand.restriction.whitelist")) - .description(OptionDescription.of(lang("hand.restriction.list.description") - .append("\n\n").append(lang("example", - Text.empty().append(Utl.Assets.LIST).styled(style -> style.withItalic(true).withColor(TextColor.fromFormatting(Formatting.GRAY))))))) - .binding(config.defaults.mainWhitelist, () -> config.mainWhitelist, n -> config.mainWhitelist = n) - .controller(StringControllerBuilder::create) - .initial("") - .build()) - .group(ListOption.createBuilder() - .name(lang("hand.restriction.blacklist")) - .description(OptionDescription.of(lang("hand.restriction.list.description") - .append("\n\n").append(lang("example", - Text.empty().append(Utl.Assets.LIST).styled(style -> style.withItalic(true).withColor(TextColor.fromFormatting(Formatting.GRAY))))))) - .binding(config.defaults.mainBlacklist, () -> config.mainBlacklist, n -> config.mainBlacklist = n) - .controller(StringControllerBuilder::create) - .initial("") - .build()) - .build()) - .category(ConfigCategory.createBuilder() - .name(lang("category.off_hand")) - .option(Option.createBuilder() - .name(lang("hand.requirement")) - .description(OptionDescription.of(lang("hand.requirement.description") - .append("\n\n").append(lang("hand.requirement.description.2",lang("hand.requirement.empty")).styled(style -> style.withColor(TextColor.fromFormatting(Formatting.AQUA)))) - .append("\n").append(lang("hand.requirement.description.3",lang("hand.requirement.restrictive")).styled(style -> style.withColor(TextColor.fromFormatting(Formatting.GREEN)))) - .append("\n").append(lang("hand.requirement.description.4",lang("hand.requirement.none")).styled(style -> style.withColor(TextColor.fromFormatting(Formatting.RED)))))) - .binding(config.defaults.offReq, () -> config.offReq, n -> config.offReq = n) - .controller(opt -> EnumControllerBuilder.create(opt).enumClass(config.HandRequirement.class) - .formatValue(v -> lang("hand.requirement."+v.toString()))) - .build()) - .group(OptionGroup.createBuilder() - .name(lang("hand.restriction")) - .description(OptionDescription.of(lang("hand.restriction.description"))) - .option(Option.createBuilder() - .name(lang("hand.restriction.blocks")) - .binding(config.defaults.offBlock,()-> config.offBlock,n -> config.offBlock = n) - .controller(opt -> BooleanControllerBuilder.create(opt).trueFalseFormatter()) - .build()) - .option(Option.createBuilder() - .name(lang("hand.restriction.food")) - .binding(config.defaults.offFood,()-> config.offFood,n -> config.offFood = n) - .controller(opt -> BooleanControllerBuilder.create(opt).trueFalseFormatter()) - .build()) - .option(Option.createBuilder() - .name(lang("hand.restriction.usable")) - .description(OptionDescription.of(lang("hand.restriction.usable.description"))) - .binding(config.defaults.offUsable,()-> config.offUsable,n -> config.offUsable = n) - .controller(opt -> BooleanControllerBuilder.create(opt).trueFalseFormatter()) - .build()) - .build()) - .group(ListOption.createBuilder() - .name(lang("hand.restriction.whitelist")) - .description(OptionDescription.of(lang("hand.restriction.list.description") - .append("\n\n").append(lang("example", - Text.empty().append(Utl.Assets.LIST).styled(style -> style.withItalic(true).withColor(TextColor.fromFormatting(Formatting.GRAY))))))) - .binding(config.defaults.offWhitelist, () -> config.offWhitelist, n -> config.offWhitelist = n) - .controller(StringControllerBuilder::create) - .initial("") - .build()) - .group(ListOption.createBuilder() - .name(lang("hand.restriction.blacklist")) - .description(OptionDescription.of(lang("hand.restriction.list.description") - .append("\n\n").append(lang("example", - Text.empty().append(Utl.Assets.LIST).styled(style -> style.withItalic(true).withColor(TextColor.fromFormatting(Formatting.GRAY))))))) - .binding(config.defaults.offBlacklist, () -> config.offBlacklist, n -> config.offBlacklist = n) - .controller(StringControllerBuilder::create) - .initial("") - .build()) - .build()) - .build().generateScreen(parent); - } -} diff --git a/src/main/java/one/oth3r/sit/Sit.java b/src/main/java/one/oth3r/sit/Sit.java index bb837c5..f7f03f8 100644 --- a/src/main/java/one/oth3r/sit/Sit.java +++ b/src/main/java/one/oth3r/sit/Sit.java @@ -5,10 +5,15 @@ import com.google.gson.GsonBuilder; import com.google.gson.reflect.TypeToken; import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry; import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; +import net.fabricmc.loader.api.FabricLoader; import net.minecraft.server.MinecraftServer; import net.minecraft.server.command.CommandManager; import net.minecraft.server.network.ServerPlayerEntity; +import one.oth3r.sit.file.Data; +import one.oth3r.sit.packet.SitPayloads; +import one.oth3r.sit.utl.Events; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -16,31 +21,29 @@ import java.lang.reflect.Type; import java.util.HashMap; public class Sit implements ModInitializer { - public static final Logger LOGGER = LoggerFactory.getLogger("sit"); public static final String MOD_ID = "oth3r-sit"; + public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID); + public static String CONFIG_DIR = FabricLoader.getInstance().getConfigDir().toFile()+"/sit!/"; + public static HashMap> playerSettings = new HashMap<>(); public static final String ENTITY_NAME = "-sit!-entity-"; public static MinecraftServer server; public static CommandManager commandManager; - public static boolean isClient = false; + + public static boolean client = false; + public static boolean singleplayer = false; @Override public void onInitialize() { - //todo future: - // make it so it updates the sitting height and pos based on the block so if it changed while offline it still works (or if stair changes shape) - // inner stair offset & custom support for that ig - config.load(); - Events.register(); + Data.loadFiles(true); + Events.registerCommon(); + //PACKETS - ServerPlayNetworking.registerGlobalReceiver(PacketBuilder.getIdentifier(), - (server, player, handler, buf, responseSender) -> { - // copy to not throw errors - PacketBuilder packet = new PacketBuilder(buf.copy()); - server.execute(() -> { - Type hashMapToken = new TypeToken>() {}.getType(); - Gson gson = new GsonBuilder().disableHtmlEscaping().create(); - playerSettings.put(player,gson.fromJson(packet.getMessage(),hashMapToken)); - }); - }); + PayloadTypeRegistry.playC2S().register(SitPayloads.SettingsPayload.ID, SitPayloads.SettingsPayload.CODEC); + ServerPlayNetworking.registerGlobalReceiver(SitPayloads.SettingsPayload.ID,((payload, context) -> server.execute(() -> { + Type hashMapToken = new TypeToken>() {}.getType(); + Gson gson = new GsonBuilder().disableHtmlEscaping().create(); + playerSettings.put(context.player(),gson.fromJson(payload.value(),hashMapToken)); + }))); } } \ No newline at end of file diff --git a/src/main/java/one/oth3r/sit/SitClient.java b/src/main/java/one/oth3r/sit/SitClient.java index 621c788..105afb6 100644 --- a/src/main/java/one/oth3r/sit/SitClient.java +++ b/src/main/java/one/oth3r/sit/SitClient.java @@ -1,25 +1,40 @@ package one.oth3r.sit; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; import net.fabricmc.api.ClientModInitializer; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import one.oth3r.sit.file.Data; +import one.oth3r.sit.packet.SitPayloads; +import one.oth3r.sit.utl.Utl; public class SitClient implements ClientModInitializer { - public static boolean inGame = false; + private static boolean IN_GAME = false; + @Override public void onInitializeClient() { - Sit.isClient = true; + Sit.client = true; + ClientPlayConnectionEvents.JOIN.register((handler, sender, client) -> { - inGame = true; + IN_GAME = true; + if (client.isInSingleplayer()) Sit.singleplayer = true; // send a data packet whenever joining a server - client.execute(SitClient::sendPackets); + sendSettingsPackets(); + }); + + // reset cashed things + ClientPlayConnectionEvents.DISCONNECT.register((handler, client) -> { + IN_GAME = false; + if (Sit.singleplayer) { + // flip the single player switch + Sit.singleplayer = false; + } }); - // reset inGame - ClientPlayConnectionEvents.DISCONNECT.register((handler, client) -> inGame = false); } - public static void sendPackets() { - Gson gson = new GsonBuilder().disableHtmlEscaping().create(); - new PacketBuilder(gson.toJson(Utl.PlayerSettings.getHandSettings())).send(); + + /** + * sends the settings packets to the server + */ + public static void sendSettingsPackets() { + if (IN_GAME) ClientPlayNetworking.send(new SitPayloads.SettingsPayload(Utl.getGson().toJson(Data.getHandConfig()))); } } diff --git a/src/main/java/one/oth3r/sit/Utl.java b/src/main/java/one/oth3r/sit/Utl.java deleted file mode 100644 index b1bd1fd..0000000 --- a/src/main/java/one/oth3r/sit/Utl.java +++ /dev/null @@ -1,74 +0,0 @@ -package one.oth3r.sit; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.reflect.TypeToken; -import net.minecraft.server.network.ServerPlayerEntity; -import net.minecraft.text.MutableText; -import net.minecraft.text.Text; - -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; - -public class Utl { - public static MutableText lang(String key, Object... args) { - if (Sit.isClient) return Text.translatable(key, args); - else return LangReader.of(key, args).getTxT(); - } - public enum HandType { - main, - off - } - public static class PlayerSettings { - public enum Setting { - requirement, - block, - food, - usable, - whitelist, - blacklist - } - /** - * Gets a HashMap of all player configurable settings. - * @return a map with player hand settings. - */ - public static HashMap getHandSettings() { - Gson gson = new GsonBuilder().disableHtmlEscaping().create(); - HashMap settings = new HashMap<>(); - // main hand - settings.put("hand.main."+Setting.requirement,String.valueOf(config.mainReq)); - settings.put("hand.main."+Setting.block,String.valueOf(config.mainBlock)); - settings.put("hand.main."+Setting.food,String.valueOf(config.mainFood)); - settings.put("hand.main."+Setting.usable,String.valueOf(config.mainUsable)); - settings.put("hand.main."+Setting.whitelist,gson.toJson(config.mainWhitelist)); - settings.put("hand.main."+Setting.blacklist,gson.toJson(config.mainBlacklist)); - // copy but offhand - settings.put("hand.off."+Setting.requirement,String.valueOf(config.offReq)); - settings.put("hand.off."+Setting.block,String.valueOf(config.offBlock)); - settings.put("hand.off."+Setting.food,String.valueOf(config.offFood)); - settings.put("hand.off."+Setting.usable,String.valueOf(config.offUsable)); - settings.put("hand.off."+Setting.whitelist,gson.toJson(config.offWhitelist)); - settings.put("hand.off."+Setting.blacklist,gson.toJson(config.offBlacklist)); - return settings; - } - // returns specific items from the player config - public static config.HandRequirement getRequirement(ServerPlayerEntity player, HandType type) { - return config.HandRequirement.get(Sit.playerSettings.get(player).get("hand."+type+".requirement")); - } - public static List getList(ServerPlayerEntity player, HandType type, Setting setting) { - Type listType = new TypeToken>() {}.getType(); - return new Gson().fromJson(Sit.playerSettings.get(player).get("hand."+type+"."+setting),listType); - } - public static boolean getToggle(ServerPlayerEntity player, HandType type, Setting setting) { - return Boolean.parseBoolean(Sit.playerSettings.get(player).get("hand."+type+"."+setting)); - } - } - public static class Assets { - public static final String CUSTOM_BLOCKS = "\"minecraft:campfire|0.255|1|lit=false\""; - public static final String REQUIREMENT_OPTIONS = String.format("%s, %s, %s", - config.HandRequirement.empty,config.HandRequirement.restrictive,config.HandRequirement.none); - public static final String LIST = "\"minecraft:torch\""; - } -} diff --git a/src/main/java/one/oth3r/sit/SitCommand.java b/src/main/java/one/oth3r/sit/command/SitCommand.java similarity index 64% rename from src/main/java/one/oth3r/sit/SitCommand.java rename to src/main/java/one/oth3r/sit/command/SitCommand.java index b740d5d..65264df 100644 --- a/src/main/java/one/oth3r/sit/SitCommand.java +++ b/src/main/java/one/oth3r/sit/command/SitCommand.java @@ -1,21 +1,20 @@ -package one.oth3r.sit; +package one.oth3r.sit.command; import com.mojang.brigadier.CommandDispatcher; -import com.mojang.brigadier.ParseResults; import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.context.CommandContext; -import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.suggestion.Suggestions; import com.mojang.brigadier.suggestion.SuggestionsBuilder; -import net.minecraft.entity.EntityType; -import net.minecraft.entity.decoration.DisplayEntity; import net.minecraft.server.command.CommandManager; import net.minecraft.server.command.ServerCommandSource; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.text.TextColor; import net.minecraft.util.Formatting; import net.minecraft.util.math.BlockPos; -import net.minecraft.world.World; +import one.oth3r.sit.utl.Logic; +import one.oth3r.sit.Sit; +import one.oth3r.sit.utl.Utl; +import one.oth3r.sit.file.Data; import java.util.concurrent.CompletableFuture; @@ -29,11 +28,13 @@ public class SitCommand { .suggests(SitCommand::getSuggestions) .executes((context2) -> command(context2.getSource(), context2.getInput())))); } + public static CompletableFuture getSuggestions(CommandContext context, SuggestionsBuilder builder) { builder.suggest("reload"); builder.suggest("purgeChairEntities"); return builder.buildFuture(); } + private static int command(ServerCommandSource source, String arg) { ServerPlayerEntity player = source.getPlayer(); //trim all the arguments before the command @@ -45,48 +46,37 @@ public class SitCommand { String[] args = arg.split(" "); if (args[0].equalsIgnoreCase("sit")) args = arg.replaceFirst("sit ", "").split(" "); + // if console if (player == null) { if (args[0].equalsIgnoreCase("reload")) { - config.load(); + Logic.reload(); Sit.LOGGER.info(Utl.lang("msg.reloaded").getString()); } return 1; } + if (args[0].equalsIgnoreCase("sit")) { + // todo make the command target the block that the player is looking at, if not looking at a block default to below BlockPos pos = player.getBlockPos(); + if (!(player.getY() -((int) player.getY()) > 0.00)) { pos = pos.add(0,-1,0); } - World world = player.getWorld(); + // if already sitting, ignore - if (Events.entities.containsKey(player)) return 1; - // make entity first to check the blocks - DisplayEntity.TextDisplayEntity entity = new DisplayEntity.TextDisplayEntity(EntityType.TEXT_DISPLAY,player.getServerWorld()); - Events.setEntity(pos,world,entity); - if (Events.checkBlocks(pos,world,Events.isAboveBlockheight(entity))) { - player.getServerWorld().spawnEntity(entity); - player.startRiding(entity); - Events.entities.put(player,entity); - return 1; - } + if (Data.getSitEntity(player) != null) return 1; + + // try to make the player sit + Logic.sit(player,pos,null); } + if (args[0].equalsIgnoreCase("reload")) { - config.load(); + Logic.reload(); player.sendMessage(Utl.lang("msg.reloaded").styled(style -> style.withColor(TextColor.fromFormatting(Formatting.GREEN)))); } - if (args[0].equalsIgnoreCase("purgeChairEntities")) { - String cmd = "kill @e[type=minecraft:text_display,name=\""+Sit.ENTITY_NAME+"\"]"; - try { - ParseResults parse = - Sit.commandManager.getDispatcher().parse(cmd, player.getCommandSource()); - Sit.commandManager.getDispatcher().execute(parse); - player.sendMessage(Utl.lang("msg.purged")); - } catch (CommandSyntaxException e) { - player.sendMessage(Utl.lang("msg.purged")); - e.printStackTrace(); - } - } + + if (args[0].equalsIgnoreCase("purgeChairEntities")) Utl.Entity.purge(player,true); return 1; } } diff --git a/src/main/java/one/oth3r/sit/file/Config.java b/src/main/java/one/oth3r/sit/file/Config.java deleted file mode 100644 index a2c2290..0000000 --- a/src/main/java/one/oth3r/sit/file/Config.java +++ /dev/null @@ -1,256 +0,0 @@ -package one.oth3r.sit.file; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonSyntaxException; -import com.google.gson.reflect.TypeToken; -import net.fabricmc.loader.api.FabricLoader; -import one.oth3r.sit.LangReader; -import one.oth3r.sit.Sit; -import one.oth3r.sit.SitClient; -import one.oth3r.sit.Utl.*; - -import java.io.File; -import java.io.FileInputStream; -import java.lang.reflect.Type; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.util.ArrayList; -import java.util.List; -import java.util.Properties; - -public class Config { - public static String lang = defaults.lang; - public static boolean keepActive = defaults.keepActive; - public static boolean sitWhileSeated = defaults.sitWhileSeated; - public static boolean stairsOn = defaults.stairsOn; - public static boolean slabsOn = defaults.slabsOn; - public static boolean carpetsOn = defaults.carpetsOn; - public static boolean fullBlocksOn = defaults.fullBlocksOn; - public static boolean customOn = defaults.customOn; - public static List customBlocks = defaults.customBlocks; - public enum HandRequirement { - empty, - restrictive, - none; - public static HandRequirement get(String s) { - try { - return HandRequirement.valueOf(s); - - } catch (IllegalArgumentException e) { - return empty; - } - } - } - public static HandRequirement mainReq = defaults.mainReq; - public static boolean mainBlock = defaults.mainBlock; - public static boolean mainFood = defaults.mainFood; - public static boolean mainUsable = defaults.mainUsable; - public static List mainWhitelist = defaults.mainWhitelist; - public static List mainBlacklist = defaults.mainBlacklist; - public static HandRequirement offReq = defaults.offReq; - public static boolean offBlock = defaults.offBlock; - public static boolean offFood = defaults.offFood; - public static boolean offUsable = defaults.offUsable; - public static List offWhitelist = defaults.offWhitelist; - public static List offBlacklist = defaults.offBlacklist; - public static File configFile() { - return new File(FabricLoader.getInstance().getConfigDir().toFile()+"/Sit!.properties"); - } - public static void load() { - if (!configFile().exists() || !configFile().canRead()) { - save(); - load(); - return; - } - try (FileInputStream fileStream = new FileInputStream(configFile())) { - Properties properties = new Properties(); - properties.load(fileStream); - String ver = (String) properties.computeIfAbsent("version", a -> String.valueOf(defaults.version)); - - // if the old version system (v1.0) remove "v" - if (ver.contains("v")) ver = ver.substring(1); - - loadVersion(properties,Double.parseDouble(ver)); - LangReader.loadLanguageFile(); - - save(); - } catch (Exception e) { - //read fail - e.printStackTrace(); - save(); - } - } - public static ArrayList validateCustomBlocks(ArrayList fix) { - ArrayList out = new ArrayList<>(); - for (String entry : fix) { - String[] split = entry.split("\\|"); - // skip if not the right size - if (split.length < 3 || split.length > 4) continue; - // keep going if that block exists -// if (Registries.BLOCK.stream().anyMatch(match -> Registries.BLOCK.getId(match).toString().equals(split[0]))) {} - // if the other entries aren't correct, skip - if (!Num.isFloat(split[1]) || !Num.isInt(split[2])) continue; - // add if everything is a okay - out.add(entry); - } - return out; - } - public static void loadVersion(Properties properties, double version) { - try { - Gson gson = new GsonBuilder().disableHtmlEscaping().create(); - Type listType = new TypeToken>() {}.getType(); - lang = (String) properties.computeIfAbsent("lang", a -> defaults.lang); - //CONFIG - keepActive = Boolean.parseBoolean((String) properties.computeIfAbsent("keep-active", a -> String.valueOf(defaults.keepActive))); - sitWhileSeated = Boolean.parseBoolean((String) properties.computeIfAbsent("sit-while-seated", a -> String.valueOf(defaults.sitWhileSeated))); - stairsOn = Boolean.parseBoolean((String) properties.computeIfAbsent("stairs", a -> String.valueOf(defaults.stairsOn))); - slabsOn = Boolean.parseBoolean((String) properties.computeIfAbsent("slabs", a -> String.valueOf(defaults.slabsOn))); - carpetsOn = Boolean.parseBoolean((String) properties.computeIfAbsent("carpets", a -> String.valueOf(defaults.carpetsOn))); - fullBlocksOn = Boolean.parseBoolean((String) properties.computeIfAbsent("full-blocks", a -> String.valueOf(defaults.fullBlocksOn))); - customOn = Boolean.parseBoolean((String) properties.computeIfAbsent("custom", a -> String.valueOf(defaults.customOn))); - try { - customBlocks = validateCustomBlocks(new Gson().fromJson((String) - properties.computeIfAbsent("custom-blocks", a -> gson.toJson(defaults.customBlocks)), listType)); - } catch (JsonSyntaxException ignore) {} - mainReq = HandRequirement.get((String) properties.computeIfAbsent("hand.main.requirement", a -> String.valueOf(defaults.mainReq))); - mainBlock = Boolean.parseBoolean((String) properties.computeIfAbsent("hand.main.block", a -> String.valueOf(defaults.mainBlock))); - mainFood = Boolean.parseBoolean((String) properties.computeIfAbsent("hand.main.food", a -> String.valueOf(defaults.mainFood))); - mainUsable = Boolean.parseBoolean((String) properties.computeIfAbsent("hand.main.usable", a -> String.valueOf(defaults.mainUsable))); - try { - mainWhitelist = new Gson().fromJson((String) - properties.computeIfAbsent("hand.main.whitelist", a -> gson.toJson(defaults.mainWhitelist)), listType); - } catch (JsonSyntaxException ignore) {} - try { - mainBlacklist = new Gson().fromJson((String) - properties.computeIfAbsent("hand.main.blacklist", a -> gson.toJson(defaults.mainBlacklist)), listType); - } catch (JsonSyntaxException ignore) {} - offReq = HandRequirement.get((String) properties.computeIfAbsent("hand.off.requirement", a -> String.valueOf(defaults.offReq))); - offBlock = Boolean.parseBoolean((String) properties.computeIfAbsent("hand.off.block", a -> String.valueOf(defaults.offBlock))); - offFood = Boolean.parseBoolean((String) properties.computeIfAbsent("hand.off.food", a -> String.valueOf(defaults.offFood))); - offUsable = Boolean.parseBoolean((String) properties.computeIfAbsent("hand.off.usable", a -> String.valueOf(defaults.offUsable))); - offWhitelist = new Gson().fromJson((String) - properties.computeIfAbsent("hand.off.whitelist", a -> gson.toJson(defaults.offWhitelist)), listType); - offBlacklist = new Gson().fromJson((String) - properties.computeIfAbsent("hand.off.blacklist", a -> gson.toJson(defaults.offBlacklist)), listType); - if (version == 1.0) { - mainReq = HandRequirement.get((String) properties.computeIfAbsent("main-hand-requirement", a -> String.valueOf(defaults.mainReq))); - mainBlock = Boolean.parseBoolean((String) properties.computeIfAbsent("main-hand-block", a -> String.valueOf(defaults.mainBlock))); - mainFood = Boolean.parseBoolean((String) properties.computeIfAbsent("main-hand-food", a -> String.valueOf(defaults.mainFood))); - mainUsable = Boolean.parseBoolean((String) properties.computeIfAbsent("main-hand-usable", a -> String.valueOf(defaults.mainUsable))); - try { - mainWhitelist = new Gson().fromJson((String) - properties.computeIfAbsent("main-hand-whitelist", a -> gson.toJson(defaults.mainWhitelist)), listType); - } catch (JsonSyntaxException ignore) {} - try { - mainBlacklist = new Gson().fromJson((String) - properties.computeIfAbsent("main-hand-blacklist", a -> gson.toJson(defaults.mainBlacklist)), listType); - } catch (JsonSyntaxException ignore) {} - offReq = HandRequirement.get((String) properties.computeIfAbsent("off-hand-requirement", a -> String.valueOf(defaults.offReq))); - offBlock = Boolean.parseBoolean((String) properties.computeIfAbsent("off-hand-block", a -> String.valueOf(defaults.offBlock))); - offFood = Boolean.parseBoolean((String) properties.computeIfAbsent("off-hand-food", a -> String.valueOf(defaults.offFood))); - offUsable = Boolean.parseBoolean((String) properties.computeIfAbsent("off-hand-usable", a -> String.valueOf(defaults.offUsable))); - try { - offWhitelist = new Gson().fromJson((String) - properties.computeIfAbsent("off-hand-whitelist", a -> gson.toJson(defaults.offWhitelist)), listType); - } catch (JsonSyntaxException ignore) {} - try { - offBlacklist = new Gson().fromJson((String) - properties.computeIfAbsent("off-hand-blacklist", a -> gson.toJson(defaults.offBlacklist)), listType); - } catch (JsonSyntaxException ignore) {} - } - } catch (Exception e) { - Sit.LOGGER.info("ERROR LOADING CONFIG - PLEASE REPORT WITH THE ERROR LOG"); - e.printStackTrace(); - } - } - public static String lang(String key, Object... args) { - return Utl.lang("config."+key, args).getString(); - } - public static void save() { - try (var file = Files.newBufferedWriter(configFile().toPath(), StandardCharsets.UTF_8)) { - Gson gson = new GsonBuilder().disableHtmlEscaping().create(); - file.write("# Sit! Config\n"); - file.write("\nversion="+defaults.version); - file.write("\n# all available languages: en_us, ru_ru, es_es"); - file.write("\nlang=" + lang); - file.write("\n\n# "+lang("general.keep_active.description")); - file.write("\nkeep-active=" + keepActive); - file.write("\n# "+lang("general.sit_while_seated.description")); - file.write("\nsit-while-seated=" + sitWhileSeated); - file.write("\n# "+lang("general.sittable.description")); - file.write("\nstairs=" + stairsOn); - file.write("\nslabs=" + slabsOn); - file.write("\ncarpets=" + carpetsOn); - file.write("\nfull-blocks=" + fullBlocksOn); - file.write("\ncustom=" + customOn); - file.write("\n# "+ Utl.lang("config."+ - "general.sittable_blocks.description") - .append("\n# ").append(lang("example",Utl.Assets.CUSTOM_BLOCKS)) - .append("\n# ").append(lang("general.sittable_blocks.description.2")) - .append("\n# ").append(lang("general.sittable_blocks.description.3")) - .append("\n# ").append(lang("general.sittable_blocks.description.4")) - .append("\n# ").append(lang("general.sittable_blocks.description.5")) - .append("\n# ").append(lang("general.sittable_blocks.description.6")).getString()); - file.write("\ncustom-blocks="+gson.toJson(customBlocks)); - file.write("\n\n# "+lang("hand")); - file.write("\n# "+ Utl.lang("config."+ - "hand.requirement.description") - .append("\n# ").append(lang("hand.requirement.description.2",HandRequirement.empty)) - .append("\n# ").append(lang("hand.requirement.description.3",HandRequirement.restrictive)) - .append("\n# ").append(lang("hand.requirement.description.4",HandRequirement.none)).getString()); - file.write("\n# "+lang("hand.requirement.options",Utl.Assets.REQUIREMENT_OPTIONS)); - file.write("\nhand.main.requirement=" + mainReq); - file.write("\n#"); - file.write("\nhand.off.requirement=" + offReq); - - file.write("\n\n# "+lang("hand.restriction")); - file.write("\n# "+lang("hand.restriction.description")); - file.write("\nhand.main.block=" + mainBlock); - file.write("\nhand.main.food=" + mainFood); - file.write("\nhand.main.usable=" + mainUsable); - file.write("\n#"); - file.write("\nhand.off.block=" + offBlock); - file.write("\nhand.off.food=" + offFood); - file.write("\nhand.off.usable=" + offUsable); - - file.write("\n\n# "+lang("hand.restriction.list")); - file.write("\n# "+lang("hand.restriction.list.description")); - file.write("\n# "+lang("example",Utl.Assets.LIST)); - file.write("\nhand.main.whitelist="+gson.toJson(mainWhitelist)); - file.write("\nhand.main.blacklist="+gson.toJson(mainBlacklist)); - file.write("\n#"); - file.write("\nhand.off.whitelist="+gson.toJson(offWhitelist)); - file.write("\nhand.off.blacklist="+gson.toJson(offBlacklist)); - - // send packets to update the settings on the server - SitClient.sendSettingsPackets(); - } catch (Exception e) { - e.printStackTrace(); - } - } - public static class defaults { - public static double version = 1.1; - public static String lang = "en_us"; - public static boolean keepActive = true; - public static boolean sitWhileSeated = true; - public static boolean stairsOn = true; - public static boolean slabsOn = true; - public static boolean carpetsOn = true; - public static boolean fullBlocksOn = false; - public static boolean customOn = false; - public static List customBlocks = List.of("minecraft:campfire|.46|1|lit=false","minecraft:soul_campfire|.46|1|lit=false,waterlogged=false"); - public static HandRequirement mainReq = HandRequirement.empty; - public static boolean mainBlock = false; - public static boolean mainFood = false; - public static boolean mainUsable = false; - public static List mainWhitelist = new ArrayList<>(); - public static List mainBlacklist = new ArrayList<>(); - public static HandRequirement offReq = HandRequirement.restrictive; - public static boolean offBlock = true; - public static boolean offFood = false; - public static boolean offUsable = true; - public static List offWhitelist = List.of("minecraft:torch","minecraft:soul_torch","minecraft:redstone_torch"); - public static List offBlacklist = new ArrayList<>(); - } -} diff --git a/src/main/java/one/oth3r/sit/file/CustomBlock.java b/src/main/java/one/oth3r/sit/file/CustomBlock.java new file mode 100644 index 0000000..6167bed --- /dev/null +++ b/src/main/java/one/oth3r/sit/file/CustomBlock.java @@ -0,0 +1,92 @@ +package one.oth3r.sit.file; + +import com.google.gson.annotations.SerializedName; +import net.minecraft.block.BlockState; +import net.minecraft.registry.Registries; +import net.minecraft.registry.tag.TagKey; +import net.minecraft.util.Identifier; +import one.oth3r.sit.utl.Utl; + +import java.util.ArrayList; + +public class CustomBlock { + + @SerializedName("block-ids") + private ArrayList blockIds = new ArrayList<>(); + @SerializedName("block-tags") + private ArrayList blockTags = new ArrayList<>(); + @SerializedName("blockstates") + private ArrayList blockStates = new ArrayList<>(); + @SerializedName("sittingHeight") + private Double sittingHeight = 0.5; + + public CustomBlock() {} + + public CustomBlock(ArrayList blockIds, ArrayList blockTags, ArrayList blockStates, Double sittingHeight) { + this.blockIds = blockIds; + this.blockTags = blockTags; + this.blockStates = blockStates; + this.sittingHeight = sittingHeight; + } + + public ArrayList getBlockIds() { + return blockIds; + } + + public ArrayList getBlockTags() { + return blockTags; + } + + public ArrayList getBlockStates() { + return blockStates; + } + + public Double getSittingHeight() { + return sittingHeight; + } + + /** + * checks if the blockstate matches the CustomBlock params + * @param blockState the blockState to check + * @return if the blockstate is allowed by the CustomBlock rules + */ + public boolean isValid(BlockState blockState) { + boolean blockType = checkBlockType(blockState); + if (!blockType) return false; + + // now check if the state is one of the acceptable states + for (String state : blockStates) { + // if there is a NOT (!) blockstate, and it is contained in the block, return false + if (state.startsWith("!") && blockState.toString().contains(state.substring(1))) return false; + // else check if the blockstate matches, if not return false + else if (!blockState.toString().contains(state)) return false; + } + + // if here, all passes have passed + return true; + } + + /** + * returns if the block is the correct type or not + * @param blockState the blockstate to check + * @return if the type of block is matching the CustomBlock rules (e.g. if it is wood, ect.) + */ + private boolean checkBlockType(BlockState blockState) { + // for all the entered blocks + for (String id : blockIds) { + // if there is a match for the NOT(!) blocks, return false immediately + if (id.startsWith("!") && id.substring(1).equals(Utl.getBlockID(blockState))) return false; + // if there is a match for the block, return true immediately + if (id.equalsIgnoreCase(Utl.getBlockID(blockState))) return true; + } + // for all the entered tags + for (String tag : blockTags) { + // substring to remove # and if needed, ! + // if there is a math for the NOT(!) tag, return false + if (tag.startsWith("!") && blockState.isIn(TagKey.of(Registries.BLOCK.getKey(), Identifier.of(tag.substring(2))))) return false; + // if there is a match, return true + if (blockState.isIn(TagKey.of(Registries.BLOCK.getKey(), Identifier.of(tag.substring(1))))) return true; + } + return false; + } +} diff --git a/src/main/java/one/oth3r/sit/file/Data.java b/src/main/java/one/oth3r/sit/file/Data.java new file mode 100644 index 0000000..fb4569a --- /dev/null +++ b/src/main/java/one/oth3r/sit/file/Data.java @@ -0,0 +1,102 @@ +package one.oth3r.sit.file; + +import net.minecraft.entity.decoration.DisplayEntity; +import net.minecraft.server.network.ServerPlayerEntity; + +import java.util.HashMap; + +public class Data { + /** + * Sit! config file + */ + private static ServerConfig serverConfig = new ServerConfig(); + + public static ServerConfig getServerConfig() { + return new ServerConfig(serverConfig); + } + + public static void setServerConfig(ServerConfig newServerConfig) { + serverConfig = new ServerConfig(newServerConfig); + } + + /** + * The default hand config for all new players + */ + private static HandConfig handConfig = new HandConfig(); + + public static HandConfig getHandConfig() { + return new HandConfig(handConfig); + } + + public static void setHandConfig(HandConfig newHandConfig) { + handConfig = new HandConfig(newHandConfig); + } + + /** + * the hand config stored per player on the server + */ + private static final HashMap playerSettings = new HashMap<>(); + + public static void clearPlayerSettings() { + playerSettings.clear(); + } + + public static void setPlayerSetting(ServerPlayerEntity player, HandConfig config) { + playerSettings.put(player, config); + } + + public static void removePlayerSetting(ServerPlayerEntity player) { + playerSettings.remove(player); + } + + public static HandConfig getPlayerSetting(ServerPlayerEntity player) { + return playerSettings.getOrDefault(player,handConfig); + } + + /** + * a list of every Sit! entity in the server, bound to the player + */ + private static final HashMap sitEntities = new HashMap<>(); + + public static void addSitEntity(ServerPlayerEntity player, DisplayEntity.TextDisplayEntity entity) { + sitEntities.put(player, entity); + } + + public static void removeSitEntity(DisplayEntity.TextDisplayEntity entity) { + sitEntities.values().remove(entity); + } + + public static DisplayEntity.TextDisplayEntity getSitEntity(ServerPlayerEntity player) { + return sitEntities.get(player); + } + + public static HashMap getSitEntities() { + return new HashMap<>(sitEntities); + } + + /** + * a list of players who just joined, to check if they are mounted to a Sit! entity + */ + private static final HashMap checkPlayers = new HashMap<>(); + + public static void setCheckPlayer(ServerPlayerEntity player, Integer time) { + checkPlayers.put(player, time); + } + + public static void removeCheckPlayer(ServerPlayerEntity player) { + checkPlayers.remove(player); + } + + public static HashMap getCheckPlayers() { + return new HashMap<>(checkPlayers); + } + + /** + * loads all config files to memory + * @param tryLegacy try to load the legacy file, usually only used on server startup + */ + public static void loadFiles(boolean tryLegacy) { + ServerConfig.load(tryLegacy); + HandConfig.load(); + } +} diff --git a/src/main/java/one/oth3r/sit/file/HandConfig.java b/src/main/java/one/oth3r/sit/file/HandConfig.java new file mode 100644 index 0000000..cd3dd25 --- /dev/null +++ b/src/main/java/one/oth3r/sit/file/HandConfig.java @@ -0,0 +1,97 @@ +package one.oth3r.sit.file; + +import com.google.gson.annotations.SerializedName; +import net.minecraft.util.Hand; +import one.oth3r.sit.Sit; +import one.oth3r.sit.utl.Utl; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.ArrayList; + +public class HandConfig { + + @SerializedName("version") + private Double version = 1.0; + @SerializedName("hand-sitting") + private boolean handSitting = true; + @SerializedName("main-hand") + private HandSetting mainHand = new HandSetting(HandSetting.SittingRequirement.EMPTY, new HandSetting.Filter()); + @SerializedName("off-hand") + private HandSetting offHand = new HandSetting(HandSetting.SittingRequirement.FILTER, + new HandSetting.Filter(false,true,false,new ArrayList<>(),new ArrayList<>())); // todo fill out some fox examples sake + + public HandConfig() {} + + public HandConfig(double version, boolean handSitting, HandSetting mainHand, HandSetting offHand) { + this.version = version; + this.handSitting = handSitting; + this.mainHand = mainHand; + this.offHand = offHand; + } + + public HandConfig(HandConfig handConfig) { + this.version = handConfig.version; + this.handSitting = handConfig.handSitting; + this.mainHand = handConfig.mainHand; + this.offHand = handConfig.offHand; + } + + public Double getVersion() { + return version; + } + + public boolean canSitWithHand() { + return handSitting; + } + + public HandSetting getHand(Hand handType) { + return handType.equals(Hand.MAIN_HAND) ? mainHand : offHand; + } + + public HandSetting getMainHand() { + return mainHand; + } + + public HandSetting getOffHand() { + return offHand; + } + + public static File getFile() { + return new File(Sit.CONFIG_DIR+"hand-config.json"); + } + + /** + * loads the Config file to Data + */ + public static void load() { + + File file = getFile(); + if (!file.exists()) save(); + // try reading the file + try (BufferedReader reader = Files.newBufferedReader(file.toPath(), StandardCharsets.UTF_8)) { + Updater.HandConfigFile.run(reader); + } catch (Exception e) { + Sit.LOGGER.error(String.format("ERROR LOADING '%s`: %s", file.getName(),e.getMessage())); + } + // save after loading + save(); + } + + /** + * saves Data.config to config.json + */ + public static void save() { + if (!getFile().exists()) { + Sit.LOGGER.info(String.format("Creating new `%s`", getFile().getName())); + } + try (BufferedWriter writer = Files.newBufferedWriter(getFile().toPath(), StandardCharsets.UTF_8)) { + writer.write(Utl.getGson().toJson(Data.getHandConfig())); + } catch (Exception e) { + Sit.LOGGER.error(String.format("ERROR SAVING '%s`: %s", getFile().getName(), e.getMessage())); + } + } +} diff --git a/src/main/java/one/oth3r/sit/file/HandSetting.java b/src/main/java/one/oth3r/sit/file/HandSetting.java new file mode 100644 index 0000000..61e1a2e --- /dev/null +++ b/src/main/java/one/oth3r/sit/file/HandSetting.java @@ -0,0 +1,79 @@ +package one.oth3r.sit.file; + +import com.google.gson.annotations.SerializedName; + +import java.util.ArrayList; + +public class HandSetting { + + public boolean mainBlock; + @SerializedName("requirement") + private SittingRequirement sittingRequirement = SittingRequirement.NONE; + @SerializedName("filter") + private Filter filter = new Filter(); + + public HandSetting() {} + + public HandSetting(SittingRequirement sittingRequirement, Filter filter) { + this.sittingRequirement = sittingRequirement; + this.filter = filter; + } + + public SittingRequirement getSittingRequirement() { + return sittingRequirement; + } + + public Filter getFilter() { + return filter; + } + + public enum SittingRequirement { + NONE, + FILTER, + EMPTY + } + + public static class Filter { + + @SerializedName("block") + private boolean block = false; + @SerializedName("food") + private boolean food = false; + @SerializedName("usable") + private boolean usable = false; + @SerializedName("custom-items") + private ArrayList customItems = new ArrayList<>(); + @SerializedName("custom-tags") + private ArrayList customTags = new ArrayList<>(); + + public Filter() {} + + public Filter(boolean block, boolean food, boolean usable, ArrayList customItems, ArrayList customTags) { + this.block = block; + this.food = food; + this.usable = usable; + this.customItems = customItems; + this.customTags = customTags; + } + + public boolean isBlock() { + return block; + } + + public boolean isFood() { + return food; + } + + public boolean isUsable() { + return usable; + } + + public ArrayList getCustomItems() { + return customItems; + } + + public ArrayList getCustomTags() { + return customTags; + } + } +} diff --git a/src/main/java/one/oth3r/sit/LangReader.java b/src/main/java/one/oth3r/sit/file/LangReader.java similarity index 85% rename from src/main/java/one/oth3r/sit/LangReader.java rename to src/main/java/one/oth3r/sit/file/LangReader.java index 2f46105..7c3b913 100644 --- a/src/main/java/one/oth3r/sit/LangReader.java +++ b/src/main/java/one/oth3r/sit/file/LangReader.java @@ -1,10 +1,10 @@ -package one.oth3r.sit; +package one.oth3r.sit.file; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import net.minecraft.text.MutableText; import net.minecraft.text.Text; -import one.oth3r.sit.file.Config; +import one.oth3r.sit.Sit; import java.io.InputStream; import java.io.InputStreamReader; @@ -75,20 +75,30 @@ public class LangReader { public static LangReader of(String translationKey, Object... placeholders) { return new LangReader(translationKey, placeholders); } + public static void loadLanguageFile() { + ClassLoader classLoader = Sit.class.getClassLoader(); try { - ClassLoader classLoader = Sit.class.getClassLoader(); - InputStream inputStream = classLoader.getResourceAsStream("assets/sit/lang/"+ Config.lang+".json"); + InputStream inputStream = classLoader.getResourceAsStream("assets/sit/lang/"+ Data.getServerConfig().getLang() +".json"); + + // if the input stream is null, the language file wasn't found if (inputStream == null) { - inputStream = classLoader.getResourceAsStream("assets/sit/lang/"+ Config.defaults.lang+".json"); - Config.lang = Config.defaults.lang; + // try loading the default language file + inputStream = classLoader.getResourceAsStream("assets/sit/lang/"+ new ServerConfig().getLang() +".json"); + //todo error message and reset back to EN_US } - if (inputStream == null) throw new IllegalArgumentException("CANT LOAD THE LANGUAGE FILE. DIRECTIONHUD WILL BREAK."); + + // if the input stream is still null, throw an exception + if (inputStream == null) throw new IllegalArgumentException("UNABLE TO LOAD THE LANGUAGE FILE."); + Type type = new TypeToken>(){}.getType(); Reader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8); languageMap.putAll(new Gson().fromJson(reader, type)); + + // close the input stream + inputStream.close(); } catch (Exception e) { - e.printStackTrace(); + Sit.LOGGER.error(e.getMessage()); } } public static String getLanguageValue(String key) { diff --git a/src/main/java/one/oth3r/sit/file/ServerConfig.java b/src/main/java/one/oth3r/sit/file/ServerConfig.java new file mode 100644 index 0000000..cd7f95a --- /dev/null +++ b/src/main/java/one/oth3r/sit/file/ServerConfig.java @@ -0,0 +1,177 @@ +package one.oth3r.sit.file; + +import com.google.gson.annotations.SerializedName; +import one.oth3r.sit.Sit; +import one.oth3r.sit.utl.Utl; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; + +public class ServerConfig { + + @SerializedName("version") + private Double version = 2.0; + @SerializedName("lang") + private String lang = "en_us"; + @SerializedName("keep-active") + private Boolean keepActive = true; + @SerializedName("sit-while-seated") + private Boolean sitWhileSeated = false; + @SerializedName("preset-blocks") + private PresetBlocks presetBlocks = new PresetBlocks(); + @SerializedName("custom-enabled") + private Boolean customEnabled = false; + @SerializedName("custom-blocks") + private ArrayList customBlocks = new ArrayList<>(); + @SerializedName("blacklisted-blocks") + private ArrayList blacklistedBlocks = new ArrayList<>(); + + public ServerConfig() {} + + public ServerConfig(ServerConfig serverConfig) { + this.version = serverConfig.version; + this.lang = serverConfig.lang; + this.keepActive = serverConfig.keepActive; + this.sitWhileSeated = serverConfig.sitWhileSeated; + this.presetBlocks = serverConfig.presetBlocks; + this.customEnabled = serverConfig.customEnabled; + this.customBlocks = serverConfig.customBlocks; + this.blacklistedBlocks = serverConfig.blacklistedBlocks; + } + + public ServerConfig(Double version, String lang, boolean keepActive, boolean sitWhileSeated, PresetBlocks presetBlocks, boolean customEnabled, ArrayList customBlocks, ArrayList blacklistedBlocks) { + this.version = version; + this.lang = lang; + this.keepActive = keepActive; + this.sitWhileSeated = sitWhileSeated; + this.presetBlocks = presetBlocks; + this.customEnabled = customEnabled; + this.customBlocks = customBlocks; + this.blacklistedBlocks = blacklistedBlocks; + } + + public Double getVersion() { + return version; + } + + public String getLang() { + return lang; + } + + public boolean isKeepActive() { + return keepActive; + } + + public boolean isSitWhileSeated() { + return sitWhileSeated; + } + + public PresetBlocks getPresetBlocks() { + return presetBlocks; + } + + public Boolean isCustomEnabled() { + return customEnabled; + } + + public ArrayList getCustomBlocks() { + return customBlocks; + } + + public ArrayList getBlacklistedBlocks() { + return blacklistedBlocks; + } + + public static class PresetBlocks { + + @SerializedName("stairs") + private boolean stairs = true; + @SerializedName("slabs") + private boolean slabs = true; + @SerializedName("carpets") + private boolean carpets = true; + @SerializedName("full-blocks") + private boolean fullBlocks = false; + + public PresetBlocks() {} + + public PresetBlocks(boolean stairs, boolean slabs, boolean carpets, boolean fullBlocks) { + this.stairs = stairs; + this.slabs = slabs; + this.carpets = carpets; + this.fullBlocks = fullBlocks; + } + + public boolean isStairs() { + return stairs; + } + + public boolean isSlabs() { + return slabs; + } + + public boolean isCarpets() { + return carpets; + } + + public boolean isFullBlocks() { + return fullBlocks; + } + } + + + + public static File getFile() { + return new File(Sit.CONFIG_DIR+"server-config.json"); + } + + /** + * loads the directionhud Config file to Data.config + */ + public static void load(boolean tryLegacy) { + + File file = getFile(); + if (!file.exists()) { + // try to make the config directory + try { + Files.createDirectories(Paths.get(Sit.CONFIG_DIR)); + } catch (Exception e) { + Sit.LOGGER.error("Failed to create config directory. Canceling all config loading..."); + return; + } + // if loading from legacy, try checking the old config directory for the file + if (tryLegacy && Updater.ServerConfigFile.Legacy.getLegacyFile().exists()) { + Sit.LOGGER.info("Updating Sit!.properties to sit!/config.json"); + Updater.ServerConfigFile.Legacy.run(); + } + save(); + } + + try (BufferedReader reader = Files.newBufferedReader(file.toPath(), StandardCharsets.UTF_8)) { + Updater.ServerConfigFile.run(reader); + } catch (Exception e) { + Sit.LOGGER.error(String.format("ERROR LOADING '%s`: %s", file.getName(),e.getMessage())); + } + // save after loading + save(); + } + + /** + * saves Data.config to config.json + */ + public static void save() { + if (!getFile().exists()) { + Sit.LOGGER.info(String.format("Creating new `%s`", getFile().getName())); + } + try (BufferedWriter writer = Files.newBufferedWriter(getFile().toPath(), StandardCharsets.UTF_8)) { + writer.write(Utl.getGson().toJson(Data.getServerConfig())); + } catch (Exception e) { + Sit.LOGGER.info(String.format("ERROR SAVING '%s`: %s", getFile().getName(), e.getMessage())); + } + } +} diff --git a/src/main/java/one/oth3r/sit/file/Updater.java b/src/main/java/one/oth3r/sit/file/Updater.java new file mode 100644 index 0000000..982f004 --- /dev/null +++ b/src/main/java/one/oth3r/sit/file/Updater.java @@ -0,0 +1,288 @@ +package one.oth3r.sit.file; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonSyntaxException; +import com.google.gson.reflect.TypeToken; +import net.minecraft.util.Hand; +import one.oth3r.sit.Sit; +import one.oth3r.sit.utl.Utl; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.lang.reflect.Type; +import java.nio.file.Files; +import java.util.*; + +public class Updater { + + public static class HandConfigFile { + + /** + * runs the updater from the file reader and sets the loaded settings when finished + * @param reader the file reader + * @throws NullPointerException if the file is null + */ + public static void run(BufferedReader reader) + throws NullPointerException { + // try to read the json + HandConfig handConfig; + try { + handConfig = Utl.getGson().fromJson(reader, HandConfig.class); + } catch (Exception e) { + throw new NullPointerException(); + } + + // throw null if the fileData is null or version is null + if (handConfig == null) throw new NullPointerException(); + + // get the file version + Double version = handConfig.getVersion(); + + // if there's no version, throw + if (version == null) throw new NullPointerException(); + + // update the config (using the non-null version) + handConfig = update(handConfig); + + // set the config in the mod data + Data.setHandConfig(handConfig); + } + + /** + * updates the file + */ + public static HandConfig update(HandConfig old) { + HandConfig serverConfig = new HandConfig(old); + return serverConfig; + } + } + + public static class ServerConfigFile { + public static final double VERSION = 1.0; + + /** + * runs the updater from the file reader and sets the loaded settings when finished + * @param reader the file reader + * @throws NullPointerException if the file is null + */ + public static void run(BufferedReader reader) + throws NullPointerException { + // try to read the json + ServerConfig serverConfig; + try { + serverConfig = Utl.getGson().fromJson(reader, ServerConfig.class); + } catch (Exception e) { + throw new NullPointerException(); + } + + // throw null if the fileData is null or version is null + if (serverConfig == null) throw new NullPointerException(); + + // get the file version + Double version = serverConfig.getVersion(); + + // if there's no version, throw + if (version == null) throw new NullPointerException(); + + // update the config (using the non-null version) + serverConfig = update(serverConfig); + + System.out.println("updated"); + System.out.println(serverConfig.getBlacklistedBlocks()); + + // set the config in the mod data + Data.setServerConfig(serverConfig); + } + + /** + * updates the file + */ + public static ServerConfig update(ServerConfig old) { + ServerConfig serverConfig = new ServerConfig(old); + return serverConfig; + } + + public static class Legacy { + + /** + * gets the legacy file, from the old directory for fabric, and the same one for spigot + */ + public static File getLegacyFile() { + // strip the new directory + return new File(Sit.CONFIG_DIR.substring(0,Sit.CONFIG_DIR.length()-5)+"Sit!.properties"); + } + + /** + * updates the old Sit!.properties to config.json + */ + public static void run() { + // shouldn't happen, only call if the file exists + File file = getLegacyFile(); + if (!file.exists()) return; + + // update to the new system + try (FileInputStream fileStream = new FileInputStream(file)) { + Properties properties = new Properties(); + properties.load(fileStream); + String ver = (String) properties.computeIfAbsent("version", a -> String.valueOf(VERSION)); + + // if the old version system (v1.0) remove "v" + if (ver.contains("v")) ver = ver.substring(1); + + loadVersion(properties,Double.parseDouble(ver)); + + } catch (Exception e) { + Sit.LOGGER.error("Error loading legacy config file: {}", e.getMessage()); + } + + // delete the old file + try { + Files.delete(file.toPath()); + Sit.LOGGER.info("Deleted " + file.getName()); + } catch (Exception e) { + Sit.LOGGER.error("Failed to delete the old Sit! config."); + } + } + + /** + * gets a list of custom blocks from the legacy way of entering custom sit blocks + */ + private static ArrayList getCustomBlocks(ArrayList fix) { + //eg. minecraft:campfire|.46|1|lit=false + ArrayList out = new ArrayList<>(); + for (String entry : fix) { + String[] split = entry.split("\\|"); + // skip if not the right size + if (split.length < 3 || split.length > 4) continue; + // if the other entries aren't correct, skip + if (!Utl.Num.isNum(split[2])) continue; + + // make the block states list if possible + ArrayList blockstates = new ArrayList<>(); + // if there are blockstates + if (split.length == 4) { + blockstates.addAll(Arrays.asList(split[3].split(","))); + } + + // add if everything is A-OK + out.add(new CustomBlock( + new ArrayList<>(Arrays.asList(split[0])), + new ArrayList<>(),blockstates,Double.parseDouble(split[1]))); + } + return out; + } + + private static ArrayList getFilterList(ArrayList whitelist, ArrayList blacklist) { + ArrayList out = new ArrayList<>(whitelist); + // add a ! in front of every entry of the blacklist + out.addAll(blacklist.stream().map(e -> "!"+e).toList()); + return out; + } + + public static void loadVersion(Properties properties, double version) { + try { + Gson gson = new GsonBuilder().disableHtmlEscaping().create(); + Type listType = new TypeToken>() {}.getType(); + ServerConfig defaultConfig = new ServerConfig(); + + // load the latest config + ServerConfig serverConfig = new ServerConfig( + 2.0, + (String) properties.computeIfAbsent("lang", a -> defaultConfig.getLang()), + Boolean.parseBoolean((String) properties.computeIfAbsent("keep-active", a -> String.valueOf(defaultConfig.isKeepActive()))), + Boolean.parseBoolean((String) properties.computeIfAbsent("sit-while-seated", a -> String.valueOf(defaultConfig.isSitWhileSeated()))), + new ServerConfig.PresetBlocks( + Boolean.parseBoolean((String) properties.computeIfAbsent("stairs", a -> String.valueOf(defaultConfig.getPresetBlocks().isStairs()))), + Boolean.parseBoolean((String) properties.computeIfAbsent("slabs", a -> String.valueOf(defaultConfig.getPresetBlocks().isSlabs()))), + Boolean.parseBoolean((String) properties.computeIfAbsent("carpets", a -> String.valueOf(defaultConfig.getPresetBlocks().isCarpets()))), + Boolean.parseBoolean((String) properties.computeIfAbsent("full-blocks", a -> String.valueOf(defaultConfig.getPresetBlocks().isFullBlocks()))) + ), + Boolean.parseBoolean((String) properties.computeIfAbsent("custom", a -> String.valueOf(defaultConfig.isCustomEnabled()))), + getCustomBlocks(new Gson().fromJson((String) + properties.computeIfAbsent("custom-blocks", a -> "[]"), listType)), + new ArrayList<>() + ); + + HandConfig defaultHandConfig = new HandConfig(); + + HandConfig handConfig = null; + try { + handConfig = new HandConfig( + 1.0, Boolean.parseBoolean((String) properties.computeIfAbsent("hand.sitting", a -> String.valueOf(defaultHandConfig.canSitWithHand()))), + new HandSetting( + Utl.Enum.get(properties.computeIfAbsent("hand.main.requirement", a -> String.valueOf(defaultHandConfig.getHand(Hand.MAIN_HAND).getSittingRequirement())),HandSetting.SittingRequirement.class,HandSetting.SittingRequirement.FILTER), + new HandSetting.Filter( + Boolean.parseBoolean((String) properties.computeIfAbsent("hand.main.block", a -> String.valueOf(defaultHandConfig.getHand(Hand.MAIN_HAND).getFilter().isBlock()))), + Boolean.parseBoolean((String) properties.computeIfAbsent("hand.main.food", a -> String.valueOf(defaultHandConfig.getHand(Hand.MAIN_HAND).getFilter().isFood()))), + Boolean.parseBoolean((String) properties.computeIfAbsent("hand.main.usable", a -> String.valueOf(defaultHandConfig.getHand(Hand.MAIN_HAND).getFilter().isUsable()))), + getFilterList( + new Gson().fromJson((String) properties.computeIfAbsent("hand.main.whitelist", a -> "[]"), listType), + new Gson().fromJson((String) properties.computeIfAbsent("hand.main.blacklist", a -> "[]"), listType) + ), + new ArrayList<>() + ) + ), + new HandSetting( + Utl.Enum.get(properties.computeIfAbsent("hand.off.requirement", a -> String.valueOf(defaultHandConfig.getHand(Hand.OFF_HAND).getSittingRequirement())),HandSetting.SittingRequirement.class,HandSetting.SittingRequirement.FILTER), + new HandSetting.Filter( + Boolean.parseBoolean((String) properties.computeIfAbsent("hand.off.block", a -> String.valueOf(defaultHandConfig.getHand(Hand.OFF_HAND).getFilter().isBlock()))), + Boolean.parseBoolean((String) properties.computeIfAbsent("hand.off.food", a -> String.valueOf(defaultHandConfig.getHand(Hand.OFF_HAND).getFilter().isFood()))), + Boolean.parseBoolean((String) properties.computeIfAbsent("hand.off.usable", a -> String.valueOf(defaultHandConfig.getHand(Hand.OFF_HAND).getFilter().isUsable()))), + getFilterList( + new Gson().fromJson((String) properties.computeIfAbsent("hand.off.whitelist", a -> "[]"), listType), + new Gson().fromJson((String) properties.computeIfAbsent("hand.off.blacklist", a -> "[]"), listType) + ), + new ArrayList<>() + ) + ) + ); + } catch (JsonSyntaxException ignored) {} + + // load an older version + if (version == 1.0) { + try { + handConfig = new HandConfig( + 1.0, defaultHandConfig.canSitWithHand(), + new HandSetting( + Utl.Enum.get(properties.computeIfAbsent("main-hand-requirement", a -> String.valueOf(defaultHandConfig.getHand(Hand.MAIN_HAND).getSittingRequirement())),HandSetting.SittingRequirement.class,HandSetting.SittingRequirement.FILTER), + new HandSetting.Filter( + Boolean.parseBoolean((String) properties.computeIfAbsent("main-hand-block", a -> String.valueOf(defaultHandConfig.getHand(Hand.MAIN_HAND).getFilter().isBlock()))), + Boolean.parseBoolean((String) properties.computeIfAbsent("main-hand-food", a -> String.valueOf(defaultHandConfig.getHand(Hand.MAIN_HAND).getFilter().isFood()))), + Boolean.parseBoolean((String) properties.computeIfAbsent("main-hand-usable", a -> String.valueOf(defaultHandConfig.getHand(Hand.MAIN_HAND).getFilter().isUsable()))), + getFilterList( + new Gson().fromJson((String) properties.computeIfAbsent("main-hand-whitelist", a -> "[]"), listType), + new Gson().fromJson((String) properties.computeIfAbsent("main-hand-blacklist", a -> "[]"), listType) + ), + new ArrayList<>() + ) + ), + new HandSetting( + Utl.Enum.get(properties.computeIfAbsent("off-hand-requirement", a -> String.valueOf(defaultHandConfig.getHand(Hand.OFF_HAND).getSittingRequirement())),HandSetting.SittingRequirement.class,HandSetting.SittingRequirement.FILTER), + new HandSetting.Filter( + Boolean.parseBoolean((String) properties.computeIfAbsent("off-hand-block", a -> String.valueOf(defaultHandConfig.getHand(Hand.OFF_HAND).getFilter().isBlock()))), + Boolean.parseBoolean((String) properties.computeIfAbsent("off-hand-food", a -> String.valueOf(defaultHandConfig.getHand(Hand.OFF_HAND).getFilter().isFood()))), + Boolean.parseBoolean((String) properties.computeIfAbsent("off-hand-usable", a -> String.valueOf(defaultHandConfig.getHand(Hand.OFF_HAND).getFilter().isUsable()))), + getFilterList( + new Gson().fromJson((String) properties.computeIfAbsent("off-hand-whitelist", a -> "[]"), listType), + new Gson().fromJson((String) properties.computeIfAbsent("off-hand-blacklist", a -> "[]"), listType) + ), + new ArrayList<>() + ) + ) + ); + } catch (JsonSyntaxException ignored) {} + } + + Data.setServerConfig(serverConfig); + Data.setHandConfig(handConfig); + ServerConfig.save(); + HandConfig.save(); + } catch (Exception e) { + Sit.LOGGER.error("Error loading legacy config: {}", e.getMessage()); + } + } + } + } +} diff --git a/src/main/java/one/oth3r/sit/mixin/TextDisplayDismountMixin.java b/src/main/java/one/oth3r/sit/mixin/TextDisplayDismountMixin.java new file mode 100644 index 0000000..3e0807b --- /dev/null +++ b/src/main/java/one/oth3r/sit/mixin/TextDisplayDismountMixin.java @@ -0,0 +1,74 @@ +package one.oth3r.sit.mixin; + +import net.minecraft.entity.*; +import net.minecraft.entity.decoration.DisplayEntity; +import net.minecraft.util.math.*; +import net.minecraft.world.World; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; + +@Mixin(DisplayEntity.TextDisplayEntity.class) +public class TextDisplayDismountMixin extends DisplayEntity { + + public TextDisplayDismountMixin(EntityType type, World world) { + super(type, world); + } + + @Override + public void refreshData(boolean shouldLerp, float lerpProgress) {} + + @Override + public Vec3d updatePassengerForDismount(LivingEntity passenger) { + int[][] offset = Dismounting.getDismountOffsets(Direction.NORTH); + // new array with another slot + int[][] dismountOffsets = new int[offset.length + 1][]; + // add an empty offset to the start of the array + dismountOffsets[0] = new int[]{0, 0}; + // copy the original elements into the new array starting from index 1 + System.arraycopy(offset, 0, dismountOffsets, 1, offset.length); + + BlockPos blockPos = this.getBlockPos(); + + for (EntityPose entityPose : passenger.getPoses()) { + Vec3d vec3d = getDismountPos(passenger, entityPose, dismountOffsets, blockPos); + + // check around the block above + if (vec3d == null) vec3d = getDismountPos(passenger, entityPose, dismountOffsets, blockPos.up()); + + if (vec3d != null) return vec3d; + + } + + return super.updatePassengerForDismount(passenger); + } + + /** + * searches around the BlockPos for a stable dismount spot using the dismountOffsets + * @param passenger the passenger to check + * @param entityPose the pose of the passenger to check + * @param dismountOffsets the positions to check around the BlockPos + * @param blockPos the BlockPos to check around + * @return the Vec3d to dismount at, null if not found + */ + @Unique + private @Nullable Vec3d getDismountPos(LivingEntity passenger, EntityPose entityPose, int[][] dismountOffsets, BlockPos blockPos) { + // iterate through all dismount offsets + for (int[] offset : dismountOffsets) { + BlockPos.Mutable mutable = new BlockPos.Mutable(); + mutable.set(blockPos.getX() + offset[0], blockPos.getY(), blockPos.getZ() + offset[1]); + + double dismountHeight = this.getWorld().getDismountHeight(mutable); + if (Dismounting.canDismountInBlock(dismountHeight)) { + Vec3d vec3d = Vec3d.ofCenter(mutable, dismountHeight); + + Box boundingBox = passenger.getBoundingBox(entityPose); + if (Dismounting.canPlaceEntityAt(this.getWorld(), passenger, boundingBox.offset(vec3d))) { + passenger.setPose(entityPose); + return vec3d; + } + } + } + return null; + } +} diff --git a/src/main/java/one/oth3r/sit/packet/CustomPayloads.java b/src/main/java/one/oth3r/sit/packet/SitPayloads.java similarity index 81% rename from src/main/java/one/oth3r/sit/packet/CustomPayloads.java rename to src/main/java/one/oth3r/sit/packet/SitPayloads.java index 7d1fea9..caae9cb 100644 --- a/src/main/java/one/oth3r/sit/packet/CustomPayloads.java +++ b/src/main/java/one/oth3r/sit/packet/SitPayloads.java @@ -7,10 +7,10 @@ import net.minecraft.network.packet.CustomPayload; import net.minecraft.util.Identifier; import one.oth3r.sit.Sit; -public class CustomPayloads { +public class SitPayloads { public record SettingsPayload(String value) implements CustomPayload { - public static final Id ID = new Id<>(Identifier.of(Sit.MOD_ID,"settings_v1.1")); + public static final Id ID = new Id<>(Identifier.of(Sit.MOD_ID,"settings_v2.0")); public static final PacketCodec CODEC = PacketCodecs.STRING.xmap(SettingsPayload::new, SettingsPayload::value).cast(); @@ -18,10 +18,5 @@ public class CustomPayloads { public Id getId() { return ID; } - - @Override - public String value() { - return value; - } } } diff --git a/src/main/java/one/oth3r/sit/screen/ModMenu.java b/src/main/java/one/oth3r/sit/screen/ModMenu.java new file mode 100644 index 0000000..264f20b --- /dev/null +++ b/src/main/java/one/oth3r/sit/screen/ModMenu.java @@ -0,0 +1,185 @@ +package one.oth3r.sit.screen; + +import com.terraformersmc.modmenu.api.ModMenuApi; + +public class ModMenu implements ModMenuApi { +// private static MutableText lang(String key, Object... args) { +// return Utl.lang("config."+key,args); +// } +// @Override +// public ConfigScreenFactory getModConfigScreenFactory() { +// return parent -> YetAnotherConfigLib.createBuilder().save(config::save) +// .title(Text.of("Sit!")) +// .category(ConfigCategory.createBuilder() +// .name(lang("category.general")) +// .option(Option.createBuilder() +// .name(lang("general.keep_active")) +// .description(OptionDescription.of(lang("general.keep_active.description"))) +// .binding(config.defaults.keepActive, () -> config.keepActive, n -> config.keepActive = n) +// .controller(opt -> BooleanControllerBuilder.create(opt).trueFalseFormatter()) +// .build()) +// .option(Option.createBuilder() +// .name(lang("general.sit_while_seated")) +// .description(OptionDescription.of(lang("general.sit_while_seated.description"))) +// .binding(config.defaults.sitWhileSeated, () -> config.sitWhileSeated, n -> config.sitWhileSeated = n) +// .controller(opt -> BooleanControllerBuilder.create(opt).trueFalseFormatter()) +// .build()) +// .group(OptionGroup.createBuilder() +// .name(lang("general.sittable")) +// .description(OptionDescription.of(lang("general.sittable.description"))) +// .option(Option.createBuilder() +// .name(lang("general.sittable.stairs")) +// .binding(config.defaults.stairsOn, () -> config.stairsOn, n -> config.stairsOn = n) +// .controller(opt -> BooleanControllerBuilder.create(opt).onOffFormatter()) +// .build()) +// .option(Option.createBuilder() +// .name(lang("general.sittable.slabs")) +// .binding(config.defaults.slabsOn, () -> config.slabsOn, n -> config.slabsOn = n) +// .controller(opt -> BooleanControllerBuilder.create(opt).onOffFormatter()) +// .build()) +// .option(Option.createBuilder() +// .name(lang("general.sittable.carpets")) +// .binding(config.defaults.carpetsOn, () -> config.carpetsOn, n -> config.carpetsOn = n) +// .controller(opt -> BooleanControllerBuilder.create(opt).onOffFormatter()) +// .build()) +// .option(Option.createBuilder() +// .name(lang("general.sittable.full_blocks")) +// .binding(config.defaults.fullBlocksOn, () -> config.fullBlocksOn, n -> config.fullBlocksOn = n) +// .controller(opt -> BooleanControllerBuilder.create(opt).onOffFormatter()) +// .build()) +// .option(Option.createBuilder() +// .name(lang("general.sittable.custom")) +// .description(OptionDescription.of(lang("general.sittable.custom.description"))) +// .binding(config.defaults.customOn, () -> config.customOn, n -> config.customOn = n) +// .controller(opt -> BooleanControllerBuilder.create(opt).onOffFormatter()) +// .build()) +// .build()) +// .group(ListOption.createBuilder() +// .name(lang("general.sittable_blocks")) +// .description(OptionDescription.of( +// lang("general.sittable_blocks.description") +// .append("\n\n").append(lang("example", +// Text.literal("\"") +// .append(Text.literal("minecraft:campfire").styled(style -> style.withColor(TextColor.fromFormatting(Formatting.AQUA)))) +// .append("|") +// .append(Text.literal("0.255").styled(style -> style.withColor(TextColor.fromFormatting(Formatting.RED)))) +// .append("|") +// .append(Text.literal("1").styled(style -> style.withColor(TextColor.fromFormatting(Formatting.GREEN)))) +// .append("|") +// .append(Text.literal("lit=false").styled(style -> style.withColor(TextColor.fromFormatting(Formatting.GOLD)))) +// .append("\"").styled(style -> style.withColor(TextColor.fromFormatting(Formatting.GRAY))))) +// .append("\n\n").append(lang("general.sittable_blocks.description.2").styled(style -> style.withColor(TextColor.fromFormatting(Formatting.AQUA)))) +// .append("\n").append(lang("general.sittable_blocks.description.3").styled(style -> style.withColor(TextColor.fromFormatting(Formatting.RED)))) +// .append("\n").append(lang("general.sittable_blocks.description.4").styled(style -> style.withColor(TextColor.fromFormatting(Formatting.GREEN)))) +// .append("\n").append(lang("general.sittable_blocks.description.5").styled(style -> style.withColor(TextColor.fromFormatting(Formatting.GOLD)))) +// .append("\n\n").append(lang("general.sittable_blocks.description.6").styled(style -> style.withColor(TextColor.fromFormatting(Formatting.YELLOW)))))) +// .binding(config.defaults.customBlocks, () -> config.customBlocks, n -> config.customBlocks = n) +// .controller(StringControllerBuilder::create) +// .initial("") +// .build()) +// .build()) +// .category(ConfigCategory.createBuilder() +// .name(lang("category.main_hand")) +// .option(Option.createBuilder() +// .name(lang("hand.requirement")) +// .description(OptionDescription.of(lang("hand.requirement.description") +// .append("\n\n").append(lang("hand.requirement.description.2",lang("hand.requirement.empty")).styled(style -> style.withColor(TextColor.fromFormatting(Formatting.AQUA)))) +// .append("\n").append(lang("hand.requirement.description.3",lang("hand.requirement.restrictive")).styled(style -> style.withColor(TextColor.fromFormatting(Formatting.GREEN)))) +// .append("\n").append(lang("hand.requirement.description.4",lang("hand.requirement.none")).styled(style -> style.withColor(TextColor.fromFormatting(Formatting.RED)))))) +// .binding(config.defaults.mainReq, () -> config.mainReq, n -> config.mainReq = n) +// .controller(opt -> EnumControllerBuilder.create(opt).enumClass(config.HandRequirement.class) +// .formatValue(v -> lang("hand.requirement."+v.toString()))) +// .build()) +// .group(OptionGroup.createBuilder() +// .name(lang("hand.restriction")) +// .description(OptionDescription.of(lang("hand.restriction.description"))) +// .option(Option.createBuilder() +// .name(lang("hand.restriction.blocks")) +// .binding(config.defaults.mainBlock,()-> config.mainBlock,n -> config.mainBlock = n) +// .controller(opt -> BooleanControllerBuilder.create(opt).trueFalseFormatter()) +// .build()) +// .option(Option.createBuilder() +// .name(lang("hand.restriction.food")) +// .binding(config.defaults.mainFood,()-> config.mainFood,n -> config.mainFood = n) +// .controller(opt -> BooleanControllerBuilder.create(opt).trueFalseFormatter()) +// .build()) +// .option(Option.createBuilder() +// .name(lang("hand.restriction.usable")) +// .description(OptionDescription.of(lang("hand.restriction.usable.description"))) +// .binding(config.defaults.mainUsable,()-> config.mainUsable,n -> config.mainUsable = n) +// .controller(opt -> BooleanControllerBuilder.create(opt).trueFalseFormatter()) +// .build()) +// .build()) +// .group(ListOption.createBuilder() +// .name(lang("hand.restriction.whitelist")) +// .description(OptionDescription.of(lang("hand.restriction.list.description") +// .append("\n\n").append(lang("example", +// Text.empty().append(Utl.Assets.LIST).styled(style -> style.withItalic(true).withColor(TextColor.fromFormatting(Formatting.GRAY))))))) +// .binding(config.defaults.mainWhitelist, () -> config.mainWhitelist, n -> config.mainWhitelist = n) +// .controller(StringControllerBuilder::create) +// .initial("") +// .build()) +// .group(ListOption.createBuilder() +// .name(lang("hand.restriction.blacklist")) +// .description(OptionDescription.of(lang("hand.restriction.list.description") +// .append("\n\n").append(lang("example", +// Text.empty().append(Utl.Assets.LIST).styled(style -> style.withItalic(true).withColor(TextColor.fromFormatting(Formatting.GRAY))))))) +// .binding(config.defaults.mainBlacklist, () -> config.mainBlacklist, n -> config.mainBlacklist = n) +// .controller(StringControllerBuilder::create) +// .initial("") +// .build()) +// .build()) +// .category(ConfigCategory.createBuilder() +// .name(lang("category.off_hand")) +// .option(Option.createBuilder() +// .name(lang("hand.requirement")) +// .description(OptionDescription.of(lang("hand.requirement.description") +// .append("\n\n").append(lang("hand.requirement.description.2",lang("hand.requirement.empty")).styled(style -> style.withColor(TextColor.fromFormatting(Formatting.AQUA)))) +// .append("\n").append(lang("hand.requirement.description.3",lang("hand.requirement.restrictive")).styled(style -> style.withColor(TextColor.fromFormatting(Formatting.GREEN)))) +// .append("\n").append(lang("hand.requirement.description.4",lang("hand.requirement.none")).styled(style -> style.withColor(TextColor.fromFormatting(Formatting.RED)))))) +// .binding(config.defaults.offReq, () -> config.offReq, n -> config.offReq = n) +// .controller(opt -> EnumControllerBuilder.create(opt).enumClass(config.HandRequirement.class) +// .formatValue(v -> lang("hand.requirement."+v.toString()))) +// .build()) +// .group(OptionGroup.createBuilder() +// .name(lang("hand.restriction")) +// .description(OptionDescription.of(lang("hand.restriction.description"))) +// .option(Option.createBuilder() +// .name(lang("hand.restriction.blocks")) +// .binding(config.defaults.offBlock,()-> config.offBlock,n -> config.offBlock = n) +// .controller(opt -> BooleanControllerBuilder.create(opt).trueFalseFormatter()) +// .build()) +// .option(Option.createBuilder() +// .name(lang("hand.restriction.food")) +// .binding(config.defaults.offFood,()-> config.offFood,n -> config.offFood = n) +// .controller(opt -> BooleanControllerBuilder.create(opt).trueFalseFormatter()) +// .build()) +// .option(Option.createBuilder() +// .name(lang("hand.restriction.usable")) +// .description(OptionDescription.of(lang("hand.restriction.usable.description"))) +// .binding(config.defaults.offUsable,()-> config.offUsable,n -> config.offUsable = n) +// .controller(opt -> BooleanControllerBuilder.create(opt).trueFalseFormatter()) +// .build()) +// .build()) +// .group(ListOption.createBuilder() +// .name(lang("hand.restriction.whitelist")) +// .description(OptionDescription.of(lang("hand.restriction.list.description") +// .append("\n\n").append(lang("example", +// Text.empty().append(Utl.Assets.LIST).styled(style -> style.withItalic(true).withColor(TextColor.fromFormatting(Formatting.GRAY))))))) +// .binding(config.defaults.offWhitelist, () -> config.offWhitelist, n -> config.offWhitelist = n) +// .controller(StringControllerBuilder::create) +// .initial("") +// .build()) +// .group(ListOption.createBuilder() +// .name(lang("hand.restriction.blacklist")) +// .description(OptionDescription.of(lang("hand.restriction.list.description") +// .append("\n\n").append(lang("example", +// Text.empty().append(Utl.Assets.LIST).styled(style -> style.withItalic(true).withColor(TextColor.fromFormatting(Formatting.GRAY))))))) +// .binding(config.defaults.offBlacklist, () -> config.offBlacklist, n -> config.offBlacklist = n) +// .controller(StringControllerBuilder::create) +// .initial("") +// .build()) +// .build()) +// .build().generateScreen(parent); +// } +} diff --git a/src/main/java/one/oth3r/sit/utl/Events.java b/src/main/java/one/oth3r/sit/utl/Events.java new file mode 100644 index 0000000..bae31ad --- /dev/null +++ b/src/main/java/one/oth3r/sit/utl/Events.java @@ -0,0 +1,69 @@ +package one.oth3r.sit.utl; + +import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; +import net.fabricmc.fabric.api.event.player.UseBlockCallback; +import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.util.ActionResult; +import one.oth3r.sit.Sit; +import one.oth3r.sit.command.SitCommand; +import one.oth3r.sit.file.Data; + +public class Events { + + public static void playerConnections() { + // PLAYER JOIN + ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> { + Data.setPlayerSetting(handler.player,Data.getHandConfig()); + Data.setCheckPlayer(handler.player, 5); + }); + + ServerPlayConnectionEvents.DISCONNECT.register((handler, server) -> { + // if keep is off, remove the entity + if (!Data.getServerConfig().isKeepActive()) { + Logic.removeEntity(handler.player); + } + Data.removePlayerSetting(handler.player); + }); + } + + public static void server() { + ServerLifecycleEvents.SERVER_STARTED.register(s -> { + Sit.server = s; + Sit.commandManager = s.getCommandManager(); + + // right click on block event + UseBlockCallback.EVENT.register((pl, world, hand, hitResult) -> { + // get the server player + ServerPlayerEntity player = Sit.server.getPlayerManager().getPlayer(pl.getUuid()); + + // make sure the player isn't null, and make sure they aren't in spectator + if (player == null || player.isSpectator()) return ActionResult.PASS; + + // consume if sitting, if not pass + return Logic.sit(player,hitResult.getBlockPos(),hitResult)? ActionResult.CONSUME : ActionResult.PASS; + }); + + }); + + ServerLifecycleEvents.SERVER_STOPPED.register(s -> { + // clear all player settings (singleplayer and such) + Data.clearPlayerSettings(); + }); + } + + public static void misc() { + // loop setup + ServerTickEvents.END_SERVER_TICK.register(minecraftServer -> minecraftServer.execute(LoopManager::tick)); + + // command setup + CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> SitCommand.register(dispatcher)); + } + + public static void registerCommon() { + playerConnections(); + server(); + } +} diff --git a/src/main/java/one/oth3r/sit/utl/Logic.java b/src/main/java/one/oth3r/sit/utl/Logic.java new file mode 100644 index 0000000..762f32b --- /dev/null +++ b/src/main/java/one/oth3r/sit/utl/Logic.java @@ -0,0 +1,117 @@ +package one.oth3r.sit.utl; + +import net.minecraft.block.*; +import net.minecraft.entity.decoration.DisplayEntity; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.Hand; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.math.BlockPos; +import one.oth3r.sit.file.Data; +import one.oth3r.sit.file.HandConfig; +import one.oth3r.sit.file.HandSetting; +import org.jetbrains.annotations.Nullable; + +public class Logic { + public static boolean sit(ServerPlayerEntity player, BlockPos blockPos, @Nullable BlockHitResult hitResult) { + // cant sit if crouching + if (player.isSneaking()) return false; + + // if hit result isnt null (check the hands of the player) & the player hand checker returns false (can't sit with the items in the hand), quit + if (hitResult != null) { + if (!checkHands(player)) return false; + } + + ServerWorld serverWorld = player.getServerWorld(); + BlockState blockState = serverWorld.getBlockState(blockPos); + + Double sitHeight = Utl.getSittingHeight(blockState,player,blockPos,hitResult); + + // if the sit height is null, its not a sittable block + if (sitHeight == null) return false; + + DisplayEntity.TextDisplayEntity entity = Utl.Entity.create(serverWorld,blockPos,sitHeight); + + if (!checkPlayerSitAbility(entity)) return false; + + Utl.Entity.spawnSit(player, entity); + + return true; + } + + /** + * checks the hands of the player and the items in each hand and sees if the player can sit down + */ + public static boolean checkHands(ServerPlayerEntity player) { + HandConfig handConfig = Data.getPlayerSetting(player); + // if can't sit with hand, false + if (!handConfig.canSitWithHand()) return false; + + boolean canSit = true; + + // for each hand + for (Hand hand : Hand.values()) { + // if they can't sit, no need to run extra code + if (!canSit) break; + + HandSetting handSetting = handConfig.getHand(hand); + switch (handSetting.getSittingRequirement()) { + case EMPTY -> canSit = player.getStackInHand(hand).isEmpty(); + case FILTER -> canSit = Utl.checkItem(handSetting.getFilter(), player.getStackInHand(hand)); + } + } + // return the output of the check + return canSit; + } + + /** + * removes the entity from the game, using the player + */ + public static void removeEntity(ServerPlayerEntity player) { + DisplayEntity.TextDisplayEntity entity = Data.getSitEntity(player); + // make sure the player has a sit entity bounded to them + if (entity == null) return; + + // remove the entity + Utl.Entity.remove(entity); + } + + /** + * checks if the player should still be sitting, e.g. the block was destroyed ect. + */ + public static void checkSittingValidity(ServerPlayerEntity player) { + DisplayEntity.TextDisplayEntity entity = Data.getSitEntity(player); + // make sure the player has a sit entity bounded to them + if (entity == null) return; + + // if the entity location isn't valid anymore, remove it + if (!Utl.Entity.isValid(player,entity)) { + removeEntity(player); + } + } + + /** + * checks if entity would cause the player to suffocate when sitting + * @param entity the entity + * @return true if there is no obstruction + */ + public static boolean checkPlayerSitAbility(DisplayEntity.TextDisplayEntity entity) { + // get the entity's block pos + BlockPos pos = Utl.Entity.getBlockPos(entity); + // get the poses to check above the block + BlockPos pos1 = new BlockPos(pos).add(0,1,0), pos2 = new BlockPos(pos).add(0,2,0); + // doesn't check 2 blocks above if not sitting above .80 of the block + if (pos.getY() > entity.getY() - .80) pos2 = pos2.add(0,-1,0); + + // check if both poses are obstructed or not + return Utl.isNotObstructed(entity.getWorld(),pos1) && Utl.isNotObstructed(entity.getWorld(),pos2); + } + + /** + * reloads the config files + */ + public static void reload() { + Data.loadFiles(false); + } + +} diff --git a/src/main/java/one/oth3r/sit/utl/LoopManager.java b/src/main/java/one/oth3r/sit/utl/LoopManager.java new file mode 100644 index 0000000..8d28fea --- /dev/null +++ b/src/main/java/one/oth3r/sit/utl/LoopManager.java @@ -0,0 +1,56 @@ +package one.oth3r.sit.utl; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.decoration.DisplayEntity; +import net.minecraft.server.network.ServerPlayerEntity; +import one.oth3r.sit.Sit; +import one.oth3r.sit.file.Data; + +import java.util.HashMap; + +public class LoopManager { + + private static int time = 0; + + public static void tick() { + time++; + if (time >= 5) { + time = 0; + + // check all sit entities to make sure their still valid + HashMap entities = Data.getSitEntities(); + for (ServerPlayerEntity player : entities.keySet()) { + DisplayEntity.TextDisplayEntity entity = entities.get(player); + + if (player.getVehicle() == null || !player.getVehicle().equals(entity)) { + Logic.removeEntity(player); + } else { + Logic.checkSittingValidity(player); + } + } + + // get the player's sit entity when they join + // todo make it so it updates the sitting height and pos based on the block so if it changed while offline it still works (or if stair changes shape) + HashMap checkPlayers = Data.getCheckPlayers(); + for (ServerPlayerEntity player : checkPlayers.keySet()) { + Integer time = checkPlayers.get(player); + // tick down or remove the player if at the end + time -= 1; + if (time <= 0) Data.removeCheckPlayer(player); + else Data.setCheckPlayer(player, time); + + if (player.getVehicle() != null) { + Entity entity = player.getVehicle(); + if (entity instanceof DisplayEntity.TextDisplayEntity tde && entity.getName().getString().equals(Sit.ENTITY_NAME)) { + // bind the entity to the player + Data.addSitEntity(player, tde); + // check if the player is still allowed to sit + Logic.checkSittingValidity(player); + // remove the player from the check + Data.removeCheckPlayer(player); + } + } + } + } + } +} diff --git a/src/main/java/one/oth3r/sit/utl/Utl.java b/src/main/java/one/oth3r/sit/utl/Utl.java new file mode 100644 index 0000000..e9f3ea0 --- /dev/null +++ b/src/main/java/one/oth3r/sit/utl/Utl.java @@ -0,0 +1,367 @@ +package one.oth3r.sit.utl; + +import com.google.gson.*; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import com.google.gson.stream.MalformedJsonException; +import net.minecraft.block.*; +import net.minecraft.block.enums.BlockHalf; +import net.minecraft.block.enums.SlabType; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.decoration.DisplayEntity; +import net.minecraft.item.BlockItem; +import net.minecraft.item.ItemStack; +import net.minecraft.registry.Registries; +import net.minecraft.registry.tag.TagKey; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.text.MutableText; +import net.minecraft.text.Text; +import net.minecraft.util.Identifier; +import net.minecraft.util.TypeFilter; +import net.minecraft.util.UseAction; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import one.oth3r.sit.Sit; +import one.oth3r.sit.file.*; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class Utl { + + /** + * check if a block is obstructed (no collision / custom list) + * @return true if not obstructed + */ + public static boolean isNotObstructed(World world, BlockPos blockPos) { + // get the block state at the blockPos + BlockState state = world.getBlockState(blockPos); + // make sure it doesn't have a collision + return state.getCollisionShape(world,blockPos).isEmpty(); + } + + public static class Num { + + public static boolean isInt(String string) { + try { + Integer.parseInt(string); + } catch (NumberFormatException nfe) { + return false; + } + return true; + } + + public static Integer toInt(String s) { + // return an int no matter what + try { + return Integer.parseInt(s); + } catch (NumberFormatException e) { + try { + return (int) Double.parseDouble(s); + } catch (NumberFormatException e2) { + return 0; + } + } + } + + public static boolean isNum(String s) { + // checks if int or a double + try { + Integer.parseInt(s); + return true; + } catch (NumberFormatException e1) { + try { + Double.parseDouble(s); + return true; + } catch (NumberFormatException e2) { + return false; + } + } + } + } + + public static final double HALF_BLOCK = 0.49; + public static final double CARPET = 0.05; + + /** + * checks if the provided itemstack is a valid one for the provided filter + * @param filter the filter + * @param itemStack itemstack to check + * @return if true, the item isn't filtered out + */ + public static boolean checkItem(HandSetting.Filter filter, ItemStack itemStack) { + // default to true if theres nothing + if (itemStack.isEmpty()) return true; + + String itemId = Registries.ITEM.getId(itemStack.getItem()).toString(); + // check the custom item ids + for (String id : filter.getCustomItems()) { + // if there is a match for the NOT(!) item, its filtered, false + if (id.startsWith("!") && id.substring(1).equalsIgnoreCase(itemId)) return false; + // if there is a match for the item, return true immediately + if (id.equalsIgnoreCase(itemId)) return true; + } + // check the custom item tags + for (String tag : filter.getCustomTags()) { + // substring to remove # and if needed, ! + // if there is a math for the NOT(!) tag, return false + if (tag.startsWith("!") && itemStack.isIn(TagKey.of(Registries.ITEM.getKey(), Identifier.of(tag.substring(2))))) return false; + // if there is a match, return true + if (itemStack.isIn(TagKey.of(Registries.ITEM.getKey(), Identifier.of(tag.substring(1))))) return true; + } + + // if none of the custom were met, try the default conditions + + // get the use actions for the filters + ArrayList food = new ArrayList<>(); + food.add(UseAction.EAT); + food.add(UseAction.DRINK); + ArrayList notUsable = new ArrayList<>(food); + notUsable.add(UseAction.NONE); + + // try the default conditions + if (filter.isBlock() && itemStack.getItem() instanceof BlockItem) return true; + if (filter.isFood() && food.contains(itemStack.getUseAction())) return true; + if (filter.isUsable() && !notUsable.contains(itemStack.getUseAction())) return true; + + // if nothing else is met, the item is filtered out + return false; + } + + /** + * get a block ID (namespace, minecraft:air) from a blockstate. (it is easier with a block, but we are mostly working with block states + * @return the block ID (minecraft:air) + */ + public static String getBlockID(BlockState blockState) { + return Registries.BLOCK.getId(blockState.getBlock()).toString(); + } + + /** + * gets the sitting height for the provided blockstate, via memory loaded config from Data + * @param blockState the state of the block + * @param player the player to + * @param blockPos the pos of the block + * @param hit nullable, for the player interaction check + * @return null if not a valid block + */ + public static Double getSittingHeight(BlockState blockState, ServerPlayerEntity player, BlockPos blockPos, @Nullable BlockHitResult hit) { + ServerConfig config = Data.getServerConfig(); + Block block = blockState.getBlock(); + + // only if custom is enabled + if (config.isCustomEnabled()) { + // if the block is on the blacklist, false + if (config.getBlacklistedBlocks().contains(getBlockID(blockState))) return null; + + for (CustomBlock customBlock : config.getCustomBlocks()) { + // if the block is valid, true + if (customBlock.isValid(blockState)) return customBlock.getSittingHeight(); + } + } + + // add the default block types and check for them + if (block instanceof StairsBlock + && config.getPresetBlocks().isStairs() + && blockState.get(StairsBlock.HALF) == BlockHalf.BOTTOM) return HALF_BLOCK; + if (config.getPresetBlocks().isSlabs() + && block instanceof SlabBlock + && blockState.get(SlabBlock.TYPE) == SlabType.BOTTOM) return HALF_BLOCK; + if (config.getPresetBlocks().isCarpets() + && block instanceof CarpetBlock) return CARPET; + if (config.getPresetBlocks().isFullBlocks() + // make sure the block is a full cube + && blockState.isFullCube(player.getWorld(),blockPos) + // make sure there isn't an action for the block IF the hit isn't null (manual command / no right click check) + && (hit == null || !blockState.onUse(player.getWorld(), player, hit).isAccepted())) return 1.0; + + // at the end, return false + return null; + } + + public static class Entity { + + /** + * checks if the entity's block is still there, & is valid + */ + public static boolean isValid(ServerPlayerEntity player, @NotNull DisplayEntity.TextDisplayEntity entity) { + BlockPos blockPos = getBlockPos(entity); + // get the blockstate + BlockState blockState = player.getWorld().getBlockState(blockPos); + // check if the block is still there & the block is a valid sit block (by checking if there is a sit height for the block) + return !blockState.isAir() && getSittingHeight(blockState,player,blockPos,null) != null; + } + + /** + * gets the bound block pos of the sit entity + */ + public static BlockPos getBlockPos(DisplayEntity.TextDisplayEntity entity) { + // get the block pos + BlockPos pos = new BlockPos(entity.getBlockX(),entity.getBlockY(),entity.getBlockZ()); + // if above the block, subtract 1 + if (isAboveBlockHeight(entity)) { + pos = pos.add(0,-1,0); + } + + return pos; + } + + /** + * using the entity's pitch, figure out if the player is above the block height or not + */ + public static boolean isAboveBlockHeight(DisplayEntity.TextDisplayEntity entity) { + return entity.getPitch() > 0; + } + + /** + * creates the sit entity from the pos & sit height provided + * @param world the world to make the entity in + * @param blockPos the pos of the entity + * @param sitHeight the height for the entity to be at + * @return the entity at the correct height and position + */ + public static DisplayEntity.TextDisplayEntity create(World world, BlockPos blockPos, double sitHeight) { + DisplayEntity.TextDisplayEntity entity = new DisplayEntity.TextDisplayEntity(EntityType.TEXT_DISPLAY,world); + + entity.setCustomName(Text.of(Sit.ENTITY_NAME)); + entity.setCustomNameVisible(false); + entity.setInvulnerable(true); + entity.setInvisible(true); + + entity.updatePositionAndAngles(blockPos.getX()+.5, blockPos.getY()+sitHeight, blockPos.getZ()+.5, 0, 0); + + // // 1.20.2 mounting pos change (shifts everything down by .25) + // double oneTwentyTwo = .25; + // entity.updatePositionAndAngles(entity.getX(),entity.getY()+oneTwentyTwo,entity.getZ(),0,0); + + // change pitch based on if player is sitting below block height or not (full block height only) + if (entity.getY() == blockPos.getY() + 1) entity.setPitch(90); // below + else entity.setPitch(-90); // above + + return entity; + } + + /** + * removes the entity from the entity map and world, dismounting any passengers + */ + public static void remove(DisplayEntity.TextDisplayEntity entity) { + // dismount everyone + entity.removeAllPassengers(); + // remove the entity + entity.setRemoved(net.minecraft.entity.Entity.RemovalReason.DISCARDED); + // remove the entity from the data set if exists + Data.removeSitEntity(entity); + } + + /** + * spawns the entity and make the player sit on it + */ + public static void spawnSit(ServerPlayerEntity player, DisplayEntity.TextDisplayEntity entity) { + player.getServerWorld().spawnEntity(entity); + player.startRiding(entity); + // add the entity to the list + Data.addSitEntity(player, entity); + } + + /** + * removes all sit entities loaded on the server + */ + public static void purge(ServerPlayerEntity player, boolean message) { + // todo test if it can purge an entity from a disconnected player or unloaded chunks + // get a list of sit entities + List list = player.getServerWorld() + .getEntitiesByType(TypeFilter.instanceOf(DisplayEntity.TextDisplayEntity.class), + entity -> entity.getName().getString().equals(Sit.ENTITY_NAME)); + + // remove each one + for (DisplayEntity.TextDisplayEntity entity : list) { + remove(entity); + } + + // send a message if needed + if (message) { + // todo maybe a count for the message for debuging + player.sendMessage(Utl.lang("msg.purged")); + } + } + } + + + + public static MutableText lang(String key, Object... args) { + if (Sit.client) return Text.translatable(key, args); + else return LangReader.of(key, args).getTxT(); + } + +// public static class ConfigExamples { +// public static final String CUSTOM_BLOCKS = "\"minecraft:campfire|0.255|1|lit=false\""; +// public static final String REQUIREMENT_OPTIONS = String.format("%s, %s, %s", +// configFile.HandRequirement.empty,configFile.HandRequirement.restrictive,configFile.HandRequirement.none); +// public static final String LIST = "\"minecraft:torch\""; +// } + + public static class Enum { + + public static > T get(Object enumString, Class enumType) { + return get(enumString,enumType,enumType.getEnumConstants()[0]); + } + /** + * gets an enum from a string without returning null + * @param enumString the string of the enum + * @param enumType the class of enums + * @param defaultEnum the enum to return if a match isn't found + * @return an enum, if there isn't a match, it returns the first enum + */ + public static > T get(Object enumString, Class enumType, T defaultEnum) { + T[] values = enumType.getEnumConstants(); + for (T all : values) { + // check if there is a match for any of the enum names + if (enumString.toString().equals(all.name())) return all; + } + // if there's no match return the first entry + return defaultEnum; + } + } + + /** + * gets a Gson with the LenientTypeAdapter + */ + public static Gson getGson() { + return new GsonBuilder() + .disableHtmlEscaping() + .setPrettyPrinting() + .registerTypeAdapterFactory(new LenientTypeAdapterFactory()) + .create(); + } + + /** + * the LenientTypeAdapter, doesn't throw anything when reading a weird JSON entry, good for human entered JSONs + */ + public static class LenientTypeAdapterFactory implements TypeAdapterFactory { + public TypeAdapter create(Gson gson, TypeToken type) { + final TypeAdapter delegate = gson.getDelegateAdapter(this, type); + + return new TypeAdapter<>() { + // normal writer + public void write(JsonWriter out, T value) throws IOException { + delegate.write(out, value); + } + // custom reader + public T read(JsonReader in) throws IOException { + try { + //Try to read value using default TypeAdapter + return delegate.read(in); + } catch (JsonSyntaxException | MalformedJsonException e) { + // don't throw anything if there's a weird JSON, just return null + in.skipValue(); + return null; + } + } + }; + } + } +} diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 95deb70..c0d2aee 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -22,7 +22,7 @@ "one.oth3r.sit.SitClient" ], "modmenu": [ - "one.oth3r.sit.ModMenu" + "one.oth3r.sit.screen.ModMenu" ] }, "depends": { @@ -34,5 +34,8 @@ "suggests": { "yet-another-config-lib": "*", "modmenu": "*" - } + }, + "mixins": [ + "sit!.mixins.json" + ] } \ No newline at end of file diff --git a/src/main/resources/sit!.mixins.json b/src/main/resources/sit!.mixins.json new file mode 100644 index 0000000..c55c295 --- /dev/null +++ b/src/main/resources/sit!.mixins.json @@ -0,0 +1,12 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "one.oth3r.sit.mixin", + "compatibilityLevel": "JAVA_17", + "mixins": [ + "TextDisplayDismountMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} \ No newline at end of file