Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 24 additions & 5 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,36 @@
# against bad commits.

name: build
on: [pull_request, push]

on:
pull_request:
paths: &build_paths
- '.github/workflows/build.yml'
- 'build.gradle'
- 'gradle.properties'
- 'gradle/**'
- 'gradlew'
- 'gradlew.bat'
- 'settings.gradle'
- 'src/**'

push:
branches: [main]
paths: *build_paths

concurrency:
group: "java-build-${{ github.ref }}"
cancel-in-progress: true

jobs:
build:
strategy:
matrix:
# Use these Java versions
java: [
21, # Current Java LTS
25, # Current Java LTS
]
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- name: checkout repository
uses: actions/checkout@v4
Expand All @@ -30,8 +49,8 @@ jobs:
- name: build
run: ./gradlew build
- name: capture build artifacts
if: ${{ matrix.java == '21' }} # Only upload artifacts built from latest java
if: ${{ matrix.java == '25' }} # Only upload artefacts built from latest java
uses: actions/upload-artifact@v4
with:
name: Artifacts
path: build/libs/
path: build/libs/
15 changes: 7 additions & 8 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
plugins {
id 'fabric-loom' version "${loom_version}"
id 'net.fabricmc.fabric-loom' version "${loom_version}"
id 'maven-publish'
}

Expand All @@ -21,11 +21,10 @@ repositories {
dependencies {
// To change the versions see the gradle.properties file
minecraft "com.mojang:minecraft:${project.minecraft_version}"
mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
implementation "net.fabricmc:fabric-loader:${project.loader_version}"

// Fabric API. This is technically optional, but you probably want it anyway.
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
implementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_api_version}"

}

Expand All @@ -38,7 +37,7 @@ processResources {
}

tasks.withType(JavaCompile).configureEach {
it.options.release = 21
it.options.release = 25
}

java {
Expand All @@ -47,8 +46,8 @@ java {
// If you remove this line, sources will not be generated.
withSourcesJar()

sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
sourceCompatibility = JavaVersion.VERSION_25
targetCompatibility = JavaVersion.VERSION_25
}

jar {
Expand All @@ -75,4 +74,4 @@ publishing {
// The repositories here will be used for publishing your artifact, not for
// retrieving dependencies.
}
}
}
7 changes: 3 additions & 4 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@ org.gradle.parallel=true

# Fabric Properties
# check these on https://fabricmc.net/develop
minecraft_version=1.21.11
yarn_mappings=1.21.11+build.3
minecraft_version=26.1-snapshot-3
loader_version=0.18.4
loom_version=1.14-SNAPSHOT

# Mod Properties
mod_version=1.0.4+1.21.11
mod_version=1.0.5-beta+26.1-snapshots
maven_group=me.imgalvin.playerfinder
archives_base_name=player-finder

# Dependencies
fabric_version=0.140.2+1.21.11
fabric_api_version=0.142.0+26.1
109 changes: 66 additions & 43 deletions src/main/java/me/imgalvin/playerfinder/PlayerFinder.java
Original file line number Diff line number Diff line change
@@ -1,55 +1,78 @@
package me.imgalvin.playerfinder;

