···88#### Changed
99- the loading logic of the storage file.
1010- most static classes to non-static, should prevent leaks.
1111-- Changed out java Timers for a tick-based timer.
1111+- changed out java Timers for a tick-based timer (so that it follows the server tick speed).
1212+- fixed the before-teleportation effects not being sent into the correct dimension when switching dimension.
12131314#### Added
1415- **a config file!**
1516 - The config file starts at **version 0**!
1617- versioning to the storage file for better handling of migrations.
1718 - We start at **version 1**, all previous version are version 0.
1818-1919+- caching for loading the translation files!
19202021### [1.3.4]
2122- Updated Italian translations (Thanks to [Jump-333](https://github.com/Jump-333)!)
···66public class Constants {
77 public static final String MOD_ID = "teleport_commands";
88 public static final String MOD_NAME = "Teleport Commands";
99- public static final String VERSION = "1.3.4";
99+ public static final String VERSION = "1.4.0";
10101111 public static final Logger LOGGER = LoggerFactory.getLogger(MOD_NAME);
1212}
···44import dev.mrsnowy.teleport_commands.storage.StorageManager;
55import dev.mrsnowy.teleport_commands.commands.*;
66import dev.mrsnowy.teleport_commands.storage.DeathLocationStorage;
77-import dev.mrsnowy.teleport_commands.storage.configManager;
88-import dev.mrsnowy.teleport_commands.utils.teleporter;
77+import dev.mrsnowy.teleport_commands.storage.ConfigManager;
88+import dev.mrsnowy.teleport_commands.utils.Teleporter;
99import net.minecraft.commands.CommandSourceStack;
1010import net.minecraft.server.MinecraftServer;
1111import net.minecraft.server.level.ServerPlayer;
···2222 public Path configDir;
2323 public MinecraftServer server;
2424 public StorageManager storageManager;
2525- public configManager config;
2525+ public ConfigManager config;
2626 public DeathLocationStorage deathLocationStorage;
2727- public teleporter teleporter;
2727+ public Teleporter teleporter;
28282929- // Gets ran when the server starts, initializes the mod :3
2929+ /// Gets ran when the server starts, initializes the mod :3
3030 public void initializeMod(MinecraftServer server) {
3131 INSTANCE = this;
3232 Constants.LOGGER.info("Initializing Teleport Commands (V{})! Hello {}!", Constants.VERSION, modLoader);
···3636 this.server = server;
37373838 storageManager = new StorageManager(this);
3939- config = new configManager(this);
3939+ config = new ConfigManager(this);
4040 deathLocationStorage = new DeathLocationStorage();
4141- teleporter = new teleporter(this);
4141+ teleporter = new Teleporter(this);
4242 }
43434444- // initialize commands, also allows me to easily disable any when there is a config
4444+ /// initialize commands, also allows me to easily disable any when there is a config
4545 public void registerCommands(CommandDispatcher<CommandSourceStack> dispatcher) {
4646- new back(dispatcher, this);
4646+ back.register(dispatcher);
4747 home.register(dispatcher);
4848 new tpa(dispatcher, this);
4949 new warp(dispatcher, this);
···5151 main.register(dispatcher);
5252 }
53535454- // Runs when the playerDeath mixin calls it, updates the /back command position
5454+ /// Runs when the playerDeath mixin calls it, updates the /back command position
5555 public void onPlayerDeath(ServerPlayer player) {
5656 BlockPos pos = new BlockPos(player.getBlockX(), player.getBlockY(), player.getBlockZ());
5757 String world = player.serverLevel().dimension().location().toString();
···11+package dev.mrsnowy.teleport_commands.utils;
22+33+import com.google.gson.*;
44+55+import java.io.*;
66+import java.util.*;
77+import java.util.stream.StreamSupport;
88+99+import net.minecraft.ChatFormatting;
1010+import net.minecraft.core.BlockPos;
1111+import net.minecraft.network.chat.ClickEvent;
1212+import net.minecraft.network.chat.Component;
1313+import net.minecraft.server.MinecraftServer;
1414+import net.minecraft.server.level.ServerLevel;
1515+import net.minecraft.server.level.ServerPlayer;
1616+1717+import static dev.mrsnowy.teleport_commands.utils.Language.getTranslation;
1818+1919+2020+public class Tools {
2121+ private static final Set<String> unsafeCollisionFreeBlocks = Set.of("block.minecraft.lava", "block.minecraft.flowing_lava", "block.minecraft.end_portal", "block.minecraft.end_gateway","block.minecraft.fire", "block.minecraft.soul_fire", "block.minecraft.powder_snow", "block.minecraft.nether_portal");
2222+2323+ // checks a 7x7x7 location around the player in order to find a safe place to teleport them to.
2424+ public static Optional<BlockPos> getSafeBlockPos(BlockPos blockPos, ServerLevel world) {
2525+ int row = 1;
2626+ int rows = 3;
2727+2828+ int blockPosX = blockPos.getX();
2929+ int blockPosY = blockPos.getY();
3030+ int blockPosZ = blockPos.getZ();
3131+3232+ if (isBlockPosSafe(blockPos, world)) {
3333+ return Optional.of(blockPos); // safe location found!
3434+3535+ } else {
3636+ // find a safe location in an x row radius
3737+ while (row <= rows) {
3838+ // TeleportCommands.LOGGER.info("currently doing row " + row + " of " + rows); //debug
3939+ for (int z = -row; z <= row; z++) {
4040+ for (int x = -row; x <= row; x++) {
4141+ for (int y = -row; y <= row; y++) {
4242+4343+ // checks if we are on the outer layer of the row, not on the inside
4444+ if ((x == -row || x == row) || (z == -row || z == row) || (y == -row || y == row)) {
4545+4646+ // calculate a new blockPos based on the offset we generated
4747+ BlockPos newPos = new BlockPos(blockPosX + x, blockPosY + y, blockPosZ + z);
4848+4949+ if (isBlockPosSafe(newPos, world)) {
5050+// return Optional.of(new Vec3(newPos.getX() + 0.5, newPos.getY(), newPos.getZ() + 0.5)); // safe location found!
5151+ return Optional.of(newPos);
5252+ }
5353+ }
5454+ }
5555+ }
5656+ }
5757+5858+ row++;
5959+ }
6060+6161+ // no safe location
6262+ return Optional.empty(); // no safe location found!
6363+ }
6464+ }
6565+6666+ public static void sendSafetyWarning(ServerPlayer player, String command) {
6767+ // asks the player if they want to teleport anyway
6868+ player.displayClientMessage(
6969+ Component.empty()
7070+ .append(getTranslation("commands.teleport_commands.common.noSafeLocation", player)
7171+ .withStyle(ChatFormatting.RED, ChatFormatting.BOLD)
7272+ )
7373+ .append("\n")
7474+ .append(getTranslation("commands.teleport_commands.common.safetyIsForLosers", player)
7575+ .withStyle(ChatFormatting.WHITE)
7676+ )
7777+ .append("\n")
7878+ .append(getTranslation("commands.teleport_commands.common.forceTeleport", player)
7979+ .withStyle(ChatFormatting.DARK_AQUA, ChatFormatting.BOLD)
8080+ .withStyle(style -> style.withClickEvent(new ClickEvent.RunCommand(command)))
8181+ )
8282+ .append("\n"), false);
8383+ }
8484+8585+ // Gets the ids of all the worlds
8686+ public static List<String> getWorldIds(MinecraftServer server) {
8787+ return StreamSupport.stream(server.getAllLevels().spliterator(), false)
8888+ .map(level -> level.dimension().location().toString())
8989+ .toList();
9090+ }
9191+9292+9393+ // checks if a BlockPos is safe, used by the teleportSafetyChecker.
9494+ private static boolean isBlockPosSafe(BlockPos bottomPlayer, ServerLevel world) {
9595+9696+ // get the block below the player
9797+ BlockPos belowPlayer = new BlockPos(bottomPlayer.getX(), bottomPlayer.getY() -1, bottomPlayer.getZ()); // below the player
9898+ String belowPlayerId = world.getBlockState(belowPlayer).getBlock().getDescriptionId(); // below the player
9999+100100+ // get the bottom of the player
101101+ String BottomPlayerId = world.getBlockState(bottomPlayer).getBlock().getDescriptionId(); // bottom of player
102102+103103+ // get the top of the player
104104+ BlockPos TopPlayer = new BlockPos(bottomPlayer.getX(), bottomPlayer.getY() + 1, bottomPlayer.getZ()); // top of player
105105+ String TopPlayerId = world.getBlockState(TopPlayer).getBlock().getDescriptionId(); // top of player
106106+107107+108108+ // check if the block position isn't safe
109109+ if ((belowPlayerId.equals("block.minecraft.water") || !world.getBlockState(belowPlayer).getCollisionShape(world, belowPlayer).isEmpty()) // check if the player is going to fall on teleport
110110+ && (world.getBlockState(bottomPlayer).getCollisionShape(world, bottomPlayer).isEmpty() && !unsafeCollisionFreeBlocks.contains(BottomPlayerId)) // check if it is a collision free block that isn't dangerous
111111+ && (!unsafeCollisionFreeBlocks.contains(TopPlayerId))) // check if it is a dangerous collision free block, if it is solid then the player crawls
112112+ {
113113+ return true; // it's safe
114114+ }
115115+ return false; // it's not safe!
116116+ }
117117+118118+ /// This function reloads "reloadable resources" which includes commands
119119+ public static void reloadResources(MinecraftServer server) {
120120+ Collection<String> collection = server.getPackRepository().getSelectedIds();
121121+ server.reloadResources(collection);
122122+ }
123123+}
···11-package dev.mrsnowy.teleport_commands.utils;
22-33-import com.google.gson.*;
44-import dev.mrsnowy.teleport_commands.Constants;
55-import dev.mrsnowy.teleport_commands.TeleportCommands;
66-77-import java.io.*;
88-import java.nio.charset.StandardCharsets;
99-import java.util.*;
1010-import java.util.regex.Matcher;
1111-import java.util.regex.Pattern;
1212-import java.util.stream.StreamSupport;
1313-1414-import net.minecraft.core.BlockPos;
1515-import net.minecraft.network.chat.Component;
1616-import net.minecraft.network.chat.MutableComponent;
1717-import net.minecraft.server.MinecraftServer;
1818-import net.minecraft.server.level.ServerLevel;
1919-import net.minecraft.server.level.ServerPlayer;
2020-2121-import static dev.mrsnowy.teleport_commands.Constants.MOD_ID;
2222-2323-2424-public class tools {
2525- private static final Set<String> unsafeCollisionFreeBlocks = Set.of("block.minecraft.lava", "block.minecraft.flowing_lava", "block.minecraft.end_portal", "block.minecraft.end_gateway","block.minecraft.fire", "block.minecraft.soul_fire", "block.minecraft.powder_snow", "block.minecraft.nether_portal");
2626-2727- // checks a 7x7x7 location around the player in order to find a safe place to teleport them to.
2828- public static Optional<BlockPos> getSafeBlockPos(BlockPos blockPos, ServerLevel world) {
2929- int row = 1;
3030- int rows = 3;
3131-3232- int blockPosX = blockPos.getX();
3333- int blockPosY = blockPos.getY();
3434- int blockPosZ = blockPos.getZ();
3535-3636- if (isBlockPosSafe(blockPos, world)) {
3737- return Optional.of(blockPos); // safe location found!
3838-3939- } else {
4040- // find a safe location in an x row radius
4141- while (row <= rows) {
4242- // TeleportCommands.LOGGER.info("currently doing row " + row + " of " + rows); //debug
4343- for (int z = -row; z <= row; z++) {
4444- for (int x = -row; x <= row; x++) {
4545- for (int y = -row; y <= row; y++) {
4646-4747- // checks if we are on the outer layer of the row, not on the inside
4848- if ((x == -row || x == row) || (z == -row || z == row) || (y == -row || y == row)) {
4949-5050- // calculate a new blockPos based on the offset we generated
5151- BlockPos newPos = new BlockPos(blockPosX + x, blockPosY + y, blockPosZ + z);
5252-5353- if (isBlockPosSafe(newPos, world)) {
5454-// return Optional.of(new Vec3(newPos.getX() + 0.5, newPos.getY(), newPos.getZ() + 0.5)); // safe location found!
5555- return Optional.of(newPos);
5656- }
5757- }
5858- }
5959- }
6060- }
6161-6262- row++;
6363- }
6464-6565- // no safe location
6666- return Optional.empty(); // no safe location found!
6767- }
6868- }
6969-7070-7171- // Gets the translated text for each player based on their language, this is fully server side and actually works (UNLIKE MOJANG'S TRANSLATED KEY'S WHICH ARE CLIENT SIDE) (I'm not mad, I swear!)
7272- public static MutableComponent getTranslatedText(String key, ServerPlayer player, MutableComponent... args) {
7373- //todo! maybe make this also loaded in memory?
7474- String language = player.clientInformation().language().toLowerCase();
7575- String regex = "%(\\d+)%";
7676- Pattern pattern = Pattern.compile(regex);
7777-7878-// MinecraftServer server = player.getServer();
7979-8080-// MinecraftServer.ServerResourcePackInfo silly2 = server.getResourceManager().listResources()
8181-8282-// java.util.stream.Stream<net.minecraft.server.packs.PackResources> SILLY = server.getResourceManager().listPacks();
8383-//
8484-// SILLY.forEach(pack -> {
8585-// Constants.LOGGER.info("{} : {} : {}", pack.packId(), pack.location(), pack.getClass());
8686-// });
8787-8888-// player.displayClientMessage(Component.literal(.toString()), false);
8989-9090- // the try catch stuff is so wacky, but it works fine and I don't need to check everything
9191- try {
9292- String filePath = String.format("/assets/%s/lang/%s.json", MOD_ID, language);
9393- InputStream stream = TeleportCommands.class.getResourceAsStream(filePath);
9494-9595- Reader reader = new InputStreamReader(Objects.requireNonNull(stream, String.format("Couldn't find the required language file for \"%s\"", language)), StandardCharsets.UTF_8);
9696- JsonElement json = JsonParser.parseReader(reader);
9797- String translation = json.getAsJsonObject().get(key).getAsString();
9898-9999-100100- // Adds the optional MutableComponents in the correct places
101101- Matcher matcher = pattern.matcher(Objects.requireNonNull(translation));
102102-103103- MutableComponent component = Component.literal("");
104104- int lastIndex = 0;
105105-106106- while (matcher.find()) {
107107- component.append(Component.literal(translation.substring(lastIndex, matcher.start())));
108108-109109- int index = Integer.parseInt(matcher.group(1));
110110- component.append(args[index]);
111111-112112- lastIndex = matcher.end();
113113- }
114114- component.append(translation.substring(lastIndex));
115115-116116- return component;
117117-118118- } catch (Exception e) {
119119-120120- try {
121121- if (!Objects.equals(language, "en_us")) {
122122-// TeleportCommands.LOGGER.warn("Key \"{}\" not found in the language: {}, falling back to default (en_us)", key, language);
123123-124124- String filePath = String.format("/assets/%s/lang/en_us.json", MOD_ID);
125125- InputStream stream = TeleportCommands.class.getResourceAsStream(filePath);
126126-127127- Reader reader = new InputStreamReader(Objects.requireNonNull(stream, String.format("Couldn't find the required language file for \"%s\"", language)), StandardCharsets.UTF_8);
128128- JsonElement json = JsonParser.parseReader(reader);
129129- String translation = json.getAsJsonObject().get(key).getAsString();
130130-131131- // Adds the optional MutableComponents in the correct places
132132- Matcher matcher = pattern.matcher(Objects.requireNonNull(translation, "translation cannot be null"));
133133-134134- MutableComponent component = Component.literal("");
135135- int lastIndex = 0;
136136-137137- while (matcher.find()) {
138138- component.append(Component.literal(translation.substring(lastIndex, matcher.start())));
139139-140140- int index = Integer.parseInt(matcher.group(1));
141141- component.append(args[index]);
142142-143143- lastIndex = matcher.end();
144144- }
145145- component.append(translation.substring(lastIndex));
146146-147147- return component;
148148- }
149149- } catch (Exception ignored1) {}
150150- Constants.LOGGER.error("Key \"{}\" not found in the default language (en_us), sending raw key as fallback.", key);
151151- return Component.literal(key);
152152- }
153153- }
154154-155155-156156- // Gets the ids of all the worlds
157157- public static List<String> getWorldIds(MinecraftServer server) {
158158- return StreamSupport.stream(server.getAllLevels().spliterator(), false)
159159- .map(level -> level.dimension().location().toString())
160160- .toList();
161161- }
162162-163163-164164- // checks if a BlockPos is safe, used by the teleportSafetyChecker.
165165- private static boolean isBlockPosSafe(BlockPos bottomPlayer, ServerLevel world) {
166166-167167- // get the block below the player
168168- BlockPos belowPlayer = new BlockPos(bottomPlayer.getX(), bottomPlayer.getY() -1, bottomPlayer.getZ()); // below the player
169169- String belowPlayerId = world.getBlockState(belowPlayer).getBlock().getDescriptionId(); // below the player
170170-171171- // get the bottom of the player
172172- String BottomPlayerId = world.getBlockState(bottomPlayer).getBlock().getDescriptionId(); // bottom of player
173173-174174- // get the top of the player
175175- BlockPos TopPlayer = new BlockPos(bottomPlayer.getX(), bottomPlayer.getY() + 1, bottomPlayer.getZ()); // top of player
176176- String TopPlayerId = world.getBlockState(TopPlayer).getBlock().getDescriptionId(); // top of player
177177-178178-179179- // check if the block position isn't safe
180180- if ((belowPlayerId.equals("block.minecraft.water") || !world.getBlockState(belowPlayer).getCollisionShape(world, belowPlayer).isEmpty()) // check if the player is going to fall on teleport
181181- && (world.getBlockState(bottomPlayer).getCollisionShape(world, bottomPlayer).isEmpty() && !unsafeCollisionFreeBlocks.contains(BottomPlayerId)) // check if it is a collision free block that isn't dangerous
182182- && (!unsafeCollisionFreeBlocks.contains(TopPlayerId))) // check if it is a dangerous collision free block, if it is solid then the player crawls
183183- {
184184- return true; // it's safe
185185- }
186186- return false; // it's not safe!
187187- }
188188-189189- /// This function reloads "reloadable resources" which includes commands
190190- public static void reloadResources(MinecraftServer server) {
191191- Collection<String> collection = server.getPackRepository().getSelectedIds();
192192- server.reloadResources(collection);
193193- }
194194-}
···22# Every field you add must be added to the root build.gradle expandProps map.
3344# Project
55-version=1.3.4
55+version=1.4.0
66group=dev.mrsnowy.teleport_commands
77java_version=21
88