Skip to content
Open
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
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,15 @@
# InvView
Allows you to get the inventory and echest of other players

## Trinkets Updated support

With Trinkets Updated 4.2.1+26.3 installed, `/view trinket <target>` opens an online
or offline player's active trinket slots. InvView itself remains
optional on the client; the usual client requirements of Trinkets still apply.

The menu displays up to 18 slots per page, supports additional pages, and includes
a regular/cosmetic toggle when cosmetic slots are available. The exact slots depend
on the target player's active Trinkets configuration. If the slot structure or target
player entity changes while viewing, the menu closes and asks you to reopen it.
Switching pages or modes preserves the cursor stack and cancels any unfinished drag
operation.
16 changes: 14 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ repositories {
maven { url "https://maven.jamieswhiteshirt.com/libs-release/" }
maven { url "https://maven.shedaniel.me/" }
maven { url 'https://maven.nucleoid.xyz' }
maven { url 'https://maven.gnomecraft.net/releases/' }
maven { url 'https://oss.sonatype.org/content/repositories/snapshots' }
mavenCentral()
maven { url 'https://api.modrinth.com/maven' }
Expand Down Expand Up @@ -48,7 +49,14 @@ dependencies {
implementation include("eu.pb4:sgui:${project.sgui_version}")
implementation include("me.lucko:fabric-permissions-api:${project.permission_api_version}")

// modImplementation "dev.emi:trinkets:${project.trinkets_version}"
compileOnly "eu.pb4:trinkets:${project.trinkets_version}"
if (providers.gradleProperty('withTrinkets').map { it.toBoolean() }.getOrElse(false)) {
localRuntime "eu.pb4:trinkets:${project.trinkets_version}"
}
testImplementation "eu.pb4:trinkets:${project.trinkets_version}"
testImplementation platform('org.junit:junit-bom:5.12.2')
testImplementation 'org.junit.jupiter:junit-jupiter'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
// modImplementation "com.github.apace100:apoli:${project.apoli_version}"

// Dev Mods - These can be commented/uncommented to test compatibility
Expand All @@ -68,7 +76,7 @@ processResources {
// ensure that the encoding is set to UTF-8, no matter what the system default is
// this fixes some edge cases with special characters not displaying correctly
// see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html
tasks.withType(JavaCompile) {
tasks.withType(JavaCompile).configureEach {
options.encoding = "UTF-8"
}

Expand All @@ -77,6 +85,10 @@ java {
targetCompatibility = JavaVersion.VERSION_25
}

test {
useJUnitPlatform()
}

jar {
from "LICENSE"
}
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ archives_base_name=InvView
# Dependencies
# check this on https://fabricmc.net/develop/
fabric_version=0.160.5+26.3
# trinkets_version=3.10.0
trinkets_version=4.2.1+26.3
# apoli_version=2.12.0
sgui_version=2.2.0+26.3
permission_api_version=0.7.0
19 changes: 7 additions & 12 deletions src/main/java/us/potatoboy/invview/InvView.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@
import net.minecraft.nbt.NbtIo;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.permissions.PermissionLevel;
import net.minecraft.util.ProblemReporter;
import net.minecraft.util.Util;
import net.minecraft.world.level.storage.LevelResource;
import net.minecraft.world.level.storage.TagValueOutput;
import us.potatoboy.invview.compat.trinkets.TrinketsCompat;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
Expand All @@ -31,38 +33,31 @@ public class InvView implements ModInitializer {

@Override
public void onInitialize() {
isTrinkets = FabricLoader.getInstance().isModLoaded("trinkets");
isTrinkets = FabricLoader.getInstance().isModLoaded("trinkets_updated");
isLuckPerms = FabricLoader.getInstance().isModLoaded("luckperms");
isApoli = FabricLoader.getInstance().isModLoaded("apoli");

CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> {

LiteralCommandNode<CommandSourceStack> viewNode = Commands
.literal("view")
.requires(Permissions.require("invview.command.root", 2))
.requires(Permissions.require(InvViewPermissions.ROOT, PermissionLevel.GAMEMASTERS))
.build();

LiteralCommandNode<CommandSourceStack> invNode = Commands
.literal("inv")
.requires(Permissions.require("invview.command.inv", 2))
.requires(Permissions.require(InvViewPermissions.INVENTORY, PermissionLevel.GAMEMASTERS))
.then(Commands.argument("target", GameProfileArgument.gameProfile())
.executes(ViewCommand::inv))
.build();

LiteralCommandNode<CommandSourceStack> echestNode = Commands
.literal("echest")
.requires(Permissions.require("invview.command.echest", 2))
.requires(Permissions.require(InvViewPermissions.ENDER_CHEST, PermissionLevel.GAMEMASTERS))
.then(Commands.argument("target", GameProfileArgument.gameProfile())
.executes(ViewCommand::eChest))
.build();

// LiteralCommandNode<ServerCommandSource> trinketNode = CommandManager
// .literal("trinket")
// .requires(Permissions.require("invview.command.trinket", 2))
// .then(CommandManager.argument("target", GameProfileArgumentType.gameProfile())
// .executes(ViewCommand::trinkets))
// .build();
//
// LiteralCommandNode<ServerCommandSource> apoliNode = CommandManager
// .literal("origin-inv")
// .requires(Permissions.require("invview.command.origin", 2))
Expand All @@ -75,7 +70,7 @@ public void onInitialize() {
viewNode.addChild(echestNode);

if (isTrinkets) {
// viewNode.addChild(trinketNode);
TrinketsCompat.register(viewNode);
}
if (isApoli) {
// viewNode.addChild(apoliNode);
Expand Down
13 changes: 13 additions & 0 deletions src/main/java/us/potatoboy/invview/InvViewPermissions.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package us.potatoboy.invview;

public final class InvViewPermissions {
public static final String ROOT = "invview.command.root";
public static final String INVENTORY = "invview.command.inv";
public static final String ENDER_CHEST = "invview.command.echest";
public static final String TRINKET = "invview.command.trinket";
public static final String PROTECTED = "invview.protected";
public static final String MODIFY = "invview.can_modify";

private InvViewPermissions() {
}
}
45 changes: 45 additions & 0 deletions src/main/java/us/potatoboy/invview/PlayerLookup.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package us.potatoboy.invview;

import com.mojang.authlib.GameProfile;
import com.mojang.logging.LogUtils;
import net.minecraft.resources.Identifier;
import net.minecraft.resources.ResourceKey;
import net.minecraft.core.registries.Registries;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ClientInformation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.players.NameAndId;
import net.minecraft.util.ProblemReporter;
import net.minecraft.world.level.storage.TagValueInput;
import us.potatoboy.invview.mixin.EntityAccessor;

public final class PlayerLookup {
private PlayerLookup() {
}

public static ServerPlayer getPlayer(MinecraftServer server, NameAndId profile) {
ServerPlayer online = server.getPlayerList().getPlayer(profile.id());
if (online != null) {
return online;
}

ServerPlayer player = new ServerPlayer(server, server.overworld(),
new GameProfile(profile.id(), profile.name()), ClientInformation.createDefault());
try (var reporter = new ProblemReporter.ScopedCollector(LogUtils.getLogger())) {
server.getPlayerList().loadPlayerData(profile).ifPresent(data -> {
var input = TagValueInput.create(reporter, server.registryAccess(), data);
player.load(input);
input.getString("Dimension").ifPresent(dimension -> {
Identifier id = Identifier.tryParse(dimension);
if (id != null) {
var level = server.getLevel(ResourceKey.create(Registries.DIMENSION, id));
if (level != null) {
((EntityAccessor) player).callSetLevel(level);
}
}
});
});
}
return player;
}
}
87 changes: 6 additions & 81 deletions src/main/java/us/potatoboy/invview/ViewCommand.java
Original file line number Diff line number Diff line change
@@ -1,50 +1,32 @@
package us.potatoboy.invview;

import com.mojang.authlib.GameProfile;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.logging.LogUtils;
import eu.pb4.sgui.api.elements.GuiElementBuilder;
import eu.pb4.sgui.api.gui.SimpleGui;
import me.lucko.fabric.api.permissions.v0.Permissions;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.arguments.GameProfileArgument;
import net.minecraft.core.registries.Registries;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.Identifier;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ClientInformation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.players.NameAndId;
import net.minecraft.util.ProblemReporter;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.inventory.PlayerEnderChestContainer;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.storage.TagValueInput;
import net.minecraft.world.level.storage.ValueInput;
import us.potatoboy.invview.gui.SavingPlayerDataGui;
import us.potatoboy.invview.gui.UnmodifiableSlot;
import us.potatoboy.invview.mixin.EntityAccessor;

import java.util.Optional;

public class ViewCommand {
private static final MinecraftServer minecraftServer = InvView.getMinecraftServer();

private static final String permProtected = "invview.protected";
private static final String permModify = "invview.can_modify";
private static final String msgProtected = "Requested inventory is protected";

public static int inv(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
ServerPlayer player = context.getSource().getPlayer();
ServerPlayer requestedPlayer = getRequestedPlayer(context);

boolean canModify = Permissions.check(context.getSource(), permModify, true);
boolean canModify = Permissions.check(context.getSource(), InvViewPermissions.MODIFY, true);

Permissions.check(requestedPlayer.getUUID(), permProtected, false).thenAcceptAsync(isProtected -> {
Permissions.check(requestedPlayer.getUUID(), InvViewPermissions.PROTECTED, false).thenAcceptAsync(isProtected -> {
if (isProtected) {
context.getSource().sendFailure(Component.literal(msgProtected));
} else {
Expand All @@ -68,9 +50,9 @@ public static int eChest(CommandContext<CommandSourceStack> context) throws Comm
ServerPlayer requestedPlayer = getRequestedPlayer(context);
PlayerEnderChestContainer requestedEchest = requestedPlayer.getEnderChestInventory();

boolean canModify = Permissions.check(context.getSource(), permModify, true);
boolean canModify = Permissions.check(context.getSource(), InvViewPermissions.MODIFY, true);

Permissions.check(requestedPlayer.getUUID(), permProtected, false).thenAcceptAsync(isProtected -> {
Permissions.check(requestedPlayer.getUUID(), InvViewPermissions.PROTECTED, false).thenAcceptAsync(isProtected -> {
if (isProtected) {
context.getSource().sendFailure(Component.literal(msgProtected));
} else {
Expand All @@ -97,37 +79,6 @@ public static int eChest(CommandContext<CommandSourceStack> context) throws Comm
return 1;
}

// public static int trinkets(CommandContext<ServerCommandSource> context) throws CommandSyntaxException {
// ServerPlayerEntity player = context.getSource().getPlayer();
// ServerPlayerEntity requestedPlayer = getRequestedPlayer(context);
// TrinketComponent requestedComponent = TrinketsApi.getTrinketComponent(requestedPlayer).get();
//
// boolean canModify = Permissions.check(context.getSource(), permModify, true);
//
// Permissions.check(requestedPlayer.getUuid(), permProtected, false).thenAcceptAsync(isProtected -> {
// if (isProtected) {
// context.getSource().sendError(Text.literal(msgProtected));
// } else {
// SimpleGui gui = new SavingPlayerDataGui(ScreenHandlerType.GENERIC_9X2, player, requestedPlayer);
// addBackground(gui);
// gui.setTitle(requestedPlayer.getName());
// int index = 0;
// for (Map<String, TrinketInventory> group : requestedComponent.getInventory().values()) {
// for (TrinketInventory inventory : group.values()) {
// for (int i = 0; i < inventory.size(); i++) {
// gui.setSlotRedirect(index, canModify ? new Slot(inventory, i, 0, 0) : new UnmodifiableSlot(inventory, i));
// index += 1;
// }
// }
// }
//
// gui.open();
// }
// });
//
// return 1;
// }

// public static int apoli(CommandContext<ServerCommandSource> context) throws CommandSyntaxException {
// ServerPlayerEntity player = context.getSource().getPlayer();
// ServerPlayerEntity requestedPlayer = getRequestedPlayer(context);
Expand Down Expand Up @@ -164,34 +115,8 @@ public static int eChest(CommandContext<CommandSourceStack> context) throws Comm

private static ServerPlayer getRequestedPlayer(CommandContext<CommandSourceStack> context)
throws CommandSyntaxException {
NameAndId playerConfigEntry = GameProfileArgument.getGameProfiles(context, "target").iterator().next();
ServerPlayer requestedPlayer = minecraftServer.getPlayerList().getPlayerByName(playerConfigEntry.name());

// If player is not currently online
if (requestedPlayer == null) {
requestedPlayer = new ServerPlayer(minecraftServer, minecraftServer.overworld(), new GameProfile(playerConfigEntry.id(), playerConfigEntry.name()),
ClientInformation.createDefault());
Optional<ValueInput> readViewOpt = minecraftServer.getPlayerList()
.loadPlayerData(playerConfigEntry).map(playerData -> TagValueInput.create(new ProblemReporter.ScopedCollector(LogUtils.getLogger()), minecraftServer.registryAccess(), playerData));
readViewOpt.ifPresent(requestedPlayer::load);

// Avoids player's dimension being reset to the overworld
if (readViewOpt.isPresent()) {
ValueInput readView = readViewOpt.get();
Optional<String> dimension = readView.getString("Dimension");

if (dimension.isPresent()) {
ServerLevel world = minecraftServer.getLevel(
ResourceKey.create(Registries.DIMENSION, Identifier.tryParse(dimension.get())));

if (world != null) {
((EntityAccessor) requestedPlayer).callSetLevel(world);
}
}
}
}

return requestedPlayer;
return PlayerLookup.getPlayer(context.getSource().getServer(),
GameProfileArgument.getGameProfiles(context, "target").iterator().next());
}

private static void addBackground(SimpleGui gui) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package us.potatoboy.invview.compat.trinkets;

import eu.pb4.trinkets.api.TrinketSlotAccess;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import org.jspecify.annotations.NonNull;

final class ReadOnlyTrinketsSlot extends TrinketsSlot {
ReadOnlyTrinketsSlot(TrinketSlotAccess access) {
super(access);
}

@Override
public @NonNull ItemStack getItem() {
return super.getItem().copy();
}

@Override
public boolean mayPlace(@NonNull ItemStack stack) {
return false;
}

@Override
public boolean mayPickup(@NonNull Player player) {
return false;
}

@Override
public boolean allowModification(@NonNull Player player) {
return false;
}

@Override
public @NonNull ItemStack remove(int amount) {
return ItemStack.EMPTY;
}

@Override
public @NonNull ItemStack safeInsert(@NonNull ItemStack stack, int count) {
return stack;
}

@Override
public void setByPlayer(@NonNull ItemStack stack) {
}

@Override
public void setByPlayer(@NonNull ItemStack stack, @NonNull ItemStack previous) {
}

@Override
public void set(@NonNull ItemStack stack) {
}

@Override
public void setChanged() {
}
}
Loading