import com.mojang.brigadier.arguments.StringArgumentType;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.registry.RegistryKey;
import net.minecraft.server.command.CommandManager;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraft.ChatFormatting;
import net.minecraft.commands.Commands;
import net.minecraft.commands.arguments.EntityArgument;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.level.Level;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class PlayerFinder implements ModInitializer {
PlayerFinderUtils utils = new PlayerFinderUtils();

public static final Logger LOGGER = LoggerFactory.getLogger("PlayerFinder");

@Override
public void onInitialize() {
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> {
dispatcher.register(CommandManager.literal("findplayer")
.then(CommandManager.argument("player", StringArgumentType.string())
.suggests(new PlayerSuggestionProvider())
.executes(context -> {
String playerName = StringArgumentType.getString(context, "player");
PlayerEntity targetPlayer = context.getSource().getServer().getPlayerManager().getPlayer(playerName);
PlayerEntity sourcePlayer = context.getSource().getPlayer();

assert targetPlayer != null;
assert sourcePlayer != null;

BlockPos targetBlockPos = targetPlayer.getBlockPos();
BlockPos sourceBlockPos = sourcePlayer.getBlockPos();
RegistryKey<World> playerDimension = targetPlayer.getEntityWorld().getRegistryKey();
RegistryKey<World> sourceDimension = sourcePlayer.getEntityWorld().getRegistryKey();

boolean isSameDimension = sourceDimension == playerDimension;

context.getSource().sendFeedback(() -> (Text) Text.literal(playerName + " is at ")
.append(Text.literal(targetBlockPos.getX() + ", " + targetBlockPos.getY() + ", " + targetBlockPos.getZ())
.formatted(utils.getDimensionColor(playerDimension)))
.append(Text.literal(" in the ")
.formatted(Formatting.WHITE))
.append(Text.literal(utils.getDimensionText(playerDimension))
.formatted(utils.getDimensionColor(playerDimension)))
.append(Text.literal(isSameDimension
? " (" + utils.getDistance(sourceBlockPos, targetBlockPos) + " blocks away)"
: " (Player is in a different dimension)")
.formatted(isSameDimension ? Formatting.GREEN : Formatting.RED)), false);
return 1;
})
)
);
});
LOGGER.info("PlayerFinder initialized!");
// _ previously registryAccess, environment
CommandRegistrationCallback.EVENT.register((dispatcher, _, _) -> dispatcher.register(Commands.literal("findplayer")
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lambda parameters are named _, but a single underscore is a reserved keyword in Java (since Java 9), so this will not compile. Rename these unused parameters to something like registryAccess/environment or ignoredRegistryAccess/ignoredEnvironment.

Suggested change
CommandRegistrationCallback.EVENT.register((dispatcher, _, _) -> dispatcher.register(Commands.literal("findplayer")
CommandRegistrationCallback.EVENT.register((dispatcher, ignoredRegistryAccess, ignoredEnvironment) -> dispatcher.register(Commands.literal("findplayer")

Copilot uses AI. Check for mistakes.
.then(Commands.argument("player", EntityArgument.player())
.executes(context -> {
LOGGER.info("Executing /findplayer command");

ServerPlayer targetPlayerName = EntityArgument.getPlayer(context, "player");
String playerName = targetPlayerName.getGameProfile().name();

ServerPlayer targetPlayer = context.getSource().getServer().getPlayerList().getPlayer(playerName);
ServerPlayer sourcePlayer = context.getSource().getServer().getPlayerList().getPlayer(context.getSource().getTextName());

Comment on lines +33 to +35
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking up sourcePlayer by context.getSource().getTextName() is fragile and also breaks for non-player command sources. Prefer using the command source API to obtain the executing player directly (e.g., getPlayer() / getPlayerOrException() depending on mappings), and handle the exception or allow console execution explicitly.

Copilot uses AI. Check for mistakes.
if (targetPlayer == null) {
context.getSource().sendSystemMessage(Component.literal("[PlayerFinder ERROR] Player " + playerName + " not found").withStyle(ChatFormatting.RED));
return 0;
}
Comment on lines +30 to +39
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EntityArgument.getPlayer(context, "player") already returns the resolved ServerPlayer, but the code then extracts a name and does a second lookup via getPlayerList().getPlayer(...). This is redundant and can produce null/incorrect results if the player state changes; use the ServerPlayer returned by the argument directly.

Suggested change
ServerPlayer targetPlayerName = EntityArgument.getPlayer(context, "player");
String playerName = targetPlayerName.getGameProfile().name();
ServerPlayer targetPlayer = context.getSource().getServer().getPlayerList().getPlayer(playerName);
ServerPlayer sourcePlayer = context.getSource().getServer().getPlayerList().getPlayer(context.getSource().getTextName());
if (targetPlayer == null) {
context.getSource().sendSystemMessage(Component.literal("[PlayerFinder ERROR] Player " + playerName + " not found").withStyle(ChatFormatting.RED));
return 0;
}
ServerPlayer targetPlayer = EntityArgument.getPlayer(context, "player");
String playerName = targetPlayer.getGameProfile().getName();
ServerPlayer sourcePlayer = context.getSource().getServer().getPlayerList().getPlayer(context.getSource().getTextName());

Copilot uses AI. Check for mistakes.
if (sourcePlayer == null) {
context.getSource().sendSystemMessage(Component.literal("[PlayerFinder ERROR] Could not determine command source player").withStyle(ChatFormatting.RED));
return 0;
}

BlockPos targetBlockPos = targetPlayer.blockPosition();
BlockPos sourceBlockPos = sourcePlayer.blockPosition();

LOGGER.info("Target player position: {}", targetBlockPos);
LOGGER.info("Source player position: {}", sourceBlockPos);

ResourceKey<Level> playerDimension = targetPlayer.level().getLevel().dimension();
ResourceKey<Level> sourceDimension = sourcePlayer.level().getLevel().dimension();
Comment on lines +51 to +52
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dimension key retrieval uses targetPlayer.level().getLevel().dimension(). For a ServerPlayer, level() already returns the level instance; the extra getLevel() call is likely invalid and will not compile. Use the level API directly to get the dimension key (e.g., targetPlayer.level().dimension()).

Suggested change
ResourceKey<Level> playerDimension = targetPlayer.level().getLevel().dimension();
ResourceKey<Level> sourceDimension = sourcePlayer.level().getLevel().dimension();
ResourceKey<Level> playerDimension = targetPlayer.level().dimension();
ResourceKey<Level> sourceDimension = sourcePlayer.level().dimension();

Copilot uses AI. Check for mistakes.

LOGGER.info("Target player dimension: {}", playerDimension);
LOGGER.info("Source player dimension: {}", sourceDimension);

boolean isSameDimension = sourceDimension == playerDimension;
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isSameDimension compares ResourceKey instances with ==. Use .equals(...) to compare keys reliably, since reference equality is not guaranteed.

Suggested change
boolean isSameDimension = sourceDimension == playerDimension;
boolean isSameDimension = sourceDimension.equals(playerDimension);

Copilot uses AI. Check for mistakes.

Component message = Component.literal(playerName + " is at ")
.append(Component.literal(targetBlockPos.getX() + ", " + targetBlockPos.getY() + ", " + targetBlockPos.getZ())
.withStyle(utils.getDimensionColor(playerDimension)))
.append(Component.literal(" in the ").withStyle(ChatFormatting.WHITE))
.append(Component.literal(utils.getDimensionText(playerDimension))
.withStyle(utils.getDimensionColor(playerDimension)))
.append(Component.literal(isSameDimension
? " (" + utils.getDistance(sourceBlockPos, targetBlockPos) + " blocks away)"
: " (Player is in a different dimension)")
.withStyle(isSameDimension ? ChatFormatting.GREEN : ChatFormatting.RED));

// Send it as a system message to the source
context.getSource().sendSystemMessage(message);

return 1;
})
)
));
}
}
25 changes: 13 additions & 12 deletions src/main/java/me/imgalvin/playerfinder/PlayerFinderUtils.java
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
package me.imgalvin.playerfinder;

import net.minecraft.registry.RegistryKey;
import net.minecraft.util.Formatting;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraft.ChatFormatting;
import net.minecraft.core.BlockPos;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.Level;
import org.jetbrains.annotations.NotNull;

public class PlayerFinderUtils {
public Formatting getDimensionColor(@NotNull RegistryKey<World> playerDimension) {
return playerDimension.equals(World.OVERWORLD) ? Formatting.GREEN :
playerDimension.equals(World.NETHER) ? Formatting.RED :
playerDimension.equals(World.END) ? Formatting.LIGHT_PURPLE :
Formatting.GRAY; // Fallback color for custom or unknown dimensions
public ChatFormatting getDimensionColor(@NotNull ResourceKey<Level> playerDimension) {
return playerDimension.equals(ServerLevel.OVERWORLD) ? ChatFormatting.GREEN :
playerDimension.equals(ServerLevel.NETHER) ? ChatFormatting.RED :
playerDimension.equals(ServerLevel.END) ? ChatFormatting.LIGHT_PURPLE :
ChatFormatting.GRAY; // Fallback colour for custom or unknown dimensions
Comment on lines +11 to +15
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getDimensionColor compares a ResourceKey<Level> against ServerLevel.OVERWORLD/NETHER/END. These constants are typically defined on Level (the key type) rather than ServerLevel; align the constants/types so the comparisons are valid for the key you accept.

Copilot uses AI. Check for mistakes.
}

public String getDimensionText(@NotNull RegistryKey<World> playerDimension) {
// note: this function only works for vanilla dimensions. custom dimensions will have a slight issue
return playerDimension.getValue().toString().split(":")[1].replace("the_", "");
public String getDimensionText(@NotNull ResourceKey<Level> playerDimension) {
return playerDimension.identifier().getPath().replace("the_", "");
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ResourceKey does not typically expose identifier(); the standard accessor for the underlying id is usually location() (returning a ResourceLocation). As written, this is likely a compile error; use the appropriate accessor and then take the path for display.

Suggested change
return playerDimension.identifier().getPath().replace("the_", "");
return playerDimension.location().getPath().replace("the_", "");

Copilot uses AI. Check for mistakes.
}

public int getDistance(@NotNull BlockPos playerPos, @NotNull BlockPos targetPos) {
System.out.println("Calculating distance between " + playerPos + " and " + targetPos);
return (int) Math.sqrt(Math.pow(playerPos.getX() - targetPos.getX(), 2) + Math.pow(playerPos.getY() - targetPos.getY(), 2) + Math.pow(playerPos.getZ() - targetPos.getZ(), 2));
Comment on lines +23 to 24
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The System.out.println in getDistance will spam stdout on every /findplayer call. Remove this debug print or route it through the mod logger at a debug level.

Suggested change
System.out.println("Calculating distance between " + playerPos + " and " + targetPos);
return (int) Math.sqrt(Math.pow(playerPos.getX() - targetPos.getX(), 2) + Math.pow(playerPos.getY() - targetPos.getY(), 2) + Math.pow(playerPos.getZ() - targetPos.getZ(), 2));
return (int) Math.sqrt(
Math.pow(playerPos.getX() - targetPos.getX(), 2)
+ Math.pow(playerPos.getY() - targetPos.getY(), 2)
+ Math.pow(playerPos.getZ() - targetPos.getZ(), 2)
);

Copilot uses AI. Check for mistakes.
}
}

This file was deleted.

4 changes: 2 additions & 2 deletions src/main/resources/fabric.mod.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
},
"depends": {
"fabricloader": ">=0.17.3",
"minecraft": "1.21.11",
"java": ">=21",
"minecraft": "26.1-alpha.3",
"java": ">=25",
"fabric-api": ">=0.139.5"
}
}
Loading