diff --git a/.gitignore b/.gitignore index a233ec2..dc73e2c 100644 --- a/.gitignore +++ b/.gitignore @@ -37,4 +37,5 @@ run/ hs_err_*.log replay_*.log *.hprof -*.jfr \ No newline at end of file +*.jfr +/.env diff --git a/build.gradle b/build.gradle index 506104f..44ebae0 100644 --- a/build.gradle +++ b/build.gradle @@ -1,13 +1,16 @@ plugins { - id 'fabric-loom' version '1.6-SNAPSHOT' + id 'fabric-loom' version '1.8-SNAPSHOT' id 'maven-publish' + id 'com.modrinth.minotaur' version '2.+' + id 'net.darkhax.curseforgegradle' version '1.1.+' + id 'co.uzzu.dotenv.gradle' version '4.0.0' } version = project.mod_version group = project.maven_group base { - archivesName = project.archives_base_name + archivesName = project.file_name } repositories { @@ -15,6 +18,10 @@ repositories { maven { url "https://maven.isxander.dev/releases" } } +loom { + accessWidenerPath = file("src/main/resources/sit!.accesswidener") +} + dependencies { minecraft "com.mojang:minecraft:${project.minecraft_version}" mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" @@ -23,16 +30,21 @@ dependencies { modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" modImplementation "com.terraformersmc:modmenu:${project.modmenu_version}" - modImplementation ("dev.isxander:yet-another-config-lib:${project.yacl_version}") } processResources { - inputs.property "version", project.version - inputs.property "minecraft_version", project.minecraft_version + filteringCharset "UTF-8" + var replaceProperties = [ + version : project.version, + minecraft_version : minecraft_version, + min_minecraft_version : min_minecraft_version, + loader_version : loader_version + ] + + inputs.properties replaceProperties filesMatching("fabric.mod.json") { - expand "version": project.version, - "minecraft_version": project.minecraft_version + expand replaceProperties } } @@ -71,4 +83,35 @@ publishing { // The repositories here will be used for publishing your artifact, not for // retrieving dependencies. } +} + +import com.modrinth.minotaur.dependencies.ModDependency + +modrinth { + token = env.fetchOrNull('MODRINTH') + projectId = 'EsYqsGV4' + versionNumber = project.mod_version + versionName = "v${project.mod_version} [Fabric]" + versionType = "release" + uploadFile = remapJar + gameVersions = [project.minecraft_version] + loaders = ['fabric', 'quilt'] + dependencies = [ + new ModDependency('P7dR8mSH', 'required'), + new ModDependency('mOgUt4GM', 'optional') + ] + changelog = file('changelog.md').text +} + +import net.darkhax.curseforgegradle.TaskPublishCurseForge + +tasks.register('publishCurseForge', TaskPublishCurseForge) { + apiToken = env.fetchOrNull('CURSEFORGE') + + def mainFile = upload(892424, remapJar) + mainFile.changelog = file('changelog.md') + mainFile.displayName = "v${project.mod_version} [Fabric]" + mainFile.addModLoader("fabric", 'quilt') + mainFile.releaseType = "release" + mainFile.addEnvironment("client", "server") } \ No newline at end of file diff --git a/changelog.md b/changelog.md new file mode 100644 index 0000000..aef44e2 --- /dev/null +++ b/changelog.md @@ -0,0 +1,9 @@ +# v1.2.1 +Quick bugfix update. :) +### block interaction checker +now it actually works (based on config sadly) +* added `interaction-blocks` to the server config, any block added to this list will block sitting with the hand +### other changes +* added refreshes to the config files on startup and reload to write missing parts to disk +* bumped the `server-config.json` version +* fixed `usable` hand filter not working \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 93235ea..c08593e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,16 +4,16 @@ org.gradle.parallel=true # Fabric Properties # check these on https://fabricmc.net/develop +min_minecraft_version=1.20.1 minecraft_version=1.20.1 yarn_mappings=1.20.1+build.10 loader_version=0.14.24 # Mod Properties -mod_version=1.1.10+1.20.1 +mod_version=1.2.1+1.20.1 maven_group=one.oth3r -archives_base_name=sit! +file_name=sit! # Dependencies fabric_version=0.91.0+1.20.1 -modmenu_version=7.0.0 -yacl_version=3.4.1+1.20.1-fabric +modmenu_version=7.0.0 \ No newline at end of file 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 2e20aea..0000000 --- a/src/main/java/one/oth3r/sit/Events.java +++ /dev/null @@ -1,279 +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.entity.player.PlayerEntity; -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.HandSettings.HandType; -import one.oth3r.sit.file.Config; - -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(Utl.HandSettings.HandType.main,player.getMainHandStack()); - itemMap.put(Utl.HandSettings.HandType.off,player.getOffHandStack()); - // if sneaking cant sit - if (player.isSneaking()) return false; - // for both hands - for (HandType type: Utl.HandSettings.HandType.values()) { - ItemStack targetStack = itemMap.get(type); - // if req is empty and the item isn't empty, false - if (Utl.HandSettings.getReq(player,type).equals(Config.HandRequirement.empty) && !targetStack.isEmpty()) return false; - // if req is restrictive - if (Utl.HandSettings.getReq(player,type).equals(Config.HandRequirement.restrictive)) { - // if item is in blacklist, false - if (checkList(Utl.HandSettings.getList(player,type,"blacklist"),targetStack)) return false; - // if item is NOT in whitelist - if (!checkList(Utl.HandSettings.getList(player,type,"whitelist"),targetStack)) { - // if block is restricted and items is block, false, ect - if (Utl.HandSettings.getBool(player,type,"block") && (targetStack.getItem() instanceof BlockItem)) return false; - if (Utl.HandSettings.getBool(player,type,"food") && food.contains(targetStack.getUseAction())) return false; - if (Utl.HandSettings.getBool(player,type,"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.size() != 0) { - 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")); - } - } - } - entity.updatePositionAndAngles(entity.getX(),entity.getY(),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) entity.setPitch(90); // below - else entity.setPitch(-90); // above - } - public static boolean sit(ServerPlayerEntity player, BlockPos pos) { - // todo interactions entity to make the sitting hitbox? - World world = player.getWorld(); - 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 false; - entities.get(player).setRemoved(Entity.RemovalReason.DISCARDED); - entities.remove(player); - } - player.getServerWorld().spawnEntity(entity); - player.startRiding(entity); - entities.put(player,entity); - return true; - } - return false; - } - 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, Utl.HandSettings.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) -> { - if (!Config.handSitting) return ActionResult.PASS; - // make sure its a server player - if (pl.getClass() != ServerPlayerEntity.class) return ActionResult.PASS; - 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(); - // check the players hands - if (!checkLogic(player)) return ActionResult.PASS; - // make the player sit - boolean status = sit(player,pos); - // if sat, cancel / FAIL the use block event - if (status) 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 be74779..0000000 --- a/src/main/java/one/oth3r/sit/ModMenu.java +++ /dev/null @@ -1,229 +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; -import one.oth3r.sit.file.Config; - -public class ModMenu implements ModMenuApi { - - private static MutableText lang(String key) { - return Text.translatable("config.sit."+key); - } - - private static MutableText lang(String key, Object... args) { - return Text.translatable("config.sit."+key,args); - } - - @Override - public ConfigScreenFactory getModConfigScreenFactory() { - // return null if YACL isn't installed to not throw an error - if (!yaclCheck()) return screen -> null; - return parent -> YetAnotherConfigLib.createBuilder().save(() -> { - // save and load to get rid of bad data - Config.save(); - Config.load(); - }) - .title(Text.of("Sit!")) - .category(ConfigCategory.createBuilder() - .name(lang("category.general")) - .tooltip(lang("category.general.tooltip")) - .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()) - .option(Option.createBuilder() - .name(Text.of("Sitting with Hand")) - .description(OptionDescription.of(Text.of("Toggles the player's ability to sit with their hand."))) - .binding(Config.defaults.handSitting,()-> Config.handSitting, n -> Config.handSitting = 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("general.sittable_blocks.description_2", - 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.withItalic(true).withColor(TextColor.fromFormatting(Formatting.GRAY))))) - .append("\n\n").append(lang("general.sittable_blocks.description_4").styled(style -> style.withColor(TextColor.fromFormatting(Formatting.AQUA)))) - .append("\n").append(lang("general.sittable_blocks.description_5").styled(style -> style.withColor(TextColor.fromFormatting(Formatting.RED)))) - .append("\n").append(lang("general.sittable_blocks.description_6").styled(style -> style.withColor(TextColor.fromFormatting(Formatting.GREEN)))) - .append("\n").append(lang("general.sittable_blocks.description_7").styled(style -> style.withColor(TextColor.fromFormatting(Formatting.GOLD)))) - .append("\n\n").append(lang("general.sittable_blocks.description_8").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")) - .tooltip(lang("category.main_hand.tooltip")) - .option(Option.createBuilder() - .name(lang("hand.requirements")) - .description(OptionDescription.of(lang("hand.requirements.description") - .append("\n\n").append(lang("hand.requirements.description_2").styled(style -> style.withColor(TextColor.fromFormatting(Formatting.AQUA)))) - .append("\n").append(lang("hand.requirements.description_3").styled(style -> style.withColor(TextColor.fromFormatting(Formatting.GREEN)))) - .append("\n").append(lang("hand.requirements.description_4").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 -> Text.translatable("config.sit."+v.name().toLowerCase()))) - .build()) - .group(OptionGroup.createBuilder() - .name(lang("hand.restrictions")) - .description(OptionDescription.of(lang("hand.restrictions.description"))) - .option(Option.createBuilder() - .name(lang("hand.restrictions.blocks")) - .binding(Config.defaults.mainBlock,()-> Config.mainBlock, n -> Config.mainBlock = n) - .controller(opt -> BooleanControllerBuilder.create(opt).trueFalseFormatter()) - .build()) - .option(Option.createBuilder() - .name(lang("hand.restrictions.food")) - .binding(Config.defaults.mainFood,()-> Config.mainFood, n -> Config.mainFood = n) - .controller(opt -> BooleanControllerBuilder.create(opt).trueFalseFormatter()) - .build()) - .option(Option.createBuilder() - .name(lang("hand.restrictions.usable")) - .description(OptionDescription.of(lang("hand.restrictions.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.whitelist")) - .description(OptionDescription.of(lang("hand.whitelist.description") - .append("\n\n").append(lang("hand.list.description")) - .append(lang("hand.list.description_2").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.blacklist")) - .description(OptionDescription.of(lang("hand.blacklist.description") - .append("\n\n").append(lang("hand.list.description")) - .append(lang("hand.list.description_2").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")) - .tooltip(lang("category.off_hand.tooltip")) - .option(Option.createBuilder() - .name(lang("hand.requirements")) - .description(OptionDescription.of(lang("hand.requirements.description") - .append("\n\n").append(lang("hand.requirements.description_2").styled(style -> style.withColor(TextColor.fromFormatting(Formatting.AQUA)))) - .append("\n").append(lang("hand.requirements.description_3").styled(style -> style.withColor(TextColor.fromFormatting(Formatting.GREEN)))) - .append("\n").append(lang("hand.requirements.description_4").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 -> Text.translatable("config.sit."+v.name().toLowerCase()))) - .build()) - .group(OptionGroup.createBuilder() - .name(lang("hand.restrictions")) - .description(OptionDescription.of(lang("hand.restrictions.description"))) - .option(Option.createBuilder() - .name(lang("hand.restrictions.blocks")) - .binding(Config.defaults.offBlock,()-> Config.offBlock, n -> Config.offBlock = n) - .controller(opt -> BooleanControllerBuilder.create(opt).trueFalseFormatter()) - .build()) - .option(Option.createBuilder() - .name(lang("hand.restrictions.food")) - .binding(Config.defaults.offFood,()-> Config.offFood, n -> Config.offFood = n) - .controller(opt -> BooleanControllerBuilder.create(opt).trueFalseFormatter()) - .build()) - .option(Option.createBuilder() - .name(lang("hand.restrictions.usable")) - .description(OptionDescription.of(lang("hand.restrictions.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.whitelist")) - .description(OptionDescription.of(lang("hand.whitelist.description") - .append("\n\n").append(lang("hand.list.description")) - .append(lang("hand.list.description_2").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.blacklist")) - .description(OptionDescription.of(lang("hand.blacklist.description") - .append("\n\n").append(lang("hand.list.description")) - .append(lang("hand.list.description_2").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); - } - - /** - * check if YACL is installed by getting a class and seeing if it throws - * @return if YACL is installed - */ - public static boolean yaclCheck() { - try { - Class.forName("dev.isxander.yacl3.platform.Env"); - return true; - } catch (ClassNotFoundException e) { - return false; - } - } -} diff --git a/src/main/java/one/oth3r/sit/PacketBuilder.java b/src/main/java/one/oth3r/sit/PacketBuilder.java deleted file mode 100644 index 6afb6cf..0000000 --- a/src/main/java/one/oth3r/sit/PacketBuilder.java +++ /dev/null @@ -1,35 +0,0 @@ -package one.oth3r.sit; - -import io.netty.buffer.ByteBuf; -import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; -import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; -import net.minecraft.network.PacketByteBuf; -import net.minecraft.util.Identifier; - -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; - -public class PacketBuilder { - public static final String SETTINGS = "settings_v1.0"; - private final String message; - private final PacketByteBuf packetByteBuf = PacketByteBufs.create(); - public PacketBuilder(ByteBuf buf) { - // Read any data sent in the packet - message = buf.toString(StandardCharsets.UTF_8); - packetByteBuf.writeBytes(buf); - } - public PacketBuilder(String message) { - this.message = message; - packetByteBuf.writeBytes(ByteBuffer.wrap(message.getBytes(StandardCharsets.UTF_8)).array()); - } - public static Identifier getIdentifier() { - // only 1 packet rn - return new Identifier(Sit.MOD_ID, SETTINGS); - } - public void send() { - ClientPlayNetworking.send(getIdentifier(), packetByteBuf); - } - public String getMessage() { - return this.message; - } -} diff --git a/src/main/java/one/oth3r/sit/Sit.java b/src/main/java/one/oth3r/sit/Sit.java index 3b23bcd..b062269 100644 --- a/src/main/java/one/oth3r/sit/Sit.java +++ b/src/main/java/one/oth3r/sit/Sit.java @@ -1,54 +1,17 @@ package one.oth3r.sit; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.reflect.TypeToken; import net.fabricmc.api.ModInitializer; -import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; -import net.minecraft.server.MinecraftServer; -import net.minecraft.server.command.CommandManager; -import net.minecraft.server.network.ServerPlayerEntity; -import net.minecraft.text.MutableText; -import net.minecraft.text.Text; -import one.oth3r.sit.file.Config; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.lang.reflect.Type; -import java.util.HashMap; +import one.oth3r.sit.file.FileData; +import one.oth3r.sit.utl.Events; public class Sit implements ModInitializer { - public static final Logger LOGGER = LoggerFactory.getLogger("sit"); - public static final String MOD_ID = "oth3r-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; @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(); - //PACKETS - ServerPlayNetworking.registerGlobalReceiver(PacketBuilder.getIdentifier(), - (server, player, handler, buf, responseSender) -> { - // copy to not throw errors - PacketBuilder packet = new PacketBuilder(buf.copy()); - server.execute(() -> { - LOGGER.info(String.format("Received custom sitting settings from %s.",player.getName().getString())); - Type hashMapToken = new TypeToken>() {}.getType(); - Gson gson = new GsonBuilder().disableHtmlEscaping().create(); - playerSettings.put(player,gson.fromJson(packet.getMessage(),hashMapToken)); - }); - }); - } - public static MutableText lang(String key, Object... args) { - if (isClient) return Text.translatable(key, args); - else return LangReader.of(key, args).getTxT(); + FileData.loadFiles(); + // save the files to populate all missing config options + FileData.saveFiles(); + Events.registerCommon(); } } \ 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 e20a5c0..43833dc 100644 --- a/src/main/java/one/oth3r/sit/SitClient.java +++ b/src/main/java/one/oth3r/sit/SitClient.java @@ -1,31 +1,16 @@ 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 one.oth3r.sit.utl.Data; +import one.oth3r.sit.utl.Events; public class SitClient implements ClientModInitializer { - public static boolean inGame = false; + @Override public void onInitializeClient() { - Sit.isClient = true; - ClientPlayConnectionEvents.JOIN.register((handler, sender, client) -> { - inGame = true; - // send a data packet whenever joining a server - sendSettingsPackets(); - }); - // reset inGame - ClientPlayConnectionEvents.DISCONNECT.register((handler, client) -> inGame = false); + Data.setClient(true); + Events.registerClient(); } - /** - * sends the settings packets to the server is the client is in game - */ - public static void sendSettingsPackets() { - if (inGame) { - Gson gson = new GsonBuilder().disableHtmlEscaping().create(); - new PacketBuilder(gson.toJson(Utl.HandSettings.getHandSettings())).send(); - } - } + } 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 ed69d96..0000000 --- a/src/main/java/one/oth3r/sit/Utl.java +++ /dev/null @@ -1,70 +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 one.oth3r.sit.file.Config; - -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; - -public class Utl { - public static class HandSettings { - public static HashMap getHandSettings() { - Gson gson = new GsonBuilder().disableHtmlEscaping().create(); - HashMap settings = new HashMap<>(); - settings.put("hand.main.requirement",String.valueOf(Config.mainReq)); - settings.put("hand.main.block",String.valueOf(Config.mainBlock)); - settings.put("hand.main.food",String.valueOf(Config.mainFood)); - settings.put("hand.main.usable",String.valueOf(Config.mainUsable)); - settings.put("hand.main.whitelist",gson.toJson(Config.mainWhitelist)); - settings.put("hand.main.blacklist",gson.toJson(Config.mainBlacklist)); - settings.put("hand.off.requirement",String.valueOf(Config.offReq)); - settings.put("hand.off.block",String.valueOf(Config.offBlock)); - settings.put("hand.off.food",String.valueOf(Config.offFood)); - settings.put("hand.off.usable",String.valueOf(Config.offUsable)); - settings.put("hand.off.whitelist",gson.toJson(Config.offWhitelist)); - settings.put("hand.off.blacklist",gson.toJson(Config.offBlacklist)); - return settings; - } - - public static Config.HandRequirement getReq(ServerPlayerEntity player, HandType type) { - return Config.HandRequirement.get(Sit.playerSettings.get(player).get("hand."+type+".requirement")); - } - - public static List getList(ServerPlayerEntity player, HandType type, String setting) { - Type listType = new TypeToken>() {}.getType(); - return new Gson().fromJson(Sit.playerSettings.get(player).get("hand."+type+"."+setting),listType); - } - - public static boolean getBool(ServerPlayerEntity player, HandType type, String setting) { - return Boolean.parseBoolean(Sit.playerSettings.get(player).get("hand."+type+"."+setting)); - } - - public enum HandType { - main, - off - } - } - public static class Num { - public static boolean isInt(String string) { - try { - Integer.parseInt(string); - } catch (NumberFormatException nfe) { - return false; - } - return true; - } - public static boolean isFloat(String string) { - try { - Float.parseFloat(string); - } catch (NumberFormatException nfe) { - return false; - } - return true; - } - } -} diff --git a/src/main/java/one/oth3r/sit/SitCommand.java b/src/main/java/one/oth3r/sit/command/SitCommand.java similarity index 53% rename from src/main/java/one/oth3r/sit/SitCommand.java rename to src/main/java/one/oth3r/sit/command/SitCommand.java index d641d30..0c80ed4 100644 --- a/src/main/java/one/oth3r/sit/SitCommand.java +++ b/src/main/java/one/oth3r/sit/command/SitCommand.java @@ -1,19 +1,18 @@ -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.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 one.oth3r.sit.file.Config; +import one.oth3r.sit.utl.Data; +import one.oth3r.sit.utl.Logic; +import one.oth3r.sit.utl.Utl; import java.util.concurrent.CompletableFuture; @@ -27,57 +26,59 @@ 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 - String keyword = "sit"; - int index = Integer.MAX_VALUE; - if (arg.contains(keyword)) index = arg.indexOf(keyword); - //trims the words before the text - if (index != Integer.MAX_VALUE) arg = arg.substring(index).trim(); + // trim all the arguments before the command (for commands like /execute) + int index = arg.indexOf("sit"); + // trims the words before the text + if (index != -1) arg = arg.substring(index).trim(); + String[] args = arg.split(" "); + // if command string starts with sit, remove it if (args[0].equalsIgnoreCase("sit")) args = arg.replaceFirst("sit ", "").split(" "); + // if console if (player == null) { if (args[0].equalsIgnoreCase("reload")) { - Config.load(); - Sit.LOGGER.info(Sit.lang("key.sit.command.reloaded").getString()); + Logic.reload(); + Data.LOGGER.info(Utl.lang("sit!.chat.reloaded").getString()); } return 1; } + + // player + if (args[0].equalsIgnoreCase("sit")) { - BlockPos pos = player.getBlockPos(); - // get the block under the player if player on top of a solid - if (!(player.getY() - ((int) player.getY()) > 0.00)) { - pos = pos.add(0,-1,0); + // if the player can't sit where they're looking, try to sit below + if (!Logic.sitLooking(player)) { + BlockPos pos = player.getBlockPos(); + + if (!(player.getY() - ((int) player.getY()) > 0.00)) { + pos = pos.add(0, -1, 0); + } + + // if already sitting, ignore + if (Data.getSitEntity(player) != null) return 1; + + // try to make the player sit + Logic.sit(player, pos, null); } - // if already sitting, ignore - if (Events.entities.containsKey(player)) return 1; - // sit - Events.sit(player,pos); } + if (args[0].equalsIgnoreCase("reload")) { - Config.load(); - player.sendMessage(Sit.lang("key.sit.command.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(Sit.lang("key.sit.command.purged")); - } catch (CommandSyntaxException e) { - player.sendMessage(Sit.lang("key.sit.command.purged")); - e.printStackTrace(); - } + Logic.reload(); + player.sendMessage(Utl.messageTag().append(Utl.lang("sit!.chat.reloaded").formatted(Formatting.GREEN))); } + + 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 f40c834..0000000 --- a/src/main/java/one/oth3r/sit/file/Config.java +++ /dev/null @@ -1,252 +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 boolean handSitting = defaults.handSitting; - 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) {} - - handSitting = Boolean.parseBoolean((String) properties.computeIfAbsent("hand.sitting", a -> String.valueOf(defaults.handSitting))); - 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 LangReader.of("config.sit."+key, args).getTxT().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, zh_tw"); - 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# "+Sit.lang("config.sit."+ - "general.sittable_blocks.description") - .append("\n# ").append(lang("general.sittable_blocks.description_2", - "\"minecraft:campfire|0.255|1|lit=false\"")) - .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")) - .append("\n# ").append(lang("general.sittable_blocks.description_7")) - .append("\n# ").append(lang("general.sittable_blocks.description_8")).getString()); - file.write("\ncustom-blocks="+gson.toJson(customBlocks)); - file.write("\n\n# "+lang("hand")); - file.write("\nhand.sitting=" + handSitting); - file.write("\n# "+Sit.lang("config.sit."+ - "hand.requirements.description") - .append("\n# ").append(lang("hand.requirements.description_2")) - .append("\n# ").append(lang("hand.requirements.description_3")) - .append("\n# ").append(lang("hand.requirements.description_4")).getString()); - file.write("\nhand.main.requirement=" + mainReq); - file.write("\nhand.main.block=" + mainBlock); - file.write("\nhand.main.food=" + mainFood); - file.write("\nhand.main.usable=" + mainUsable); - file.write("\nhand.main.whitelist="+gson.toJson(mainWhitelist)); - file.write("\nhand.main.blacklist="+gson.toJson(mainBlacklist)); - file.write("\nhand.off.requirement=" + offReq); - file.write("\nhand.off.block=" + offBlock); - file.write("\nhand.off.food=" + offFood); - file.write("\nhand.off.usable=" + offUsable); - 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 boolean handSitting = true; - 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..0e1ab0b --- /dev/null +++ b/src/main/java/one/oth3r/sit/file/CustomBlock.java @@ -0,0 +1,101 @@ +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.state.State; +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<>(); + + + public CustomBlock() {} + + public CustomBlock(ArrayList blockIds, ArrayList blockTags, ArrayList blockStates) { + this.blockIds = blockIds; + this.blockTags = blockTags; + this.blockStates = blockStates; + } + + public ArrayList getBlockIds() { + return blockIds; + } + + public ArrayList getBlockTags() { + return blockTags; + } + + public ArrayList getBlockStates() { + return blockStates; + } + + + + /** + * 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; + + /// BLOCK STATE CHECKER + // now check if the state is one of the acceptable states + for (String state : blockStates) { + // if there is a NOT (!) blockstate + if (state.startsWith("!")) { + // if it is contained in the block, return false + // remove the '!' + String fixedState = state.substring(1); + if (blockState.getEntries().entrySet().stream().map(State.PROPERTY_MAP_PRINTER).anyMatch(s -> s.equals(fixedState))) return false; + } + // else check if the blockstate matches, if not return false + else if (blockState.getEntries().entrySet().stream().map(State.PROPERTY_MAP_PRINTER).noneMatch(s -> s.equals(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; + } + + // a boolean to check if one of the blocks are in a filtered tag + boolean tagCheck = false; + + // 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(), new Identifier(tag.substring(2))))) return false; + // if there is a match, return true + if (blockState.isIn(TagKey.of(Registries.BLOCK.getKey(), Identifier.tryParse(tag.substring(1))))) tagCheck = true; + } + + // not returning true in the loop because there might be a (!) not tag that the block might fall into, after the block was already in another tag + return tagCheck; + } +} diff --git a/src/main/java/one/oth3r/sit/file/CustomFile.java b/src/main/java/one/oth3r/sit/file/CustomFile.java new file mode 100644 index 0000000..eab2d74 --- /dev/null +++ b/src/main/java/one/oth3r/sit/file/CustomFile.java @@ -0,0 +1,101 @@ +package one.oth3r.sit.file; + +import net.fabricmc.loader.api.FabricLoader; +import one.oth3r.sit.utl.Data; +import one.oth3r.sit.utl.Utl; +import org.jetbrains.annotations.NotNull; + +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; + +public interface CustomFile > { + + void reset(); + + /** + * saves the current instance to file + */ + default void save() { + if (!getFile().exists()) { + Data.LOGGER.info(String.format("Creating new `%s`", getFile().getName())); + } + try (BufferedWriter writer = Files.newBufferedWriter(getFile().toPath(), StandardCharsets.UTF_8)) { + writer.write(Utl.getGson().toJson(this)); + } catch (Exception e) { + Data.LOGGER.error(String.format("ERROR SAVING '%s`: %s", getFile().getName(), e.getMessage())); + } + } + + /** + * loads the file to the current instance using updateFromReader() + */ + default void load() { + File file = getFile(); + if (!file.exists()) fileNotExist(); + // try reading the file + try (BufferedReader reader = Files.newBufferedReader(file.toPath(), StandardCharsets.UTF_8)) { + updateFromReader(reader); + } catch (Exception e) { + Data.LOGGER.error(String.format("ERROR LOADING '%s`: %s", file.getName(),e.getMessage())); + } + } + + default void updateFromReader(BufferedReader reader) { + // try to read the json + T file; + try { + file = Utl.getGson().fromJson(reader, getFileClass()); + } catch (Exception e) { + throw new NullPointerException(); + } + // throw null if the fileData is null or version is null + if (file == null) throw new NullPointerException(); + + // update the instance + file.update(); + // load the file to the current object + loadFileData(file); + } + + @NotNull + Class getFileClass(); + + /** + * loads the data from the file object into the current object + * @param newFile the file to take the properties from + */ + void loadFileData(T newFile); + + /** + * updates the file based on the version number of the current instance + */ + void update(); + + /** + * logic for the file not existing when loading, defaults to saving + */ + default void fileNotExist() { + // try to make the config directory + try { + Files.createDirectories(Paths.get(getDirectory())); + } catch (Exception e) { + Data.LOGGER.error("Failed to create config directory. Canceling all config loading..."); + return; + } + save(); + } + + String getFileName(); + + default String getDirectory() { + return FabricLoader.getInstance().getConfigDir().toFile()+"/"; + } + + default File getFile() { + return new File(getDirectory()+getFileName()); + } +} diff --git a/src/main/java/one/oth3r/sit/file/CustomItem.java b/src/main/java/one/oth3r/sit/file/CustomItem.java new file mode 100644 index 0000000..05515f6 --- /dev/null +++ b/src/main/java/one/oth3r/sit/file/CustomItem.java @@ -0,0 +1,65 @@ +package one.oth3r.sit.file; + +import com.google.gson.annotations.SerializedName; +import net.minecraft.item.ItemStack; +import net.minecraft.registry.Registries; +import net.minecraft.registry.tag.TagKey; +import net.minecraft.util.Identifier; + +import java.util.ArrayList; + +public class CustomItem { + @SerializedName("item-ids") + private ArrayList itemIDs = new ArrayList<>(); + @SerializedName("item-tags") + private ArrayList itemTags = new ArrayList<>(); + + public CustomItem() {} + + public CustomItem(ArrayList itemIDs, ArrayList itemTags) { + this.itemIDs = itemIDs; + this.itemTags = itemTags; + } + + public ArrayList getItemIDs() { + return itemIDs; + } + + public ArrayList getItemTags() { + return itemTags; + } + + /** + * returns if the block is the correct type or not + * @param itemStack the blockstate to check + * @return if the type of block is matching the CustomBlock rules (e.g. if it is wood, ect.) + */ + public boolean checkItem(ItemStack itemStack) { + String itemId = Registries.ITEM.getId(itemStack.getItem()).toString(); + // check the custom item ids + for (String id : itemIDs) { + // 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; + } + + // a boolean to check if one of the items are in a filtered tag + boolean tagCheck = false; + + // check the custom item tags + for (String tag : itemTags) { + // substring to remove # and if needed, "!" + // if a NOT tag + if (tag.startsWith("!")) { + // if there is a math for the NOT(!) tag, return false + if (itemStack.isIn(TagKey.of(Registries.ITEM.getKey(), new Identifier(tag.substring(2))))) return false; + } + // else (normal tag), if there is a match, set tagCheck to true + else if (itemStack.isIn(TagKey.of(Registries.ITEM.getKey(), new Identifier(tag.substring(1))))) tagCheck = true; + } + + // not returning true in the loop because there might be a (!) not tag that the item might fall into, after the item was already in another tag + return tagCheck; + } +} diff --git a/src/main/java/one/oth3r/sit/file/FileData.java b/src/main/java/one/oth3r/sit/file/FileData.java new file mode 100644 index 0000000..1e7de3d --- /dev/null +++ b/src/main/java/one/oth3r/sit/file/FileData.java @@ -0,0 +1,110 @@ +package one.oth3r.sit.file; + +import net.minecraft.server.network.ServerPlayerEntity; +import one.oth3r.sit.utl.Data; +import one.oth3r.sit.utl.Utl; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; + +public class FileData { + /** + * Sit! config file + */ + private static ServerConfig serverConfig = new ServerConfig(); + + public static ServerConfig getServerConfig() { + return serverConfig; + } + + public static void setServerConfig(ServerConfig newServerConfig) { + serverConfig = new ServerConfig(newServerConfig); + } + + /** + * The default sitting config for all new players + */ + private static SittingConfig sittingConfig = new SittingConfig(); + + public static SittingConfig getSittingConfig() { + return sittingConfig; + } + + public static void setSittingConfig(SittingConfig newSittingConfig) { + sittingConfig = new SittingConfig(newSittingConfig); + } + + /** + * the sitting 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, SittingConfig config) { + playerSettings.put(player, config); + } + + public static void removePlayerSetting(ServerPlayerEntity player) { + playerSettings.remove(player); + } + + public static SittingConfig getPlayerSetting(ServerPlayerEntity player) { + return playerSettings.getOrDefault(player, sittingConfig); + } + + /** + * loads all config files to memory + */ + public static void loadFiles() { + getServerConfig().load(); + + getSittingConfig().load(); + // if loading file and is on supported server on client, send the new settings over + if (Data.isClient() && Data.isSupportedServer()) { + Utl.sendSettingsPackets(); + } + } + + /** + * saves all config files + */ + public static void saveFiles() { + getSittingConfig().save(); + getServerConfig().save(); + } + + public static class Defaults { + public static final ArrayList SITTING_BLOCKS = new ArrayList<>(Arrays.asList( + new SittingBlock(new ArrayList<>(),new ArrayList<>(Arrays.asList("#minecraft:campfires")), new ArrayList<>(Arrays.asList("lit=false")),.437), + new SittingBlock(new ArrayList<>(Arrays.asList("!minecraft:crimson_stem","!minecraft:warped_stem","minecraft:polished_basalt")), new ArrayList<>(Arrays.asList("#minecraft:logs","!#minecraft:oak_logs")), new ArrayList<>(Arrays.asList("!axis=y")),1.0), + new SittingBlock(new ArrayList<>(Arrays.asList()), new ArrayList<>(Arrays.asList("#minecraft:beds")), new ArrayList<>(Arrays.asList("part=foot","occupied=false")),.5625) + )); + + public static final ArrayList BLACKLISTED_BLOCKS = new ArrayList<>(Arrays.asList( + new CustomBlock(new ArrayList<>(),new ArrayList<>(Arrays.asList("#minecraft:shulker_boxes")),new ArrayList<>()) + )); + + public static final ArrayList INTERACTION_BLOCKS = new ArrayList<>(Arrays.asList( + new CustomBlock(new ArrayList<>(Arrays.asList("minecraft:crafter")),new ArrayList<>(Arrays.asList( + "#minecraft:shulker_boxes","#c:player_workstations/furnaces","#c:player_workstations/crafting_tables", + "#c:villager_job_sites","#minecraft:trapdoors","#c:chests")),new ArrayList<>()) + )); + + public static final HandSetting MAIN_HAND = new HandSetting(HandSetting.SittingRequirement.EMPTY, new HandSetting.Filter( + false,new HandSetting.Filter.Presets(), + new CustomItem( + new ArrayList<>(), + new ArrayList<>(Arrays.asList("#minecraft:bookshelf_books","!#minecraft:lectern_books"))))); + + public static final HandSetting OFF_HAND = new HandSetting(HandSetting.SittingRequirement.FILTER, new HandSetting.Filter( + false, new HandSetting.Filter.Presets(false, true, false), + new CustomItem(new ArrayList<>(Arrays.asList("minecraft:filled_map", + "minecraft:torch", "minecraft:soul_torch","minecraft:redstone_torch", + "minecraft:lantern", "minecraft:soul_lantern")), + new ArrayList<>()))); + } +} 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..b0c5527 --- /dev/null +++ b/src/main/java/one/oth3r/sit/file/HandSetting.java @@ -0,0 +1,96 @@ +package one.oth3r.sit.file; + +import com.google.gson.annotations.SerializedName; + +import java.util.Arrays; +import java.util.stream.Collectors; + +public class HandSetting { + + @SerializedName("requirement") + private SittingRequirement sittingRequirement = SittingRequirement.NONE; + @SerializedName("requirement-options") @SuppressWarnings("unused") + private final String sittingRequirementOptions = Arrays.stream(SittingRequirement.values()).map(Enum::toString).collect(Collectors.joining(", ")); + @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("invert-filter") + private Boolean invert = false; + @SerializedName("presets") + private Presets presets = new Presets(); + @SerializedName("custom-items") + private CustomItem customItems = new CustomItem(); + + public Filter() {} + + public Filter(boolean invert, Presets presets, CustomItem customItems) { + this.invert = invert; + this.presets = presets; + this.customItems = customItems; + } + + public Boolean isInverted() { + return invert; + } + + public Presets getPresets() { + return presets; + } + + public CustomItem getCustomItems() { + return customItems; + } + + public static class Presets { + @SerializedName("block") + private boolean block = false; + @SerializedName("food") + private boolean food = false; + @SerializedName("usable") + private boolean usable = false; + + public Presets() {} + + public Presets(boolean block, boolean food, boolean usable) { + this.block = block; + this.food = food; + this.usable = usable; + } + + public boolean isBlock() { + return block; + } + + public boolean isFood() { + return food; + } + + public boolean isUsable() { + return usable; + } + } + } +} diff --git a/src/main/java/one/oth3r/sit/LangReader.java b/src/main/java/one/oth3r/sit/file/LangReader.java similarity index 83% rename from src/main/java/one/oth3r/sit/LangReader.java rename to src/main/java/one/oth3r/sit/file/LangReader.java index 2f46105..bcafcb2 100644 --- a/src/main/java/one/oth3r/sit/LangReader.java +++ b/src/main/java/one/oth3r/sit/file/LangReader.java @@ -1,10 +1,11 @@ -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 one.oth3r.sit.utl.Data; import java.io.InputStream; import java.io.InputStreamReader; @@ -75,20 +76,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-oth3r/lang/" + FileData.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-oth3r/lang/" + new ServerConfig().getLang() +".json"); + Data.LOGGER.error("COULDN'T LOAD THE LANGUAGE FILE. RESETTING 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 ENGLISH 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(); + Data.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..05c604e --- /dev/null +++ b/src/main/java/one/oth3r/sit/file/ServerConfig.java @@ -0,0 +1,390 @@ +package one.oth3r.sit.file; + +import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; +import com.google.gson.annotations.SerializedName; +import com.google.gson.reflect.TypeToken; +import net.minecraft.util.Hand; +import one.oth3r.sit.utl.Data; +import one.oth3r.sit.utl.Utl; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.FileInputStream; +import java.lang.reflect.Type; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Properties; + +public class ServerConfig implements CustomFile { + + @SerializedName("version") + private Double version = 2.1; + @SerializedName("lang") + private String lang = "en_us"; + @SerializedName("lang-options") + private final String langOptions = "en_us, it_it, pt_br, tr_tr, zh_tw"; + @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 sittingBlocks = FileData.Defaults.SITTING_BLOCKS; + @SerializedName("blacklisted-blocks") + private ArrayList blacklistedBlocks = FileData.Defaults.BLACKLISTED_BLOCKS; + @SerializedName("interaction-blocks") + private ArrayList interactionBlocks = FileData.Defaults.INTERACTION_BLOCKS; + + 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.sittingBlocks = serverConfig.sittingBlocks; + this.blacklistedBlocks = serverConfig.blacklistedBlocks; + this.interactionBlocks = serverConfig.interactionBlocks; + } + + public ServerConfig(Double version, String lang, boolean keepActive, boolean sitWhileSeated, + PresetBlocks presetBlocks, boolean customEnabled, + ArrayList sittingBlocks, ArrayList blacklistedBlocks, + ArrayList interactionBlocks) { + this.version = version; + this.lang = lang; + this.keepActive = keepActive; + this.sitWhileSeated = sitWhileSeated; + this.presetBlocks = presetBlocks; + this.customEnabled = customEnabled; + this.sittingBlocks = sittingBlocks; + this.blacklistedBlocks = blacklistedBlocks; + this.interactionBlocks = interactionBlocks; + } + + public Double getVersion() { + return version; + } + + public String getLang() { + return lang; + } + + public boolean isKeepActive() { + return keepActive; + } + + public boolean canSitWhileSeated() { + return sitWhileSeated; + } + + public PresetBlocks getPresetBlocks() { + return presetBlocks; + } + + public Boolean isCustomEnabled() { + return customEnabled; + } + + public ArrayList getSittingBlocks() { + return sittingBlocks; + } + + public ArrayList getBlacklistedBlocks() { + return blacklistedBlocks; + } + + public ArrayList getInteractionBlocks() { + return interactionBlocks; + } + + 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; + } + } + + @Override + public void reset() { + loadFileData(new ServerConfig()); + } + + @Override + public @NotNull Class getFileClass() { + return ServerConfig.class; + } + + @Override + public void loadFileData(ServerConfig newFile) { + this.version = newFile.version; + this.lang = newFile.lang; + this.keepActive = newFile.keepActive; + this.sitWhileSeated = newFile.sitWhileSeated; + this.presetBlocks = newFile.presetBlocks; + this.customEnabled = newFile.customEnabled; + this.sittingBlocks = newFile.sittingBlocks; + this.blacklistedBlocks = newFile.blacklistedBlocks; + } + + @Override + public void update() { + /// update to 2.1, just a new list, nothing to change + if (version == 2.0) { + version = 2.1; + } + } + + @Override + public String getFileName() { + return "server-config.json"; + } + + @Override + public String getDirectory() { + return Data.CONFIG_DIR; + } + + @Override + public void fileNotExist() { + CustomFile.super.fileNotExist(); + // try checking the old/legacy config directory for the file + if (Legacy.getLegacyFile().exists()) { + Data.LOGGER.info("Updating Sit!.properties to sit!/config.json"); + Legacy.run(); + } + } + + protected 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(Data.CONFIG_DIR.substring(0, Data.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(new ServerConfig().getVersion())); + + // 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) { + Data.LOGGER.error("Error loading legacy config file: {}", e.getMessage()); + } + + // delete the old file + try { + Files.delete(file.toPath()); + Data.LOGGER.info("Deleted " + file.getName()); + } catch (Exception e) { + Data.LOGGER.error("Failed to delete the old Sit! config."); + } + } + + /** + * converts the legacy hand requirement enum to the new one + * @param requirement the old string + */ + private static HandSetting.SittingRequirement handRequirementUpdater(String requirement) { + return switch (requirement) { + case "restrictive" -> HandSetting.SittingRequirement.FILTER; + case "none" -> HandSetting.SittingRequirement.NONE; + default -> HandSetting.SittingRequirement.EMPTY; + }; + } + + /** + * 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 SittingBlock( + 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 { + 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.canSitWhileSeated()))), + new 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<>(), FileData.Defaults.INTERACTION_BLOCKS + ); + + SittingConfig defaultSittingConfig = new SittingConfig(); + + SittingConfig sittingConfig = new SittingConfig(); + // * filters are flipped because the way they work are flipped + try { + sittingConfig = new SittingConfig( + 1.0, true, Boolean.parseBoolean((String) properties.computeIfAbsent("hand.sitting", a -> String.valueOf(defaultSittingConfig.canSitWithHand()))), + new HandSetting( + handRequirementUpdater((String) properties.computeIfAbsent("hand.main.requirement", a -> String.valueOf(defaultSittingConfig.getHand(Hand.MAIN_HAND).getSittingRequirement()))), + new HandSetting.Filter(false, + new HandSetting.Filter.Presets( + !Boolean.parseBoolean((String) properties.computeIfAbsent("hand.main.block", a -> String.valueOf(!defaultSittingConfig.getHand(Hand.MAIN_HAND).getFilter().getPresets().isBlock()))), + !Boolean.parseBoolean((String) properties.computeIfAbsent("hand.main.food", a -> String.valueOf(!defaultSittingConfig.getHand(Hand.MAIN_HAND).getFilter().getPresets().isFood()))), + !Boolean.parseBoolean((String) properties.computeIfAbsent("hand.main.usable", a -> String.valueOf(!defaultSittingConfig.getHand(Hand.MAIN_HAND).getFilter().getPresets().isUsable())))), + new CustomItem(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( + handRequirementUpdater((String) properties.computeIfAbsent("hand.off.requirement", a -> String.valueOf(defaultSittingConfig.getHand(Hand.OFF_HAND).getSittingRequirement()))), + new HandSetting.Filter(false, + new HandSetting.Filter.Presets( + !Boolean.parseBoolean((String) properties.computeIfAbsent("hand.off.block", a -> String.valueOf(!defaultSittingConfig.getHand(Hand.OFF_HAND).getFilter().getPresets().isBlock()))), + !Boolean.parseBoolean((String) properties.computeIfAbsent("hand.off.food", a -> String.valueOf(!defaultSittingConfig.getHand(Hand.OFF_HAND).getFilter().getPresets().isFood()))), + !Boolean.parseBoolean((String) properties.computeIfAbsent("hand.off.usable", a -> String.valueOf(!defaultSittingConfig.getHand(Hand.OFF_HAND).getFilter().getPresets().isUsable())))), + new CustomItem(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 { + sittingConfig = new SittingConfig( + 1.0, true, defaultSittingConfig.canSitWithHand(), + new HandSetting( + handRequirementUpdater((String) properties.computeIfAbsent("main-hand-requirement", a -> String.valueOf(defaultSittingConfig.getHand(Hand.MAIN_HAND).getSittingRequirement()))), + new HandSetting.Filter(false, + new HandSetting.Filter.Presets( + !Boolean.parseBoolean((String) properties.computeIfAbsent("main-hand-block", a -> String.valueOf(!defaultSittingConfig.getHand(Hand.MAIN_HAND).getFilter().getPresets().isBlock()))), + !Boolean.parseBoolean((String) properties.computeIfAbsent("main-hand-food", a -> String.valueOf(!defaultSittingConfig.getHand(Hand.MAIN_HAND).getFilter().getPresets().isFood()))), + !Boolean.parseBoolean((String) properties.computeIfAbsent("main-hand-usable", a -> String.valueOf(!defaultSittingConfig.getHand(Hand.MAIN_HAND).getFilter().getPresets().isUsable())))), + new CustomItem(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( + handRequirementUpdater((String) properties.computeIfAbsent("off-hand-requirement", a -> String.valueOf(defaultSittingConfig.getHand(Hand.OFF_HAND).getSittingRequirement()))), + new HandSetting.Filter(false, + new HandSetting.Filter.Presets( + !Boolean.parseBoolean((String) properties.computeIfAbsent("off-hand-block", a -> String.valueOf(!defaultSittingConfig.getHand(Hand.OFF_HAND).getFilter().getPresets().isBlock()))), + !Boolean.parseBoolean((String) properties.computeIfAbsent("off-hand-food", a -> String.valueOf(!defaultSittingConfig.getHand(Hand.OFF_HAND).getFilter().getPresets().isFood()))), + !Boolean.parseBoolean((String) properties.computeIfAbsent("off-hand-usable", a -> String.valueOf(!defaultSittingConfig.getHand(Hand.OFF_HAND).getFilter().getPresets().isUsable())))), + new CustomItem(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) {} + } + + FileData.setServerConfig(serverConfig); + FileData.setSittingConfig(sittingConfig); + serverConfig.save(); + sittingConfig.save(); + } catch (Exception e) { + Data.LOGGER.error("Error loading legacy config: {}", e.getMessage()); + } + } + } +} diff --git a/src/main/java/one/oth3r/sit/file/SittingBlock.java b/src/main/java/one/oth3r/sit/file/SittingBlock.java new file mode 100644 index 0000000..1f2cf36 --- /dev/null +++ b/src/main/java/one/oth3r/sit/file/SittingBlock.java @@ -0,0 +1,25 @@ +package one.oth3r.sit.file; + +import com.google.gson.annotations.SerializedName; + +import java.util.ArrayList; + +public class SittingBlock extends CustomBlock { + @SerializedName("sitting-height") + private Double sittingHeight = 0.5; + + /** + * gets the sitting height of a block, limiting the size from 0 - 1 + * @return the sitting height, clamped + */ + public Double getSittingHeight() { + return Math.max(0f, Math.min(1f, sittingHeight)); + } + + public SittingBlock() {} + + public SittingBlock(ArrayList blockIds, ArrayList blockTags, ArrayList blockStates, Double sittingHeight) { + super(blockIds, blockTags, blockStates); + this.sittingHeight = sittingHeight; + } +} diff --git a/src/main/java/one/oth3r/sit/file/SittingConfig.java b/src/main/java/one/oth3r/sit/file/SittingConfig.java new file mode 100644 index 0000000..d9e65e8 --- /dev/null +++ b/src/main/java/one/oth3r/sit/file/SittingConfig.java @@ -0,0 +1,98 @@ +package one.oth3r.sit.file; + +import com.google.gson.annotations.SerializedName; +import net.minecraft.util.Hand; +import one.oth3r.sit.utl.Data; +import org.jetbrains.annotations.NotNull; + +public class SittingConfig implements CustomFile { + + @SerializedName("version") + private Double version = 1.0; + @SerializedName("enabled") + private Boolean enabled = true; + @SerializedName("hand-sitting") + private Boolean handSitting = true; + @SerializedName("main-hand") + private HandSetting mainHand = FileData.Defaults.MAIN_HAND; + @SerializedName("off-hand") + private HandSetting offHand = FileData.Defaults.OFF_HAND; + + public SittingConfig() {} + + public SittingConfig(double version, boolean enabled, boolean handSitting, HandSetting mainHand, HandSetting offHand) { + this.version = version; + this.enabled = enabled; + this.handSitting = handSitting; + this.mainHand = mainHand; + this.offHand = offHand; + } + + public SittingConfig(SittingConfig sittingConfig) { + loadFileData(sittingConfig); + } + + public Double getVersion() { + return version; + } + + public Boolean getEnabled() { + return enabled; + } + + public void setEnabled(Boolean enabled) { + this.enabled = enabled; + } + + public boolean canSitWithHand() { + return handSitting; + } + + public void setHandSitting(Boolean handSitting) { + this.handSitting = handSitting; + } + + public HandSetting getHand(Hand handType) { + return handType.equals(Hand.MAIN_HAND) ? mainHand : offHand; + } + + public HandSetting getMainHand() { + return mainHand; + } + + public HandSetting getOffHand() { + return offHand; + } + + @Override + public void reset() { + loadFileData(new SittingConfig()); + } + + @Override + public @NotNull Class getFileClass() { + return SittingConfig.class; + } + + @Override + public void loadFileData(SittingConfig newFile) { + this.version = newFile.version; + this.enabled = newFile.enabled; + this.handSitting = newFile.handSitting; + this.mainHand = newFile.mainHand; + this.offHand = newFile.offHand; + } + + @Override + public void update() {} + + @Override + public String getFileName() { + return "sitting-config.json"; + } + + @Override + public String getDirectory() { + return Data.CONFIG_DIR; + } +} diff --git a/src/main/java/one/oth3r/sit/mixin/ReloadCommandMixin.java b/src/main/java/one/oth3r/sit/mixin/ReloadCommandMixin.java new file mode 100644 index 0000000..aefbbe3 --- /dev/null +++ b/src/main/java/one/oth3r/sit/mixin/ReloadCommandMixin.java @@ -0,0 +1,34 @@ +package one.oth3r.sit.mixin; + +import com.mojang.brigadier.CommandDispatcher; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.command.ReloadCommand; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.util.Formatting; +import one.oth3r.sit.file.FileData; +import one.oth3r.sit.utl.Data; +import one.oth3r.sit.utl.Utl; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(ReloadCommand.class) +public class ReloadCommandMixin { + @Inject(at = @At("TAIL"), method = "register") + private static void register(CommandDispatcher dispatcher, CallbackInfo ci) { + FileData.loadFiles(); + + // make sure the server isn't null + MinecraftServer server = Data.getServer(); + if (server == null || server.getPlayerManager() == null) return; + + // send a reloaded message to all players with permissions + for (ServerPlayerEntity player : server.getPlayerManager().getPlayerList()) { + if (player.isCreativeLevelTwoOp()) { + player.sendMessage(Utl.messageTag().append(Utl.lang("sit!.chat.reloaded").formatted(Formatting.GREEN))); + } + } + } +} 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..980dc38 --- /dev/null +++ b/src/main/java/one/oth3r/sit/mixin/TextDisplayDismountMixin.java @@ -0,0 +1,71 @@ +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 abstract class TextDisplayDismountMixin extends DisplayEntity { + public TextDisplayDismountMixin(EntityType entityType, World world) { + super(entityType, world); + } + + @Override + public Vec3d updatePassengerForDismount(LivingEntity passenger) { + // get the passenger's horizontal rotation, rotated counterclockwise, because the method rotates it clockwise for some reason + int[][] offset = Dismounting.getDismountOffsets(passenger.getHorizontalFacing().rotateYCounterclockwise()); + // 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/PacketSender.java b/src/main/java/one/oth3r/sit/packet/PacketSender.java new file mode 100644 index 0000000..ff624bb --- /dev/null +++ b/src/main/java/one/oth3r/sit/packet/PacketSender.java @@ -0,0 +1,39 @@ +package one.oth3r.sit.packet; + +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.util.Identifier; +import one.oth3r.sit.utl.Data; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; + +public class PacketSender { + private final PacketByteBuf data; + private final PacketType type; + + public PacketSender(PacketType type, String data) { + this.type = type; + this.data = PacketByteBufs.create() + .writeBytes(ByteBuffer.wrap(data.getBytes(StandardCharsets.UTF_8))); + } + + public void sendToPlayer(ServerPlayerEntity player) { + ServerPlayNetworking.send(player,getIdentifier(type),data); + } + + public void sendToServer() { + ClientPlayNetworking.send(getIdentifier(type),data); + } + + public static Identifier getIdentifier(PacketType packetType) { + return new Identifier(Data.MOD_ID, packetType.getId()); + } + + public static String getPacketData(PacketByteBuf buf) { + return buf.toString(StandardCharsets.UTF_8); + } +} diff --git a/src/main/java/one/oth3r/sit/packet/PacketType.java b/src/main/java/one/oth3r/sit/packet/PacketType.java new file mode 100644 index 0000000..28b351c --- /dev/null +++ b/src/main/java/one/oth3r/sit/packet/PacketType.java @@ -0,0 +1,16 @@ +package one.oth3r.sit.packet; + +public enum PacketType { + RESPONSE("response_v1.0"), + SETTINGS("settings_v2.0"); + + final String id; + + PacketType(String id) { + this.id = id; + } + + public String getId() { + return id; + } +} diff --git a/src/main/java/one/oth3r/sit/screen/ClickableImageWidget.java b/src/main/java/one/oth3r/sit/screen/ClickableImageWidget.java new file mode 100644 index 0000000..375c367 --- /dev/null +++ b/src/main/java/one/oth3r/sit/screen/ClickableImageWidget.java @@ -0,0 +1,31 @@ +package one.oth3r.sit.screen; + +import com.mojang.blaze3d.systems.RenderSystem; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.tooltip.Tooltip; +import net.minecraft.client.gui.widget.ButtonWidget; +import net.minecraft.text.Text; +import net.minecraft.util.Identifier; + +import java.util.function.Supplier; + +public class ClickableImageWidget extends ButtonWidget { + private final Identifier image; + + public ClickableImageWidget(int x, int y, int width, int height, Tooltip tooltip, Identifier image, ButtonWidget.PressAction onPress) { + super(x, y, width, height, Text.empty(), onPress, Supplier::get); + this.image = image; + this.setTooltip(tooltip); + } + + @Override + protected void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) { + context.setShaderColor(1.0f, 1.0f, 1.0f, this.alpha); + RenderSystem.enableBlend(); + RenderSystem.enableDepthTest(); + context.drawTexture(image, + this.getX(), this.getY(), 0.0f, 0.0f, this.getWidth(), this.getHeight(), this.getWidth(), this.getHeight()); + context.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f); + } + +} diff --git a/src/main/java/one/oth3r/sit/screen/ConfigScreen.java b/src/main/java/one/oth3r/sit/screen/ConfigScreen.java new file mode 100644 index 0000000..637bd6a --- /dev/null +++ b/src/main/java/one/oth3r/sit/screen/ConfigScreen.java @@ -0,0 +1,77 @@ +package one.oth3r.sit.screen; + +import com.mojang.blaze3d.systems.RenderSystem; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.screen.ConfirmLinkScreen; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.widget.ButtonWidget; +import net.minecraft.text.Text; +import net.minecraft.util.Identifier; +import one.oth3r.sit.file.FileData; +import one.oth3r.sit.utl.Data; + +public class ConfigScreen extends Screen { + protected final Screen parent; + + public ConfigScreen(Screen parent) { + super(Text.translatable("sit!.screen.config")); + this.parent = parent; + } + + @Override + protected void init() { + int startY = this.height / 4 + 48; + int spacing = 36; + TextureButtonWidget serverConfigButton = this.addDrawableChild(new TextureButtonWidget.Builder(Text.translatable("config.server"), + (button) -> client.setScreen(new UnderConstructionScreen(this, FileData.getServerConfig())), false) + .dimensions(250,30).texture(Identifier.of(Data.MOD_ID, "server_button"), 246, 26).build()); + serverConfigButton.setPosition(this.width / 2 - (serverConfigButton.getWidth()/2), startY); + + TextureButtonWidget sittingConfigButton = this.addDrawableChild(new TextureButtonWidget.Builder(Text.translatable("config.sitting"), + (button) -> client.setScreen(new UnderConstructionScreen(this, FileData.getSittingConfig())), false) + .dimensions(250,30).texture(Identifier.of(Data.MOD_ID, "sitting_button"), 246, 26).build()); + sittingConfigButton.setPosition(this.width / 2 - (sittingConfigButton.getWidth()/2), startY+36); + + + TextureButtonWidget issuesButton = this.addDrawableChild(new TextureButtonWidget.Builder(Text.translatable("sit!.gui.button.issues"), + ConfirmLinkScreen.opening(this, "https://github.com/Oth3r/Sit/issues"), true) + .dimensions(20,20).texture(Identifier.of(Data.MOD_ID, "issues"), 15, 15).build()); + issuesButton.setPosition(this.width / 2 - 125, startY + 72 + 12); + + + this.addDrawableChild(ButtonWidget.builder(Text.translatable("sit!.gui.button.website"), + ConfirmLinkScreen.opening(this, "https://modrinth.com/mod/sit!") + ).dimensions(this.width / 2 - 100, startY + 72 + 12, 98, 20).build()); + + this.addDrawableChild(ButtonWidget.builder(Text.translatable("gui.done"), (button) -> { + close(); + }).dimensions(this.width / 2 + 2, startY + 72 + 12, 98, 20).build()); + + TextureButtonWidget donateButton = this.addDrawableChild(new TextureButtonWidget.Builder(Text.translatable("sit!.gui.button.donate"), + ConfirmLinkScreen.opening(this, "https://Ko-fi.com/oth3r"), true) + .dimensions(20,20).texture(Identifier.of(Data.MOD_ID, "donate"), 15, 15).build()); + donateButton.setPosition(this.width / 2 + 105, startY + 72 + 12); + } + + @Override + public void render(DrawContext context, int mouseX, int mouseY, float delta) { + super.render(context, mouseX, mouseY, delta); + + // todo fade in like the title screen on first load? + renderBanner(context,width/2 - 64,this.height / 4 -38,1); + } + + @Override + public void close() { + this.client.setScreen(parent); + } + + private void renderBanner(DrawContext context, int x, int y, float alpha) { + RenderSystem.enableBlend(); + + context.drawTexture(Identifier.of(Data.MOD_ID, "textures/gui/banner.png"), + x, y, 0.0f, 0.0f, 128, 72, 128, 72); + + RenderSystem.disableBlend(); + } +} 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..7a64bf2 --- /dev/null +++ b/src/main/java/one/oth3r/sit/screen/ModMenu.java @@ -0,0 +1,11 @@ +package one.oth3r.sit.screen; + +import com.terraformersmc.modmenu.api.ConfigScreenFactory; +import com.terraformersmc.modmenu.api.ModMenuApi; + +public class ModMenu implements ModMenuApi { + @Override + public ConfigScreenFactory getModConfigScreenFactory() { + return ConfigScreen::new; + } +} diff --git a/src/main/java/one/oth3r/sit/screen/TextureButtonWidget.java b/src/main/java/one/oth3r/sit/screen/TextureButtonWidget.java new file mode 100644 index 0000000..122137b --- /dev/null +++ b/src/main/java/one/oth3r/sit/screen/TextureButtonWidget.java @@ -0,0 +1,85 @@ +package one.oth3r.sit.screen; + +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.tooltip.Tooltip; +import net.minecraft.client.gui.widget.ButtonWidget; +import net.minecraft.text.Text; +import net.minecraft.util.Identifier; +import org.jetbrains.annotations.Nullable; + +public class TextureButtonWidget extends ButtonWidget { + //todo gray support + protected final Identifier texture; + protected final int textureWidth; + protected final int textureHeight; + protected final boolean tooltip; + + TextureButtonWidget(int width, int height, Text message, int textureWidth, int textureHeight, Identifier texture, ButtonWidget.PressAction onPress, @Nullable ButtonWidget.NarrationSupplier narrationSupplier, boolean tooltip) { + super(0, 0, width, height, message, onPress, narrationSupplier == null ? DEFAULT_NARRATION_SUPPLIER : narrationSupplier); + this.textureWidth = textureWidth; + this.textureHeight = textureHeight; + this.texture = texture; + this.tooltip = tooltip; + if (tooltip) setTooltip(Tooltip.of(message)); + } + + @Override + public void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) { + super.renderWidget(context, mouseX, mouseY, delta); + int x = this.getX() + this.getWidth() / 2 - this.textureWidth / 2; + int y = this.getY() + this.getHeight() / 2 - this.textureHeight / 2; + context.drawGuiTexture(this.texture, x, y, this.textureWidth, this.textureHeight); + } + + @Override + public void drawMessage(DrawContext context, TextRenderer textRenderer, int color) { + if (!this.tooltip) super.drawMessage(context, textRenderer, color); + } + + + public static class Builder { + private final Text text; + private final ButtonWidget.PressAction onPress; + private final boolean hideText; + private int width = 150; + private int height = 20; + @Nullable + private Identifier texture; + private int textureWidth; + private int textureHeight; + @Nullable + ButtonWidget.NarrationSupplier narrationSupplier; + + public Builder(Text text, ButtonWidget.PressAction onPress, boolean hideText) { + this.text = text; + this.onPress = onPress; + this.hideText = hideText; + } + + public Builder dimensions(int width, int height) { + this.width = width; + this.height = height; + return this; + } + + public Builder texture(Identifier texture, int width, int height) { + this.texture = texture; + this.textureWidth = width; + this.textureHeight = height; + return this; + } + + public Builder narration(ButtonWidget.NarrationSupplier narrationSupplier) { + this.narrationSupplier = narrationSupplier; + return this; + } + + public TextureButtonWidget build() { + if (this.texture == null) { + throw new IllegalStateException("Sprite not set"); + } + return new TextureButtonWidget(width,height,text,textureWidth,textureHeight,texture,onPress,narrationSupplier,hideText); + } + } +} diff --git a/src/main/java/one/oth3r/sit/screen/UnderConstructionScreen.java b/src/main/java/one/oth3r/sit/screen/UnderConstructionScreen.java new file mode 100644 index 0000000..ba418fc --- /dev/null +++ b/src/main/java/one/oth3r/sit/screen/UnderConstructionScreen.java @@ -0,0 +1,79 @@ +package one.oth3r.sit.screen; + +import net.minecraft.client.gui.screen.ConfirmLinkScreen; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.tooltip.Tooltip; +import net.minecraft.client.gui.widget.ButtonWidget; +import net.minecraft.text.Text; +import net.minecraft.util.Identifier; +import net.minecraft.util.Util; +import one.oth3r.sit.file.CustomFile; +import one.oth3r.sit.file.SittingConfig; +import one.oth3r.sit.utl.Data; +import one.oth3r.sit.utl.Utl; + +import java.net.URI; +import java.nio.file.Paths; + +public class UnderConstructionScreen> extends Screen { + protected final Screen parent; + protected T file; + + public UnderConstructionScreen(Screen parent, T file) { + super(Text.translatable("sit!.screen.config")); + this.parent = parent; + this.file = file; + } + + @Override + protected void init() { + int startY = this.height / 5-4; + ButtonWidget foxPNG = this.addDrawableChild(new ClickableImageWidget(70,70,140,140, Tooltip.of(Text.of("Art by @bunnestbun")), + Identifier.of(Data.MOD_ID, "textures/gui/fox.png"), ConfirmLinkScreen.opening(this, "https://www.instagram.com/bunnestbun/"))); + foxPNG.setPosition(this.width / 2 - (foxPNG.getWidth()/2), startY-35); + + ButtonWidget openFileButton = this.addDrawableChild(new ButtonWidget.Builder(Text.translatable("sit!.gui.button.file"), + (button) -> Util.getOperatingSystem().open(this.file.getFile())) + .dimensions(0, 0, 118 ,20).build()); + openFileButton.setPosition(this.width / 2 - 70, startY+110); + + TextureButtonWidget folderButton = this.addDrawableChild(new TextureButtonWidget.Builder(Text.translatable("sit!.gui.button.folder"), + (button) -> Util.getOperatingSystem().open(this.file.getFile().getParent()), true) + .dimensions(20,20).texture(Identifier.of(Data.MOD_ID, "folder"), 15, 15).build()); + folderButton.setPosition(this.width / 2 + 50, startY + 110); + + TextureButtonWidget resetButton = this.addDrawableChild(new TextureButtonWidget.Builder(Text.translatable("sit!.gui.button.reset"), + (button) -> { + this.file.reset(); + this.file.save(); + }, true) + .dimensions(20,20).texture(Identifier.of(Data.MOD_ID, "reset_file"), 15, 15).build()); + resetButton.setPosition(this.width / 2 -70, startY + 135); + + ButtonWidget revertButton = this.addDrawableChild(new ButtonWidget.Builder(Text.translatable("sit!.gui.button.revert"), + (button) -> this.file.save()) + .dimensions(0, 0, 118,20).build()); + revertButton.setPosition(this.width / 2 - 48, startY+135); + + + ButtonWidget saveExitButton = this.addDrawableChild(new ButtonWidget.Builder(Text.translatable("sit!.gui.button.save"), + (button) -> { + this.file.load(); + this.file.save(); + + // send the settings to the server if editing the sitting file and on a supported server + if (this.file instanceof SittingConfig && Data.isSupportedServer()) { + Utl.sendSettingsPackets(); + } + + this.client.setScreen(parent); + }) + .dimensions(0, 0, 140,20).build()); + saveExitButton.setPosition(this.width / 2 - 70, startY+168); + } + + @Override + public void close() { + this.client.setScreen(parent); + } +} diff --git a/src/main/java/one/oth3r/sit/utl/Data.java b/src/main/java/one/oth3r/sit/utl/Data.java new file mode 100644 index 0000000..8c67fde --- /dev/null +++ b/src/main/java/one/oth3r/sit/utl/Data.java @@ -0,0 +1,125 @@ +package one.oth3r.sit.utl; + +import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.entity.decoration.DisplayEntity; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerPlayerEntity; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; + +public class Data { + public static final String MOD_ID = "sit-oth3r"; + public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID); + + public static final String CONFIG_DIR = FabricLoader.getInstance().getConfigDir().toFile()+"/sit!/"; + + public static final String ENTITY_NAME = "-sit!-entity-"; + + // init on server load + private static MinecraftServer server; + + public static MinecraftServer getServer() { + return server; + } + + public static void setServer(MinecraftServer server) { + Data.server = server; + } + + // client booleans + private static boolean client = false; + private static boolean inGame = false; + private static boolean singleplayer = false; + private static boolean supportedServer = false; + + public static boolean isClient() { + return client; + } + + public static void setClient(boolean client) { + Data.client = client; + } + + public static boolean isInGame() { + return inGame; + } + + public static void setInGame(boolean inGame) { + Data.inGame = inGame; + } + + public static boolean isSingleplayer() { + return singleplayer; + } + + public static void setSingleplayer(boolean singleplayer) { + Data.singleplayer = singleplayer; + } + + public static boolean isSupportedServer() { + return supportedServer; + } + + public static void setSupportedServer(boolean supportedServer) { + Data.supportedServer = supportedServer; + } + + /** + * a list of players who just joined, to check if they are mounted to a Sit! entity + * (they don't load in on the player join event for some reason) + */ + 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); + } + + + /** + * a list of players that need a sit entity spawned for them, on the server loop to stop crashing with other mods (ASYNC) + */ + private static final HashMap spawnList = new HashMap<>(); + + public static void setSpawnList(ServerPlayerEntity player, DisplayEntity.TextDisplayEntity entity) { + spawnList.put(player, entity); + } + + public static void removeSpawnList(ServerPlayerEntity player) { + spawnList.remove(player); + } + + public static HashMap getSpawnList() { + return new HashMap<>(spawnList); + } + + /** + * 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); + } +} 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..fbc2241 --- /dev/null +++ b/src/main/java/one/oth3r/sit/utl/Events.java @@ -0,0 +1,217 @@ +package one.oth3r.sit.utl; + +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; +import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +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.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.client.option.KeyBinding; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Formatting; +import one.oth3r.sit.command.SitCommand; +import one.oth3r.sit.file.FileData; +import one.oth3r.sit.file.LangReader; +import one.oth3r.sit.file.SittingConfig; +import one.oth3r.sit.packet.PacketSender; +import one.oth3r.sit.packet.PacketType; +import one.oth3r.sit.screen.ConfigScreen; +import org.lwjgl.glfw.GLFW; + +public class Events { + + private static class Keybindings { + private static KeyBinding toggle_key; + private static KeyBinding sit_key; + private static KeyBinding config__key; + + private static void register() { + toggle_key = KeyBindingHelper.registerKeyBinding(new KeyBinding( + "key.sit!.toggle", + GLFW.GLFW_KEY_UNKNOWN, + "category.sit!" + )); + sit_key = KeyBindingHelper.registerKeyBinding(new KeyBinding( + "key.sit!.sit", + GLFW.GLFW_KEY_UNKNOWN, + "category.sit!" + )); + config__key = KeyBindingHelper.registerKeyBinding(new KeyBinding( + "key.sit!.config", + GLFW.GLFW_KEY_UNKNOWN, + "category.sit!" + )); + } + + private static void loopLogic(MinecraftClient client) { + ClientPlayerEntity player = client.player; + + while (config__key.wasPressed()) { + client.setScreen(new ConfigScreen(client.currentScreen)); + } + + /// anything below uses the player object, make sure it's not null + if (player == null) return; + + while (toggle_key.wasPressed()) { + if (Data.isInGame()) { + player.sendMessage(Logic.toggleSiting(), true); + } + } + + while (sit_key.wasPressed()) { + // just send the sit command + if (Data.isInGame()) { + if (Data.isSupportedServer()) { + player.networkHandler.sendCommand("sit"); + } else { + // unsupported server message if not in a Sit! server + player.sendMessage(Utl.lang("sit!.chat.unsupported") + .formatted(Formatting.RED), true); + } + } + } + } + } + + private static class Packet { + private static void common() { + // server receiver is common + + /// receiving the sitting setting payload + ServerPlayNetworking.registerGlobalReceiver(PacketSender.getIdentifier(PacketType.SETTINGS), + ((server, player, handler, buf, responseSender) -> { + String packetData = PacketSender.getPacketData(buf); + server.execute(() -> { + // save the setting on the server for that player + FileData.setPlayerSetting(player,Utl.getGson().fromJson(packetData, SittingConfig.class)); + + // send the player back a response packet for confirmation + new PacketSender(PacketType.RESPONSE,PacketType.RESPONSE.getId()).sendToPlayer(player); + + // log the receiving of the packet from the player + Data.LOGGER.info(Utl.lang("sit!.console.player_settings",player.getName().getString()).getString()); + }); + })); + } + + private static void client() { + /// receiving the response packet from the server + ClientPlayNetworking.registerGlobalReceiver(PacketSender.getIdentifier(PacketType.RESPONSE), + ((client, handler, buf, responseSender) -> { + String packetData = PacketSender.getPacketData(buf); + client.execute(() -> { + if (!Data.isSupportedServer()) { + Data.setSupportedServer(true); + Data.LOGGER.info(Utl.lang("sit!.console.connected",packetData).getString()); + } + }); + })); + } + } + + private static void clientMisc() { + // client tick loop + ClientTickEvents.END_CLIENT_TICK.register(client -> { + assert client.player != null; + Keybindings.loopLogic(client); + }); + } + + /** + * registers all client connection code + */ + private static void clientConnections() { + ClientPlayConnectionEvents.JOIN.register((handler, sender, client) -> { + Data.setInGame(true); + if (client.isInSingleplayer()) Data.setSingleplayer(true); + // send a data packet whenever joining a server + Utl.sendSettingsPackets(); + }); + + // reset cashed things on disconnect + ClientPlayConnectionEvents.DISCONNECT.register((handler, client) -> { + Data.setInGame(false); + Data.setSingleplayer(false); + Data.setSupportedServer(false); + }); + } + + /** + * registers all common server player connection code + */ + private static void playerConnections() { + // PLAYER JOIN + ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> { + FileData.setPlayerSetting(handler.player, FileData.getSittingConfig()); + Data.setCheckPlayer(handler.player, 5); + }); + + ServerPlayConnectionEvents.DISCONNECT.register((handler, server) -> { + // if keep is off, remove the entity + if (!FileData.getServerConfig().isKeepActive()) { + Logic.removeEntity(handler.player); + } + FileData.removePlayerSetting(handler.player); + }); + } + + /** + * registers all server lifecycle events + */ + private static void serverLifecycle() { + ServerLifecycleEvents.SERVER_STARTED.register(s -> { + Data.setServer(s); + LangReader.loadLanguageFile(); + + // right click on block event + UseBlockCallback.EVENT.register((pl, world, hand, hitResult) -> { + if (Data.isClient() && !Data.isSingleplayer()) return ActionResult.PASS; + // get the server player + ServerPlayerEntity player = Data.getServer().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 the server + Data.setServer(null); + // clear all player settings (singleplayer and such) + FileData.clearPlayerSettings(); + }); + + // server loop setup + ServerTickEvents.END_SERVER_TICK.register(minecraftServer -> minecraftServer.execute(LoopManager::tick)); + + // server command setup + CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> SitCommand.register(dispatcher)); + } + + // a one call method for the common and client + + public static void registerCommon() { + playerConnections(); + serverLifecycle(); + Packet.common(); + } + + public static void registerClient() { + Keybindings.register(); + clientConnections(); + clientMisc(); + Packet.client(); + } +} 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..62556b4 --- /dev/null +++ b/src/main/java/one/oth3r/sit/utl/Logic.java @@ -0,0 +1,186 @@ +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.text.MutableText; +import net.minecraft.util.Formatting; +import net.minecraft.util.Hand; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.math.BlockPos; +import one.oth3r.sit.file.FileData; +import one.oth3r.sit.file.SittingConfig; +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 sitting on a sit entity and sit while seated off, false + if (!FileData.getServerConfig().canSitWhileSeated() && Data.getSitEntity(player) != null) 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; + } + + public static boolean sitLooking(ServerPlayerEntity player) { + return sit(player, Utl.getBlockPosPlayerIsLookingAt(player.getServerWorld(),player,5),null); + } + + /** + * 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) { + SittingConfig sittingConfig = FileData.getPlayerSetting(player); + // if can't sit with hand, false + if (!sittingConfig.canSitWithHand()) return false; + + // a boolean that shows if the player can sit or not + 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 = sittingConfig.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 bound to the player 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); + } + + /** + * spawns a sit entity for the player, they HAVE TO BE in the spawn list + */ + public static void spawnEntity(ServerPlayerEntity player) { + // return if not in the list + if (Data.getSpawnList().get(player) == null) return; + + // if the player is already sitting on a sit entity, remove it before spawning a new one + if (Data.getSitEntity(player) != null) Logic.removeEntity(player); + // get the new entity + DisplayEntity.TextDisplayEntity sitEntity = Data.getSpawnList().get(player); + // spawn and ride the entity + player.getServerWorld().spawnEntity(sitEntity); + player.startRiding(sitEntity); + // add the entity to the list + Data.addSitEntity(player, sitEntity); + // remove the entity from the spawn list + Data.removeSpawnList(player); + } + + /** + * 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), posBelow = new BlockPos(pos); + // 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); + posBelow = posBelow.add(0,-1,0); + } + + // check if both poses are obstructed or not + return Utl.isNotObstructed(entity.getWorld(),pos1) && Utl.isNotObstructed(entity.getWorld(),pos2) + // also check if occupied, checking below to make sure you cant sit directly on top of another sit entity + && Utl.isNotOccupied(pos) && Utl.isNotOccupied(pos1) && Utl.isNotOccupied(pos2) && Utl.isNotOccupied(posBelow); + } + + /** + * reloads the config files + */ + public static void reload() { + FileData.loadFiles(); + FileData.saveFiles(); + } + + /** + * toggles the sit ablity config option + * @return returns a message, that can be sent to the player + */ + public static MutableText toggleSiting() { + if (Data.isSupportedServer()) { + // get the sitting config + SittingConfig config = FileData.getSittingConfig(); + // toggle the setting + config.setEnabled(!config.getEnabled()); + + // set the sitting config to the new value + FileData.setSittingConfig(config); + // save the changes to the file + config.save(); + // send the changes to the server + Utl.sendSettingsPackets(); + + + // get the message settings + String messageKey = "sit!.chat.sit_toggle."+(config.getEnabled()?"on":"off"); + Formatting messageColor = config.getEnabled()?Formatting.GREEN:Formatting.RED; + + // send the player the actionbar message + return Utl.lang("sit!.chat.sit_toggle", + Utl.lang(messageKey).formatted(messageColor)); + } else { + // unsupported server message if not in a Sit! server + return Utl.lang("sit!.chat.unsupported") + .formatted(Formatting.RED); + } + } + +} 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..219cce4 --- /dev/null +++ b/src/main/java/one/oth3r/sit/utl/LoopManager.java @@ -0,0 +1,59 @@ +package one.oth3r.sit.utl; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.decoration.DisplayEntity; +import net.minecraft.server.network.ServerPlayerEntity; + +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 + 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(Data.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); + } + } + } + + // spawn entities for everyone in the spawn list + HashMap spawnList = Data.getSpawnList(); + for (ServerPlayerEntity player : spawnList.keySet()) { + Logic.spawnEntity(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..1242235 --- /dev/null +++ b/src/main/java/one/oth3r/sit/utl/Utl.java @@ -0,0 +1,469 @@ +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.entity.player.PlayerEntity; +import net.minecraft.item.BlockItem; +import net.minecraft.item.ItemStack; +import net.minecraft.registry.Registries; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.text.MutableText; +import net.minecraft.text.Text; +import net.minecraft.text.TextColor; +import net.minecraft.util.*; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.hit.HitResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.RaycastContext; +import net.minecraft.world.World; +import one.oth3r.sit.file.*; +import one.oth3r.sit.packet.PacketSender; +import one.oth3r.sit.packet.PacketType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; + +public class Utl { + + /** + * check if a block is obstructed (no collision) + * @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(); + } + + /** + * checks the list of sit entities and sees if any of them are occupying the block pos + */ + public static boolean isNotOccupied(BlockPos pos) { + return Data.getSitEntities().values().stream().noneMatch(entity -> entity.getBlockPos().equals(pos)); + } + + 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.5; + public static final double CARPET = 0.062; + + /** + * 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 there's nothing + if (itemStack.isEmpty()) return true; + + boolean TRUE = true, FALSE = false; + if (filter.isInverted()) { + TRUE = false; + FALSE = true; + } + + boolean itemcheck = filter.getCustomItems().checkItem(itemStack); + + // iif the item passes the checks, return true + if (itemcheck) 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); + + HandSetting.Filter.Presets presets = filter.getPresets(); + + // try the default conditions + if (presets.isBlock() && itemStack.getItem() instanceof BlockItem) return TRUE; + if (presets.isFood() && food.contains(itemStack.getUseAction())) return TRUE; + if (presets.isUsable() && !notUsable.contains(itemStack.getUseAction())) return TRUE; + + // if nothing else is met, the item is filtered out + return FALSE; + } + + /** + * get a block ID (eg. 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 = FileData.getServerConfig(); + Block block = blockState.getBlock(); + + // make sure that the block that is being sit on has no interaction when hand sitting + if (hit != null && blockIsInList(config.getInteractionBlocks(), blockState)) { + return null; + } + + // only if custom is enabled + if (config.isCustomEnabled()) { + // if the block is on the blacklist, false + if (blockIsInList(config.getBlacklistedBlocks(),blockState)) return null; + + for (SittingBlock sittingBlock : config.getSittingBlocks()) { + // if the block is valid, true + if (sittingBlock.isValid(blockState)) return sittingBlock.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)) return 1.0; + + // at the end, return false + return null; + } + + /** + * checks if a blockstate is in the list provided + * @return + */ + public static boolean blockIsInList(ArrayList blockList, BlockState blockState) { + return blockList.stream().anyMatch(c -> c.isValid(blockState)); + } + + 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 flags + entity.setCustomName(Text.of(Data.ENTITY_NAME)); + entity.setCustomNameVisible(false); + entity.setInvulnerable(true); + entity.setInvisible(true); + + /// make a double for adjusting the entity height if some versions change the player sit height on entities again + double adjustmentY = 0; + + // get the entities y level + double entityY = blockPos.getY(); + entityY += sitHeight + adjustmentY; + + // set the entities position + entity.updatePositionAndAngles(blockPos.getX()+.5, entityY, blockPos.getZ()+.5, 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) { + Data.setSpawnList(player, entity); + } + + /** + * removes all sit entities loaded on the server + */ + public static void purge(ServerPlayerEntity player, boolean message) { + /// FYI it cant 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(Data.ENTITY_NAME)); + + // amount of sit entities purged + int count = 0; + + // remove each one & count + for (DisplayEntity.TextDisplayEntity entity : list) { + remove(entity); + count++; + } + + // send a message if needed + if (message) { + player.sendMessage(messageTag().append(Utl.lang("sit!.chat.purged",Utl.lang("sit!.chat.purged.total",count).styled( + style -> style.withColor(Colors.LIGHT_GRAY).withItalic(true) + )).styled( + style -> style.withColor(Formatting.GREEN) + ))); + } + } + } + + public static MutableText messageTag() { + return Text.literal("[").append(Text.literal("Sit!").styled( + style -> style.withColor(TextColor.parse("#c400ff").result().orElse(TextColor.fromFormatting(Formatting.DARK_PURPLE)))) + ).append("] "); + } + + /** + * gets a MutableText using the language key, if on server, using the custom lang reader + */ + public static MutableText lang(String key, Object... args) { + if (Data.isClient()) return Text.translatable(key, args); + else return LangReader.of(key, args).getTxT(); + } + + 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; + } + } + + /** + * sends the settings packets to the server, if client & in game + */ + public static void sendSettingsPackets() { + if (Data.isClient() && Data.isInGame()) { + new PacketSender(PacketType.SETTINGS, Utl.getGson().toJson(FileData.getSittingConfig())).sendToServer(); + } + } + + /** + * 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 + */ + @SuppressWarnings("unchecked") + public static class LenientTypeAdapterFactory implements TypeAdapterFactory { + public TypeAdapter create(Gson gson, TypeToken type) { + final TypeAdapter delegate = gson.getDelegateAdapter(this, type); + + // Check if the type is a List, then run the custom list type adapter + if (List.class.isAssignableFrom(type.getRawType())) { + Type elementType = ((ParameterizedType) type.getType()).getActualTypeArguments()[0]; + TypeAdapter elementAdapter = gson.getAdapter(TypeToken.get(elementType)); + // the custom adapter + return (TypeAdapter) new RemoveNullListTypeAdapter<>(elementAdapter); + } + + 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; + } + } + }; + } + } + + /** + * type adapter that doesnt allow null / bad entries + */ + private static class RemoveNullListTypeAdapter extends TypeAdapter> { + private final TypeAdapter elementAdapter; + + RemoveNullListTypeAdapter(TypeAdapter elementAdapter) { + this.elementAdapter = elementAdapter; + } + + @Override + public void write(JsonWriter out, List value) throws IOException { + out.beginArray(); + for (E element : value) { + elementAdapter.write(out, element); + } + out.endArray(); + } + + @Override + public List read(JsonReader in) throws IOException { + List list = new ArrayList<>(); + in.beginArray(); + while (in.hasNext()) { + try { + E element = elementAdapter.read(in); + // skip null entry + if (element == null) continue; + list.add(element); + } catch (Exception e) { + // skip invalid entry + in.skipValue(); + } + } + in.endArray(); + return list; + } + } + + public static BlockPos getBlockPosPlayerIsLookingAt(ServerWorld world, PlayerEntity player, double range) { + // pos, adjusted to player eye level + Vec3d rayStart = player.getPos().add(0, player.getEyeHeight(player.getPose()), 0); + // extend ray by the range + Vec3d rayEnd = rayStart.add(player.getRotationVector().multiply(range)); + + BlockHitResult hitResult = world.raycast(new RaycastContext(rayStart, rayEnd, RaycastContext.ShapeType.OUTLINE, RaycastContext.FluidHandling.NONE, ShapeContext.absent())); + + if (hitResult.getType() == HitResult.Type.BLOCK) { + return hitResult.getBlockPos(); + } + + return new BlockPos(player.getBlockPos()); + } +} diff --git a/src/main/resources/assets/sit/icon.png b/src/main/resources/assets/sit-oth3r/icon.png similarity index 100% rename from src/main/resources/assets/sit/icon.png rename to src/main/resources/assets/sit-oth3r/icon.png diff --git a/src/main/resources/assets/sit-oth3r/lang/en_us.json b/src/main/resources/assets/sit-oth3r/lang/en_us.json new file mode 100644 index 0000000..d870855 --- /dev/null +++ b/src/main/resources/assets/sit-oth3r/lang/en_us.json @@ -0,0 +1,107 @@ +{ + "category.sit!": "Sit!", + + "config.entry.exclusion": "Put a `!` in front of a entry to exclude it!", + "config.entry.example": "Entry example: %s", + + "config.server": "Server Config", + "config.server.description": "Configures the server side settings.", + + "config.server.lang": "Language", + "config.server.lang.description": "The language used for the Sit! mod.", + + "config.server.keep-active": "Keep Active", + "config.server.keep-active.description": "Toggles if the Sit! entity should stay, even if the player / server is offline. \n When false, the player will not be sitting when logging back in.", + + "config.server.sit-while-seated": "Sit While Seated", + "config.server.sit-while-seated.description": "Toggles the ability to sit on another Sit! block while already sitting.", + + "config.server.preset-blocks": "Preset Blocks", + "config.server.preset-blocks.description": "Toggles for the default Sit! blocks.", + "config.server.preset-blocks.stairs": "Stairs", + "config.server.preset-blocks.slabs": "Slabs", + "config.server.preset-blocks.carpets": "Carpets", + "config.server.preset-blocks.full-blocks": "Full Blocks", + + "config.server.custom-enabled": "Custom", + "config.server.custom-enabled.description": "Toggles the use of custom blocks for sitting.", + + "config.server.custom-blocks": "Custom Blocks", + "config.server.custom-blocks.description": "The list of custom sitting blocks.", + + "config.server.custom-block.block-ids": "Block IDs", + "config.server.custom-block.block-ids.description": "The block id(s) for the custom sitting block.", + "config.server.custom-block.block-tags": "Block Tags", + "config.server.custom-block.block-tags.description": "The block tag(s) for the custom sitting block.", + "config.server.custom-block.blockstates": "Blockstates", + "config.server.custom-block.blockstates.description": "The blockstates that the block must have to be a custom sitting block.", + "config.server.custom-block.sitting-height": "Sitting Height", + "config.server.custom-block.sitting-height.description": "The player sitting height of the custom block.", + + "config.server.blacklisted-blocks": "Blacklisted Blocks", + "config.server.blacklisted-blocks.description": "The list of blocks that arent allowed to be sat on.", + + + "config.sitting": "Sitting Config", + "config.sitting.description": "Configures the sitting ability, on the server each player can have their own sitting config when they use the mod.", + + "config.sitting.enabled": "Enabled", + "config.sitting.enabled.description": "Toggles the ability to sit.", + + "config.sitting.hand-sitting": "Hand Sitting", + "config.sitting.hand-sitting.description": "Toggles the ability to sit using hand interactions.", + + "config.sitting.hand.main": "Main Hand", + "config.sitting.hand.main.description": "main hand", + "config.sitting.hand.off": "Off Hand", + "config.sitting.hand.off.description": "off hand", + "config.sitting.hand.description": "Configures %s sitting settings.", + + "config.sitting.hand.requirement": "Sitting Requirement", + "config.sitting.hand.requirement.description": "The hand requirement to sit. Eg, if EMPTY, the hand has to be empty", + "config.sitting.hand.requirement.description.none": "No requirement to sit.", + "config.sitting.hand.requirement.description.empty": "The hand has to be empty to sit.", + "config.sitting.hand.requirement.description.filter": "The hand can only sit if the item in the hand matches one of the filters.", + + "config.sitting.hand.filter": "Filter", + "config.sitting.hand.filter.description": "The list of items for the filter hand requirement.", + + "config.sitting.hand.filter.block": "Blocks", + "config.sitting.hand.filter.block.description": "The blocks default filter.", + "config.sitting.hand.filter.food": "Foods", + "config.sitting.hand.filter.food.description": "The foods default filter.", + "config.sitting.hand.filter.usable": "Usables", + "config.sitting.hand.filter.usable.description": "The usables default filter. (Tridents, Shields, Bows)", + + "config.sitting.hand.filter.custom-items": "Custom Items", + "config.sitting.hand.filter.custom-items.description": "A list of custom items to add to the filter.", + "config.sitting.hand.filter.custom-tags": "Custom Tags", + "config.sitting.hand.filter.custom-tags.description": "A list of custom item tags to add to the filter.", + + "sit!.chat.toggle_sit": "%s Sitting!", + "sit!.chat.toggle_sit.on": "Enabled", + "sit!.chat.toggle_sit.off": "Disabled", + "sit!.chat.unsupported": "Sit! is not available on this server.", + "sit!.chat.reloaded": "Reloaded the config!", + "sit!.chat.purged": "Purged all loaded Sit! entities! %s", + "sit!.chat.purged.total": "(%s removed)", + + "key.sit!.toggle": "Toggle Sitting", + "key.sit!.sit": "Sit", + "key.sit!.config": "Open Config", + + "sit!.screen.config": "Sit! Config", + "sit!.gui.button.file": "Open File", + "sit!.gui.button.folder": "Open Folder", + "sit!.gui.button.reset": "Reset", + "sit!.gui.button.issues": "Issues", + "sit!.gui.button.donate": "Donate", + "sit!.gui.button.revert": "Revert Changes", + "sit!.gui.button.save": "Save and Close", + "sit!.gui.button.website": "Website", + + "sit!.console.connected": "Connected to Sit! server: %s", + "sit!.console.player_settings": "Received custom sitting settings from %s!", + + "modmenu.descriptionTranslation.sit-oth3r": "Adds sitting to minecraft! Endless customizability for hand restrictions and sittable blocks.\n Players can have their own sitting settings when using the Sit! client on the server!" +} \ No newline at end of file diff --git a/src/main/resources/assets/sit-oth3r/lang/it_it.json b/src/main/resources/assets/sit-oth3r/lang/it_it.json new file mode 100644 index 0000000..06837ff --- /dev/null +++ b/src/main/resources/assets/sit-oth3r/lang/it_it.json @@ -0,0 +1,83 @@ +{ + "category.sit!": "Sit!", + "config.entry.exclusion": "Metti un `!` davanti a un campo per escluderlo!", + "config.entry.example": "Esempio campo: %s", + "config.server": "Configura Server", + "config.server.description": "Configura altre impostazioni del server.", + "config.server.lang": "Lingua", + "config.server.lang.description": "La lingua usata dalla mod Sit!.", + "config.server.keep-active": "Mantieni Attivo", + "config.server.keep-active.description": "Decidi se l'entità Sit! deve restare, anche se il giocatore / server è offline.", + "config.server.sit-while-seated": "Siediti mentre sei seduto", + "config.server.sit-while-seated.description": "Decidi se togliere la possibilità di sedersi a un blocco Sit! mentre ti siedi.", + "config.server.preset-blocks": "Preset Blocks", + "config.server.preset-blocks.description": "Attiva per i blocchi Sit! default.", + "config.server.preset-blocks.stairs": "Scale", + "config.server.preset-blocks.slabs": "Mezzi blocchi", + "config.server.preset-blocks.carpets": "Tappeti", + "config.server.preset-blocks.full-blocks": "Blocchi interi", + "config.server.custom-enabled": "Custom", + "config.server.custom-enabled.description": "Attiva l'uso di blocchi custom per sederti.", + "config.server.custom-blocks": "Blocchi Custom", + "config.server.custom-blocks.description": "La lista dei blocchi custom per sedersi.", + "config.server.custom-block.block-ids": "ID dei blocchi", + "config.server.custom-block.block-ids.description": "L'id dei blocchi custom per sedersi.", + "config.server.custom-block.block-tags": "Tag dei blocchi", + "config.server.custom-block.block-tags.description": "I tag dei blocchi custom per sedersi.", + "config.server.custom-block.blockstates": "Stato dei blocchi", + "config.server.custom-block.blockstates.description": "Lo stato che un blocco deve avere per potercisi sedere.", + "config.server.custom-block.sitting-height": "Altezza per sedersi", + "config.server.custom-block.sitting-height.description": "L'altezza del giocatore per sedersi sul blocco custom.", + "config.server.blacklisted-blocks": "Blocchi Blacklistati", + "config.server.blacklisted-blocks.description": "La lista dei blocchi su cui non puoi sederti.", + "config.sitting": "Configura Sedersi", + "config.sitting.description": "Configura l'abilità di sedersi, sul server ogni giocatore può avere la propria configurazione quando usano la mod.", + "config.sitting.enabled": "Attivato", + "config.sitting.enabled.description": "Attiva o disattiva l'abilità di sedersi.", + "config.sitting.hand-sitting": "Siediti con la mano", + "config.sitting.hand-sitting.description": "Attiva l'abilità di sedersi usando l'interazione della mano.", + "config.sitting.hand.main": "Mano principale", + "config.sitting.hand.main.description": "mano principale", + "config.sitting.hand.off": "Seconda mano", + "config.sitting.hand.off.description": "seconda mano", + "config.sitting.hand.description": "Configura %s impostazioni per sedersi.", + "config.sitting.hand.requirement": "Requisiti per Sedersi", + "config.sitting.hand.requirement.description": "I requisiti per sedersi. Ad esempio, se EMPTY, la mano deve essere libera", + "config.sitting.hand.requirement.description.none": "Nessun requisito per sedersi.", + "config.sitting.hand.requirement.description.empty": "La mano deve essere libera per sedersi.", + "config.sitting.hand.requirement.description.filter": "La mano può farti sedere solo se il blocco è in un filtro.", + "config.sitting.hand.filter": "Filtro", + "config.sitting.hand.filter.description": "La lista di oggetti nel filtro della mano.", + "config.sitting.hand.filter.block": "Blocchi", + "config.sitting.hand.filter.block.description": "Filtro default per i blocchi.", + "config.sitting.hand.filter.food": "Cibi", + "config.sitting.hand.filter.food.description": "Il filtro dei cibi default.", + "config.sitting.hand.filter.usable": "Usabili", + "config.sitting.hand.filter.usable.description": "La lista del filtro Usabili. (Tridenti, Scudi, Archi)", + "config.sitting.hand.filter.custom-items": "Oggetti custom", + "config.sitting.hand.filter.custom-items.description": "Una lista degli oggetti custom da aggiungere al filtro.", + "config.sitting.hand.filter.custom-tags": "Tag Custom", + "config.sitting.hand.filter.custom-tags.description": "Una lista di tag di oggetti custom da aggiungere al filtro.", + "sit!.chat.toggle_sit": "%s Sedersi!", + "sit!.chat.toggle_sit.on": "Attivato", + "sit!.chat.toggle_sit.off": "Disattivato", + "sit!.chat.unsupported": "Sit! non è disponibile in questo server.", + "sit!.chat.reloaded": "Ricaricata la configurazione!", + "sit!.chat.purged": "Rimosse tutte le entità Sit! caricate! %s", + "sit!.chat.purged.total": "(%s rimosso)", + "key.sit!.toggle": "Attiva Sedersi", + "key.sit!.sit": "Siediti", + "key.sit!.config": "Apri Impostazioni", + "sit!.screen.config": "Impostazioni Sit!", + "sit!.gui.button.file": "Apri File", + "sit!.gui.button.folder": "Apri Cartella", + "sit!.gui.button.reset": "Resetta", + "sit!.gui.button.issues": "Problemi", + "sit!.gui.button.donate": "Dona", + "sit!.gui.button.revert": "Annulla i Cambiamenti", + "sit!.gui.button.save": "Salva ed Esci", + "sit!.gui.button.website": "Sito Web", + "sit!.console.connected": "Connesso al server Sit!: %s", + "sit!.console.player_settings": "Ricevute impostazioni custom da %s!", + "modmenu.descriptionTranslation.sit-oth3r": "Aggiunge l'abilità di sedersi a minecraft! infiniti modi di personalizzare le restrizioni della mano e dei blocchi su cui puoi sederti.\nI giocatori possono avere le loro impostazioni per sedersi quando usano un client Sit! nel server!" +} \ No newline at end of file diff --git a/src/main/resources/assets/sit-oth3r/lang/pt_br.json b/src/main/resources/assets/sit-oth3r/lang/pt_br.json new file mode 100644 index 0000000..6cf4080 --- /dev/null +++ b/src/main/resources/assets/sit-oth3r/lang/pt_br.json @@ -0,0 +1,83 @@ +{ + "category.sit!": "Sit!", + "config.entry.exclusion": "Coloque um `!` antes de uma entrada para excluí-la!", + "config.entry.example": "Exemplo de entrada: %s", + "config.server": "Configuração do Servidor", + "config.server.description": "Configure as configurações do lado do servidor.", + "config.server.lang": "Idioma", + "config.server.lang.description": "A linguagem utilizada para o mod Sit!", + "config.server.keep-active": "Manter Ativo", + "config.server.keep-active.description": "Ativa ou desativa se a entidade Sit! deve permanecer, mesmo que o jogador ou o servidor esteja offline. \nQuando desativado, o jogador não estará sentado ao fazer login novamente.", + "config.server.sit-while-seated": "Sentar Enquanto Está Sentado", + "config.server.sit-while-seated.description": "Ativa ou desativa a capacidade de sentar em outro bloco Sit! enquanto já está sentado.", + "config.server.preset-blocks": "Blocos predefinidos", + "config.server.preset-blocks.description": "Ativa ou desativa os blocos Sit! padrão.", + "config.server.preset-blocks.stairs": "Escadas", + "config.server.preset-blocks.slabs": "Lajes", + "config.server.preset-blocks.carpets": "Tapetes", + "config.server.preset-blocks.full-blocks": "Blocos Inteiros", + "config.server.custom-enabled": "Customizado", + "config.server.custom-enabled.description": "Ativa ou desativa o uso de blocos personalizados para sentar.", + "config.server.custom-blocks": "Blocos Personalizados", + "config.server.custom-blocks.description": "A lista de blocos personalizados para sentar.", + "config.server.custom-block.block-ids": "IDs dos Blocos", + "config.server.custom-block.block-ids.description": "O(s) ID(s) do bloco para o bloco personalizado de sentar.", + "config.server.custom-block.block-tags": "Tags dos Blocos", + "config.server.custom-block.block-tags.description": "A(s) tag(s) do bloco para o bloco personalizado de sentar.", + "config.server.custom-block.blockstates": "Estados do bloco", + "config.server.custom-block.blockstates.description": "Os estados do bloco que o bloco deve ter para ser um bloco personalizado de sentar.", + "config.server.custom-block.sitting-height": "Altura de Sentar", + "config.server.custom-block.sitting-height.description": "A altura de sentar do jogador para o bloco personalizado.", + "config.server.blacklisted-blocks": "Blocos Bloqueados", + "config.server.blacklisted-blocks.description": "A lista de blocos nos quais não é permitido sentar.", + "config.sitting": "Configuração de Sentar", + "config.sitting.description": "Configura a habilidade de sentar; no servidor, cada jogador pode ter sua própria configuração de sentar ao usar o mod.", + "config.sitting.enabled": "Habilitado", + "config.sitting.enabled.description": "Alterna a capacidade de sentar.", + "config.sitting.hand-sitting": "Sentar com as Mãos", + "config.sitting.hand-sitting.description": "Alterna a capacidade de sentar usando interações com a mão.", + "config.sitting.hand.main": "Mão Principal", + "config.sitting.hand.main.description": "mão principal", + "config.sitting.hand.off": "Mão Secundária", + "config.sitting.hand.off.description": "mão secundária", + "config.sitting.hand.description": "Configura as configurações de sentar do %s.", + "config.sitting.hand.requirement": "Requisito para Sentar", + "config.sitting.hand.requirement.description": "A exigência da mão para sentar. Por exemplo, se for VAZIA, a mão deve estar vazia.", + "config.sitting.hand.requirement.description.none": "Nenhum requisito para sentar.", + "config.sitting.hand.requirement.description.empty": "A mão tem que estar vazia para sentar.", + "config.sitting.hand.requirement.description.filter": "A mão só pode sentar se o item na mão corresponder a um dos filtros.", + "config.sitting.hand.filter": "Filtro", + "config.sitting.hand.filter.description": "A lista de itens para a exigência de filtro da mão.", + "config.sitting.hand.filter.block": "Blocos", + "config.sitting.hand.filter.block.description": "O filtro padrão dos blocos.", + "config.sitting.hand.filter.food": "Alimentos", + "config.sitting.hand.filter.food.description": "O filtro padrão dos alimentos.", + "config.sitting.hand.filter.usable": "Usáveis", + "config.sitting.hand.filter.usable.description": "O filtro padrão dos itens utilizáveis. (Tridentes, Escudos, Arcos)", + "config.sitting.hand.filter.custom-items": "Itens Customizados", + "config.sitting.hand.filter.custom-items.description": "Uma lista de itens customizados para adicionar ao filtro.", + "config.sitting.hand.filter.custom-tags": "Tags Customizadas", + "config.sitting.hand.filter.custom-tags.description": "Uma lista de tags de itens customizados para adicionar ao filtro.", + "sit!.chat.toggle_sit": "%s Sentado!", + "sit!.chat.toggle_sit.on": "Habilitado", + "sit!.chat.toggle_sit.off": "Desabilitado", + "sit!.chat.unsupported": "Sit! não está disponível neste servidor.", + "sit!.chat.reloaded": "Configuração recarregada!", + "sit!.chat.purged": "Eliminou todas as entidades Sit! carregadas! %s", + "sit!.chat.purged.total": "(%s removido)", + "key.sit!.toggle": "Alternar Sentar", + "key.sit!.sit": "Sentar", + "key.sit!.config": "Abrir Configuração", + "sit!.screen.config": "Configuração do Sit!", + "sit!.gui.button.file": "Abrir Arquivo", + "sit!.gui.button.folder": "Abrir Pasta", + "sit!.gui.button.reset": "Reiniciar", + "sit!.gui.button.issues": "Problemas", + "sit!.gui.button.donate": "Doar", + "sit!.gui.button.revert": "Reverter Alterações", + "sit!.gui.button.save": "Salvar e Fechar", + "sit!.gui.button.website": "Website", + "sit!.console.connected": "Conectado ao servidor Sit!: %s", + "sit!.console.player_settings": "Recebidas configurações de sentar personalizadas de %s!", + "modmenu.descriptionTranslation.sit-oth3r": "Adiciona a função de sentar ao Minecraft! Personalização infinita para restrições de mão e blocos onde é possível sentar. Os jogadores podem ter suas próprias configurações de sentar ao usar o cliente Sit! no servidor!" +} \ No newline at end of file diff --git a/src/main/resources/assets/sit-oth3r/lang/tr_tr.json b/src/main/resources/assets/sit-oth3r/lang/tr_tr.json new file mode 100644 index 0000000..c1d5bd7 --- /dev/null +++ b/src/main/resources/assets/sit-oth3r/lang/tr_tr.json @@ -0,0 +1,83 @@ +{ + "category.sit!": "Sit!", + "config.entry.exclusion": "Bir girdiyi hariç tutmak için önüne '!' koyun!", + "config.entry.example": "Girdi örneği: %s", + "config.server": "Sunucu Ayarları", + "config.server.description": "Sunucu tarafında ayarları yapılandırır.", + "config.server.lang": "Dil", + "config.server.lang.description": "Sit! modu için kullanılan dil.", + "config.server.keep-active": "Aktif Tut", + "config.server.keep-active.description": "Oyuncu / sunucu offline olduğunda, varlığın aynı halde kalıp kalmayacağını açar kapatır.\nYanlış olarak ayarlandığında, oyuncu giriş yaptığında oturuyor halde olmayacaktır.", + "config.server.sit-while-seated": "Binerken otur", + "config.server.sit-while-seated.description": "Halihazırda oturuyorken başka bir Sit! bloğuna oturma özelliğini açar kapatır.", + "config.server.preset-blocks": "Önceden Ayarlı Bloklar", + "config.server.preset-blocks.description": "Varsayılan Sit! bloklarını açar kapatır.", + "config.server.preset-blocks.stairs": "Merdivenler", + "config.server.preset-blocks.slabs": "Basamaklar", + "config.server.preset-blocks.carpets": "Halılar", + "config.server.preset-blocks.full-blocks": "Tam Bloklar", + "config.server.custom-enabled": "Özel", + "config.server.custom-enabled.description": "Oturma için özel blokların kullanımını açar kapatır.", + "config.server.custom-blocks": "Özel Bloklar", + "config.server.custom-blocks.description": "Özel oturma bloklarının listesi.", + "config.server.custom-block.block-ids": "Blok ID'leri", + "config.server.custom-block.block-ids.description": "Özel oturma bloğu için blok id(leri).", + "config.server.custom-block.block-tags": "Blok Etiketleri", + "config.server.custom-block.block-tags.description": "Özel oturma bloğu için blok etiket(leri).", + "config.server.custom-block.blockstates": "Blok durumu", + "config.server.custom-block.blockstates.description": "Bloğun özel oturma bloğu olması gerektiğini belirten blok durumu.", + "config.server.custom-block.sitting-height": "Oturma Yüksekliği", + "config.server.custom-block.sitting-height.description": "Özel blok için oyuncunun oturma yüksekliği.", + "config.server.blacklisted-blocks": "Karalistedeki Bloklar", + "config.server.blacklisted-blocks.description": "Oturulmasına izin verilmeyen bloklar listesi.", + "config.sitting": "Oturma Ayarları", + "config.sitting.description": "Oturma özelliğini yapılandırır, sunucuda her oyuncu modu kullandığında kendi oturma yapılandırmasına sahip olabilir.", + "config.sitting.enabled": "Aktif", + "config.sitting.enabled.description": "Oturma özelliğini açar kapatır.", + "config.sitting.hand-sitting": "El ile Oturma", + "config.sitting.hand-sitting.description": "El etkileşimlerini kullanarak oturma özelliğini açar kapatır.", + "config.sitting.hand.main": "Ana El", + "config.sitting.hand.main.description": "oyuncunun kullandığı ana el", + "config.sitting.hand.off": "Diğer El", + "config.sitting.hand.off.description": "oyuncunun kullandığı diğer el", + "config.sitting.hand.description": "%s oturma ayarlarını ayarlar.", + "config.sitting.hand.requirement": "Oturma Gereksinimi", + "config.sitting.hand.requirement.description": "Oturmak için el gereksinimi. Ör: eğer BOŞ ise oturmak için elin boş olması gerek", + "config.sitting.hand.requirement.description.none": "Oturmak için gereksinim yok.", + "config.sitting.hand.requirement.description.empty": "Oturmak için elin boş olması gerek.", + "config.sitting.hand.requirement.description.filter": "El ile oturmak için, eldeki öğe filtrelerden biriyle eşleşmelidir.", + "config.sitting.hand.filter": "Filtre", + "config.sitting.hand.filter.description": "El gereksinimi için filtrelenecek eşyaların listesi.", + "config.sitting.hand.filter.block": "Bloklar", + "config.sitting.hand.filter.block.description": "Blokların varsayılan filtresi.", + "config.sitting.hand.filter.food": "Yiyecekler", + "config.sitting.hand.filter.food.description": "Yiyeceklerin varsayılan filtresi.", + "config.sitting.hand.filter.usable": "Kullanılabilenler", + "config.sitting.hand.filter.usable.description": "Kullanılabilenlerin varsayılan listesi. (Mızraklar, Kalkanlar, Yaylar)", + "config.sitting.hand.filter.custom-items": "Özel Eşyalar", + "config.sitting.hand.filter.custom-items.description": "Filtreye eklenecek özel eşyaların listesi.", + "config.sitting.hand.filter.custom-tags": "Özel Etiketler", + "config.sitting.hand.filter.custom-tags.description": "Filtreye eklenecek özel etiketlerin listesi.", + "sit!.chat.toggle_sit": "%s oturuyor!", + "sit!.chat.toggle_sit.on": "Aktif", + "sit!.chat.toggle_sit.off": "Pasif", + "sit!.chat.unsupported": "Sunucuda Sit! modu mevcut değil.", + "sit!.chat.reloaded": "Ayarlamalar yeniden yüklendi!", + "sit!.chat.purged": "Yüklenmiş bütün Sit! canlıları yok edildi! %s", + "sit!.chat.purged.total": "(%s kaldırıldı)", + "key.sit!.toggle": "Oturmayı Aç / Kapat", + "key.sit!.sit": "Otur", + "key.sit!.config": "Ayarlandırmaları aç", + "sit!.screen.config": "Sit! Ayarlandırmaları", + "sit!.gui.button.file": "Dosya Aç", + "sit!.gui.button.folder": "Klasörü Aç", + "sit!.gui.button.reset": "Sıfırla", + "sit!.gui.button.issues": "Sorunlar", + "sit!.gui.button.donate": "Bağış yap", + "sit!.gui.button.revert": "Değişiklikleri Geri Al", + "sit!.gui.button.save": "Kaydet ve Kapat", + "sit!.gui.button.website": "İnternet Sitesi", + "sit!.console.connected": "Sit! sunucusuna bağlanıldı: %s", + "sit!.console.player_settings": "Özel oturma ayarları %s alındı!", + "modmenu.descriptionTranslation.sit-oth3r": "Minecraft'a oturmayı ekler! El kısıtlamaları ve oturulabilir bloklar için sınırsız özelleştirme.\nOyuncular Sit! modunu kendi client'inde kullanırken sunucuda kendi oturma ayarlarına sahip olabilirler!" +} \ No newline at end of file diff --git a/src/main/resources/assets/sit-oth3r/lang/zh_tw.json b/src/main/resources/assets/sit-oth3r/lang/zh_tw.json new file mode 100644 index 0000000..148a76f --- /dev/null +++ b/src/main/resources/assets/sit-oth3r/lang/zh_tw.json @@ -0,0 +1,83 @@ +{ + "category.sit!": "Sit!", + "config.entry.exclusion": "在條目前方加上 `!` 以排除它!", + "config.entry.example": "條目範例: %s", + "config.server": "伺服器設定", + "config.server.description": "設定伺服器端的設定。", + "config.server.lang": "語言", + "config.server.lang.description": "Sit! 模組使用的語言。", + "config.server.keep-active": "保持活動", + "config.server.keep-active.description": "切換坐下! 實體是否應該保留,即使玩家/伺服器離線。 \n 當設定為 false 時,玩家在重新登入時將不會坐下。", + "config.server.sit-while-seated": "坐下時坐下", + "config.server.sit-while-seated.description": "切換在已經坐下的情況下,是否能夠坐在另一個坐下! 方塊上的能力。", + "config.server.preset-blocks": "預設方塊", + "config.server.preset-blocks.description": "預設 Sit! 方塊的切換。", + "config.server.preset-blocks.stairs": "階梯", + "config.server.preset-blocks.slabs": "半磚", + "config.server.preset-blocks.carpets": "地毯", + "config.server.preset-blocks.full-blocks": "完整方塊", + "config.server.custom-enabled": "自訂", + "config.server.custom-enabled.description": "切換是否使用自訂方塊來坐下。", + "config.server.custom-blocks": "自訂方塊", + "config.server.custom-blocks.description": "自訂坐下方塊的清單。", + "config.server.custom-block.block-ids": "方塊 ID", + "config.server.custom-block.block-ids.description": "自訂坐下方塊的方塊 ID。", + "config.server.custom-block.block-tags": "方塊標籤", + "config.server.custom-block.block-tags.description": "自訂坐下方塊的方塊標籤。", + "config.server.custom-block.blockstates": "方塊狀態", + "config.server.custom-block.blockstates.description": "方塊必須具備的方塊狀態,才能成為自訂坐下方塊。", + "config.server.custom-block.sitting-height": "坐下高度", + "config.server.custom-block.sitting-height.description": "自訂方塊的玩家坐下高度。", + "config.server.blacklisted-blocks": "黑名單方塊", + "config.server.blacklisted-blocks.description": "不允許坐下的方塊清單。", + "config.sitting": "坐下設定", + "config.sitting.description": "設定坐下能力,在伺服器上,每個玩家在使用模組時都可以擁有自己的坐下設定。", + "config.sitting.enabled": "啟用", + "config.sitting.enabled.description": "切換坐下能力。", + "config.sitting.hand-sitting": "手持坐下", + "config.sitting.hand-sitting.description": "切換使用手部互動來坐下的能力。", + "config.sitting.hand.main": "慣用手", + "config.sitting.hand.main.description": "慣用手", + "config.sitting.hand.off": "非慣用手", + "config.sitting.hand.off.description": "非慣用手", + "config.sitting.hand.description": "設定 %s 坐下設定。", + "config.sitting.hand.requirement": "坐下需求", + "config.sitting.hand.requirement.description": "坐下的手部需求。例如,如果設定為 EMPTY,則手部必須是空的", + "config.sitting.hand.requirement.description.none": "沒有坐下需求。", + "config.sitting.hand.requirement.description.empty": "手部必須是空的才能坐下。", + "config.sitting.hand.requirement.description.filter": "只有當手持的物品符合其中一個篩選器時,才能坐下。", + "config.sitting.hand.filter": "篩選器", + "config.sitting.hand.filter.description": "篩選器手部需求的物品清單。", + "config.sitting.hand.filter.block": "方塊", + "config.sitting.hand.filter.block.description": "方塊的預設篩選器。", + "config.sitting.hand.filter.food": "食物", + "config.sitting.hand.filter.food.description": "食物的預設篩選器。", + "config.sitting.hand.filter.usable": "可使用物品", + "config.sitting.hand.filter.usable.description": "可使用物品的預設篩選器。(三叉戟、盾牌、弓)", + "config.sitting.hand.filter.custom-items": "自訂物品", + "config.sitting.hand.filter.custom-items.description": "要新增到篩選器的自訂物品清單。", + "config.sitting.hand.filter.custom-tags": "自訂標籤", + "config.sitting.hand.filter.custom-tags.description": "要新增到篩選器的自訂物品標籤清單。", + "sit!.chat.toggle_sit": "%s 坐下!", + "sit!.chat.toggle_sit.on": "已啟用", + "sit!.chat.toggle_sit.off": "已停用", + "sit!.chat.unsupported": "此伺服器不支援坐下! 功能。", + "sit!.chat.reloaded": "設定已重新載入!", + "sit!.chat.purged": "已清除所有已載入的 Sit! 實體! %s", + "sit!.chat.purged.total": "(已移除 %s 個)", + "key.sit!.toggle": "調整設定", + "key.sit!.sit": "坐下", + "key.sit!.config": "開啟設定", + "sit!.screen.config": "坐下! 設定", + "sit!.gui.button.file": "開啟檔案", + "sit!.gui.button.folder": "開啟資料夾", + "sit!.gui.button.reset": "重置", + "sit!.gui.button.issues": "問題", + "sit!.gui.button.donate": "贊助", + "sit!.gui.button.revert": "還原變更", + "sit!.gui.button.save": "儲存並關閉", + "sit!.gui.button.website": "網站", + "sit!.console.connected": "已連線至 Sit! 伺服器: %s", + "sit!.console.player_settings": "已從 %s 收到自訂坐下設定!", + "modmenu.descriptionTranslation.sit-oth3r": "在 Minecraft 中加入坐下的動作!可自訂無盡的動作限制和可坐下的方塊。\n當玩家在伺服器上使用 Sit! 用戶端時,可以擁有自己的坐下設定!" +} \ No newline at end of file diff --git a/src/main/resources/assets/sit-oth3r/textures/gui/banner.png b/src/main/resources/assets/sit-oth3r/textures/gui/banner.png new file mode 100644 index 0000000..f94bc86 Binary files /dev/null and b/src/main/resources/assets/sit-oth3r/textures/gui/banner.png differ diff --git a/src/main/resources/assets/sit-oth3r/textures/gui/fox.png b/src/main/resources/assets/sit-oth3r/textures/gui/fox.png new file mode 100644 index 0000000..3d62c80 Binary files /dev/null and b/src/main/resources/assets/sit-oth3r/textures/gui/fox.png differ diff --git a/src/main/resources/assets/sit-oth3r/textures/gui/sprites/donate.png b/src/main/resources/assets/sit-oth3r/textures/gui/sprites/donate.png new file mode 100644 index 0000000..4a0892c Binary files /dev/null and b/src/main/resources/assets/sit-oth3r/textures/gui/sprites/donate.png differ diff --git a/src/main/resources/assets/sit-oth3r/textures/gui/sprites/file.png b/src/main/resources/assets/sit-oth3r/textures/gui/sprites/file.png new file mode 100644 index 0000000..ba61436 Binary files /dev/null and b/src/main/resources/assets/sit-oth3r/textures/gui/sprites/file.png differ diff --git a/src/main/resources/assets/sit-oth3r/textures/gui/sprites/folder.png b/src/main/resources/assets/sit-oth3r/textures/gui/sprites/folder.png new file mode 100644 index 0000000..98df93f Binary files /dev/null and b/src/main/resources/assets/sit-oth3r/textures/gui/sprites/folder.png differ diff --git a/src/main/resources/assets/sit-oth3r/textures/gui/sprites/issues.png b/src/main/resources/assets/sit-oth3r/textures/gui/sprites/issues.png new file mode 100644 index 0000000..59626c2 Binary files /dev/null and b/src/main/resources/assets/sit-oth3r/textures/gui/sprites/issues.png differ diff --git a/src/main/resources/assets/sit-oth3r/textures/gui/sprites/reset_file.png b/src/main/resources/assets/sit-oth3r/textures/gui/sprites/reset_file.png new file mode 100644 index 0000000..82d9f17 Binary files /dev/null and b/src/main/resources/assets/sit-oth3r/textures/gui/sprites/reset_file.png differ diff --git a/src/main/resources/assets/sit-oth3r/textures/gui/sprites/server.png b/src/main/resources/assets/sit-oth3r/textures/gui/sprites/server.png new file mode 100644 index 0000000..dc6fb9e Binary files /dev/null and b/src/main/resources/assets/sit-oth3r/textures/gui/sprites/server.png differ diff --git a/src/main/resources/assets/sit-oth3r/textures/gui/sprites/server_button.png b/src/main/resources/assets/sit-oth3r/textures/gui/sprites/server_button.png new file mode 100644 index 0000000..e2b6a1d Binary files /dev/null and b/src/main/resources/assets/sit-oth3r/textures/gui/sprites/server_button.png differ diff --git a/src/main/resources/assets/sit-oth3r/textures/gui/sprites/sitting_button.png b/src/main/resources/assets/sit-oth3r/textures/gui/sprites/sitting_button.png new file mode 100644 index 0000000..920bfff Binary files /dev/null and b/src/main/resources/assets/sit-oth3r/textures/gui/sprites/sitting_button.png differ diff --git a/src/main/resources/assets/sit/lang/en_us.json b/src/main/resources/assets/sit/lang/en_us.json deleted file mode 100644 index fe8916c..0000000 --- a/src/main/resources/assets/sit/lang/en_us.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "config.sit.empty": "Empty", - "config.sit.restrictive": "Restrictive", - "config.sit.none": "None", - "config.sit.category.general": "General", - "config.sit.category.general.tooltip": "General settings", - "config.sit.category.main_hand": "Main Hand", - "config.sit.category.main_hand.tooltip": "Main hand settings", - "config.sit.category.off_hand": "Off Hand", - "config.sit.category.off_hand.tooltip": "Off hand settings", - "config.sit.general.keep_active": "Keep Active", - "config.sit.general.keep_active.description": "Keeps the entities active even when logging off / shutting down", - "config.sit.general.sittable": "Sittable Blocks", - "config.sit.general.sittable.description": "Toggle the ability to sit on different block types.", - "config.sit.general.sit_while_seated": "Sit While Seated", - "config.sit.general.sit_while_seated.description": "Toggle the ability to sit on other blocks while already being seated on one.", - "config.sit.general.sittable.stairs": "Stairs", - "config.sit.general.sittable.slabs": "Slabs", - "config.sit.general.sittable.carpets": "Carpets", - "config.sit.general.sittable.full_blocks": "Full Blocks", - "config.sit.general.sittable.custom": "Custom", - "config.sit.general.sittable.custom.description": "Enables adding custom blocks to sit on.", - "config.sit.general.sittable_blocks": "Custom Sittable Blocks", - "config.sit.general.sittable_blocks.description": "Add custom sittable blocks!", - "config.sit.general.sittable_blocks.description_2": "Example: %s", - "config.sit.general.sittable_blocks.description_4": "First entry: custom block", - "config.sit.general.sittable_blocks.description_5": "Second entry: sitting height (number from 0-1 eg 0.52)", - "config.sit.general.sittable_blocks.description_6": "Third entry: hitbox size (where the player spawns above the entity when dismounting)", - "config.sit.general.sittable_blocks.description_7": "Fourth entry (optional): required blockstate to sit (Put a \"!\" to exclude blockstates)", - "config.sit.general.sittable_blocks.description_8": "Separate different entries with \"|\"!", - "config.sit.hand": "Hand Settings", - "config.sit.hand.requirements": "Requirements", - "config.sit.hand.requirements.description": "Hand requirements for sitting.", - "config.sit.hand.requirements.description_2": "Empty = hand has to be empty", - "config.sit.hand.requirements.description_3": "Restrictive = set restrictions for hand state", - "config.sit.hand.requirements.description_4": "None = can sit whenever", - "config.sit.hand.restrictions": "Restrictions", - "config.sit.hand.restrictions.description": "Toggle preset hand restrictions for sitting.", - "config.sit.hand.restrictions.blocks": "Blocks", - "config.sit.hand.restrictions.food": "Food", - "config.sit.hand.restrictions.usable": "Usable", - "config.sit.hand.restrictions.usable.description": "eg. bows, tridents, shield", - "config.sit.hand.whitelist": "Whitelist", - "config.sit.hand.whitelist.description": "Make a custom whitelist for items that the player can use to sit with.", - "config.sit.hand.blacklist": "Blacklist", - "config.sit.hand.blacklist.description": "Make a custom blacklist for items that the player can't use to sit with.", - "config.sit.hand.list.description": "Example: ", - "config.sit.hand.list.description_2": "\"minecraft:torch\"", - "key.sit.command.reloaded": "Reloaded the config!", - "key.sit.command.purged": "Purged all active chair entities!" -} \ No newline at end of file diff --git a/src/main/resources/assets/sit/lang/es_es.json b/src/main/resources/assets/sit/lang/es_es.json deleted file mode 100644 index fa51f3d..0000000 --- a/src/main/resources/assets/sit/lang/es_es.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "config.sit.empty": "Vacía", - "config.sit.restrictive": "Restrictiva", - "config.sit.none": "Ninguna", - "config.sit.category.general": "General", - "config.sit.category.general.tooltip": "Configuración general", - "config.sit.category.main_hand": "Mano principal", - "config.sit.category.main_hand.tooltip": "Ajustes de la mano principal", - "config.sit.category.off_hand": "Mano secundaria", - "config.sit.category.off_hand.tooltip": "Ajustes de la mano secundaria", - "config.sit.general.keep_active": "Mantener activo", - "config.sit.general.keep_active.description": "Mantener entidades activas incluso al cerrar sesión o al apagar el servidor", - "config.sit.general.sittable": "Bloques aptos para sentarse", - "config.sit.general.sittable.description": "Activa/Desactiva los tipos de bloques en los que se puede sentar.", - "config.sit.general.sit_while_seated": "Cambiar de asiento", - "config.sit.general.sit_while_seated.description": "Permite cambiar de asiento estando sentado.", - "config.sit.general.sittable.stairs": "Escaleras", - "config.sit.general.sittable.slabs": "Losas", - "config.sit.general.sittable.carpets": "Alfombras", - "config.sit.general.sittable.full_blocks": "Bloques Enteros", - "config.sit.general.sittable.custom": "Personalizados", - "config.sit.general.sittable.custom.description": "Permitir sentarse en los bloques personalizados.", - "config.sit.general.sittable_blocks": "Bloques Personalizados", - "config.sit.general.sittable_blocks.description": "¡Añadir bloques personalizados para sentarse!", - "config.sit.general.sittable_blocks.description_2": "Ejemplo: %s", - "config.sit.general.sittable_blocks.description_4": "Primera entrada: bloque personalizado", - "config.sit.general.sittable_blocks.description_5": "Segunda entrada: altura del asiento (número entre 0 y 1, por ejemplo, 0.52)", - "config.sit.general.sittable_blocks.description_6": "Tercera entrada: Altura del bloque (en que altura del bloque el jugador aparece al pararse)", - "config.sit.general.sittable_blocks.description_7": "Cuarta entrada (opcional): estado del bloque requerido para sentarse (Usa una exclamación (!) para excluir estados)", - "config.sit.general.sittable_blocks.description_8": "¡Separar diferentes entradas con \"|\"!", - "config.sit.hand": "Configuración de mano", - "config.sit.hand.requirements": "Requisitos", - "config.sit.hand.requirements.description": "Requisitos de la mano para sentarse.", - "config.sit.hand.requirements.description_2": "Vacía = la mano debe estar vacía", - "config.sit.hand.requirements.description_3": "Restrictiva = establecer restricciones para el estado de la mano", - "config.sit.hand.requirements.description_4": "Ninguna = sin restricciones ni requisitos", - "config.sit.hand.restrictions": "Restricciones", - "config.sit.hand.restrictions.description": "Activa/Desactiva las restricciones preestablecidas de la mano para sentarse.", - "config.sit.hand.restrictions.blocks": "Bloques", - "config.sit.hand.restrictions.food": "Comida", - "config.sit.hand.restrictions.usable": "Utilizable", - "config.sit.hand.restrictions.usable.description": "Ejemplos: arcos, tridentes, escudo", - "config.sit.hand.whitelist": "Lista blanca", - "config.sit.hand.whitelist.description": "Crea una lista blanca personalizada para objetos con los que el jugador puede sentarse.", - "config.sit.hand.blacklist": "Lista negra", - "config.sit.hand.blacklist.description": "Crea una lista negra personalizada para objetos con los que el jugador no puede sentarse.", - "config.sit.hand.list.description": "Ejemplo: ", - "config.sit.hand.list.description_2": "\"minecraft:torch\"", - "key.sit.command.reloaded": "¡Configuración recargada!", - "key.sit.command.purged": "¡Se purgaron a todas las entidades activas de sillas!" -} \ No newline at end of file diff --git a/src/main/resources/assets/sit/lang/ru_ru.json b/src/main/resources/assets/sit/lang/ru_ru.json deleted file mode 100644 index 08afb4b..0000000 --- a/src/main/resources/assets/sit/lang/ru_ru.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "config.sit.empty": "Пусто", - "config.sit.restrictive": "Ограничение", - "config.sit.none": "Нет", - "config.sit.category.general": "Общий", - "config.sit.category.general.tooltip": "Основные настройки", - "config.sit.category.main_hand": "Главная рука", - "config.sit.category.main_hand.tooltip": "Настройки главной руки", - "config.sit.category.off_hand": "Левая рука", - "config.sit.category.off_hand.tooltip": "Настройки левой руки", - "config.sit.general.keep_active": "Оставлять активным", - "config.sit.general.keep_active.description": "Оставлять сущности активными даже при выходе из системы / выключении", - "config.sit.general.sittable": "Блоки, для сидения", - "config.sit.general.sittable.description": "Вкл/выкл возможность сидеть на разных типах блоков.", - "config.sit.general.sit_while_seated": "Пересаживание на другой блок", - "config.sit.general.sit_while_seated.description": "Вкл/выкл возможность пересесть на другой блок, когда уже сидишь.", - "config.sit.general.sittable.stairs": "Ступени", - "config.sit.general.sittable.slabs": "Полублоки", - "config.sit.general.sittable.carpets": "Ковры", - "config.sit.general.sittable.full_blocks": "Полные блоки", - "config.sit.general.sittable.custom": "Другие блоки", - "config.sit.general.sittable.custom.description": "Позволяет добавить собственные блоки, на которые можно сесть.", - "config.sit.general.sittable_blocks": "Собственные блоки для сидения", - "config.sit.general.sittable_blocks.description": "Добавь свой блок для сидения!", - "config.sit.general.sittable_blocks.description_2": "Пример: %s", - "config.sit.general.sittable_blocks.description_4": "Первый вход: собственный блок", - "config.sit.general.sittable_blocks.description_5": "Второй вход: высота сидения (число от 0 до 1, например 0.52)", - "config.sit.general.sittable_blocks.description_6": "Третий вход: размер хитбокса (место, где игрок появляется над объектом при спешивании)", - "config.sit.general.sittable_blocks.description_7": "Четвертый вход (опционально): требуемое состояние блока для сидения (Вставьте \"!\" для исключения состояния блоков)", - "config.sit.general.sittable_blocks.description_8": "Разделяйте разные входы знаком \"|\"!", - "config.sit.hand": "Настройки руки", - "config.sit.hand.requirements": "Требования", - "config.sit.hand.requirements.description": "Что нужно, чтобы сесть.", - "config.sit.hand.requirements.description_2": "Пусто = рука должна быть пуста", - "config.sit.hand.requirements.description_3": "Ограниченный = установить ограничения для состояния руки", - "config.sit.hand.requirements.description_4": "Ничего = можно сеть когда угодно", - "config.sit.hand.restrictions": "Ограничения", - "config.sit.hand.restrictions.description": "Вкл/выкл пресет ограничений руки, для возможности сидеть.", - "config.sit.hand.restrictions.blocks": "Блоки", - "config.sit.hand.restrictions.food": "Еда", - "config.sit.hand.restrictions.usable": "Доступный", - "config.sit.hand.restrictions.usable.description": "например луки, трезубцы, щиты", - "config.sit.hand.whitelist": "Белый список", - "config.sit.hand.whitelist.description": "Создай свой белый список предметов, которые игрок может использовать, чтобы сесть.", - "config.sit.hand.blacklist": "Черный список", - "config.sit.hand.blacklist.description": "Создай свой черный список предметов, которые игрок не может использовать, чтобы сесть.", - "config.sit.hand.list.description": "Пример: ", - "config.sit.hand.list.description_2": "\"minecraft:torch\"", - "key.sit.command.reloaded": "Конфигурация перезагружена!", - "key.sit.command.purged": "Очищены все сущности-сидения!" -} \ No newline at end of file diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 95deb70..dd81d2b 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -1,9 +1,9 @@ { "schemaVersion": 1, - "id": "oth3r-sit", + "id": "sit-oth3r", "version": "${version}", "name": "Sit!", - "description": "Adds sitting to minecraft! Endless customizability for hand restrictions and sittable blocks.", + "description": "Adds sitting to minecraft! Endless customizability for hand restrictions and sittable blocks.\n Players can have their own sitting settings when using the Sit! client on the server!", "authors": [ "Oth3r" ], @@ -12,7 +12,7 @@ "sources": "https://github.com/Oth3r/Sit" }, "license": "LGPL-3.0-only", - "icon": "assets/sit/icon.png", + "icon": "assets/sit-oth3r/icon.png", "environment": "*", "entrypoints": { "main": [ @@ -22,17 +22,19 @@ "one.oth3r.sit.SitClient" ], "modmenu": [ - "one.oth3r.sit.ModMenu" + "one.oth3r.sit.screen.ModMenu" ] }, "depends": { "fabricloader": ">=0.14.21", - "minecraft": "=${minecraft_version}", - "java": ">=17", - "fabric-api": "*" + "minecraft": ">=${min_minecraft_version} <=${minecraft_version}", + "fabric": "*" }, "suggests": { - "yet-another-config-lib": "*", "modmenu": "*" - } + }, + "accessWidener": "sit!.accesswidener", + "mixins": [ + "sit!.mixins.json" + ] } \ No newline at end of file diff --git a/src/main/resources/sit!.accesswidener b/src/main/resources/sit!.accesswidener new file mode 100644 index 0000000..605426b --- /dev/null +++ b/src/main/resources/sit!.accesswidener @@ -0,0 +1,3 @@ +accessWidener v2 named + +accessible field net/minecraft/state/State PROPERTY_MAP_PRINTER Ljava/util/function/Function; \ 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..ca325d4 --- /dev/null +++ b/src/main/resources/sit!.mixins.json @@ -0,0 +1,13 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "one.oth3r.sit.mixin", + "compatibilityLevel": "JAVA_17", + "mixins": [ + "ReloadCommandMixin", + "TextDisplayDismountMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} \ No newline at end of file