add trade.log for server admins to review

This commit is contained in:
trunksbomb
2026-03-25 01:37:07 -04:00
parent a5bc9789a3
commit 2c157be8cb
22 changed files with 156 additions and 60 deletions

View File

@@ -1,6 +1,6 @@
package com.trunksbomb.trade.mod.client;
package com.trunksbomb.trade.client;
import com.trunksbomb.trade.mod.trade.TradeView;
import com.trunksbomb.trade.trade.TradeView;
import java.util.UUID;
import net.minecraft.client.Minecraft;
import net.minecraft.network.chat.Component;

View File

@@ -1,12 +1,12 @@
package com.trunksbomb.trade.mod.client;
package com.trunksbomb.trade.client;
import com.trunksbomb.trade.mod.network.TradeActionPayload;
import com.trunksbomb.trade.mod.network.DebugTradeControlPayload;
import com.trunksbomb.trade.mod.trade.DebugControlAction;
import com.trunksbomb.trade.mod.trade.DebugUnsafeState;
import com.trunksbomb.trade.mod.trade.TradeAction;
import com.trunksbomb.trade.mod.trade.TradeStage;
import com.trunksbomb.trade.mod.trade.TradeView;
import com.trunksbomb.trade.network.TradeActionPayload;
import com.trunksbomb.trade.network.DebugTradeControlPayload;
import com.trunksbomb.trade.trade.DebugControlAction;
import com.trunksbomb.trade.trade.DebugUnsafeState;
import com.trunksbomb.trade.trade.TradeAction;
import com.trunksbomb.trade.trade.TradeStage;
import com.trunksbomb.trade.trade.TradeView;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

View File

@@ -1,13 +1,13 @@
package com.trunksbomb.trade.mod.command;
package com.trunksbomb.trade.command;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import com.trunksbomb.trade.mod.TradeConfig;
import com.trunksbomb.trade.mod.trade.DebugUnsafeState;
import com.trunksbomb.trade.mod.trade.DebugTradeSession;
import com.trunksbomb.trade.mod.trade.TradeManager;
import com.trunksbomb.trade.trade.DebugUnsafeState;
import com.trunksbomb.trade.trade.DebugTradeSession;
import com.trunksbomb.trade.trade.TradeManager;
import java.util.concurrent.CompletableFuture;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;

View File

@@ -0,0 +1,31 @@
package com.trunksbomb.trade.mod;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import net.minecraft.server.MinecraftServer;
import net.neoforged.fml.loading.FMLPaths;
public final class TradeAuditLog {
private static final DateTimeFormatter TIMESTAMP_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private static final Object LOCK = new Object();
private TradeAuditLog() {}
public static void log(MinecraftServer server, String message) {
Path logPath = FMLPaths.GAMEDIR.get().resolve("logs").resolve("trade.log");
String line = "[" + LocalDateTime.now().format(TIMESTAMP_FORMAT) + "] " + message + System.lineSeparator();
synchronized (LOCK) {
try {
Files.createDirectories(logPath.getParent());
Files.writeString(logPath, line, StandardOpenOption.CREATE, StandardOpenOption.APPEND);
} catch (IOException exception) {
TradeMod.LOGGER.warn("Failed to write trade audit log entry", exception);
}
}
}
}

View File

@@ -1,8 +1,8 @@
package com.trunksbomb.trade.mod;
import com.mojang.blaze3d.platform.InputConstants;
import com.trunksbomb.trade.mod.client.TradeClientState;
import com.trunksbomb.trade.mod.network.TradeRequestPayload;
import com.trunksbomb.trade.client.TradeClientState;
import com.trunksbomb.trade.network.TradeRequestPayload;
import net.minecraft.client.KeyMapping;
import net.minecraft.client.Minecraft;
import net.minecraft.network.chat.Component;

View File

@@ -1,9 +1,9 @@
package com.trunksbomb.trade.mod;
import com.mojang.logging.LogUtils;
import com.trunksbomb.trade.mod.command.TradeCommand;
import com.trunksbomb.trade.mod.network.TradeNetworking;
import com.trunksbomb.trade.mod.trade.TradeManager;
import com.trunksbomb.trade.command.TradeCommand;
import com.trunksbomb.trade.network.TradeNetworking;
import com.trunksbomb.trade.trade.TradeManager;
import net.neoforged.bus.api.IEventBus;
import net.neoforged.fml.ModContainer;
import net.neoforged.fml.common.Mod;

View File

@@ -1,7 +1,7 @@
package com.trunksbomb.trade.mod.network;
package com.trunksbomb.trade.network;
import com.trunksbomb.trade.mod.TradeMod;
import com.trunksbomb.trade.mod.trade.DebugControlAction;
import com.trunksbomb.trade.trade.DebugControlAction;
import java.util.UUID;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;

View File

@@ -1,7 +1,7 @@
package com.trunksbomb.trade.mod.network;
package com.trunksbomb.trade.network;
import com.trunksbomb.trade.mod.TradeMod;
import com.trunksbomb.trade.mod.trade.TradeAction;
import com.trunksbomb.trade.trade.TradeAction;
import java.util.UUID;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;

View File

@@ -1,4 +1,4 @@
package com.trunksbomb.trade.mod.network;
package com.trunksbomb.trade.network;
import com.trunksbomb.trade.mod.TradeMod;
import java.util.UUID;

View File

@@ -1,6 +1,6 @@
package com.trunksbomb.trade.mod.network;
package com.trunksbomb.trade.network;
import com.trunksbomb.trade.mod.trade.TradeManager;
import com.trunksbomb.trade.trade.TradeManager;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.neoforged.neoforge.network.handling.DirectionalPayloadHandler;
@@ -72,7 +72,7 @@ public final class TradeNetworking {
private static void invokeClientState(String methodName, Class<?>[] parameterTypes, Object... arguments) {
try {
Class<?> clientState = Class.forName("com.trunksbomb.trade.mod.client.TradeClientState");
Class<?> clientState = Class.forName("com.trunksbomb.trade.client.TradeClientState");
clientState.getMethod(methodName, parameterTypes).invoke(null, arguments);
} catch (ReflectiveOperationException exception) {
throw new RuntimeException("Failed to invoke TradeClientState#" + methodName, exception);

View File

@@ -1,4 +1,4 @@
package com.trunksbomb.trade.mod.network;
package com.trunksbomb.trade.network;
import com.trunksbomb.trade.mod.TradeMod;
import net.minecraft.network.RegistryFriendlyByteBuf;

View File

@@ -1,7 +1,7 @@
package com.trunksbomb.trade.mod.network;
package com.trunksbomb.trade.network;
import com.trunksbomb.trade.mod.TradeMod;
import com.trunksbomb.trade.mod.trade.TradeView;
import com.trunksbomb.trade.trade.TradeView;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;

View File

@@ -1,4 +1,4 @@
package com.trunksbomb.trade.mod.trade;
package com.trunksbomb.trade.trade;
public enum DebugControlAction {
SET_OFFER,

View File

@@ -1,7 +1,7 @@
package com.trunksbomb.trade.mod.trade;
package com.trunksbomb.trade.trade;
import com.trunksbomb.trade.mod.network.TradeClosePayload;
import com.trunksbomb.trade.mod.network.TradeStatePayload;
import com.trunksbomb.trade.network.TradeClosePayload;
import com.trunksbomb.trade.network.TradeStatePayload;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
@@ -44,6 +44,22 @@ public class DebugTradeSession {
return player;
}
public List<ItemStack> selfOfferSnapshot() {
List<ItemStack> result = new ArrayList<>(OFFER_SLOT_COUNT);
for (TradeEntry entry : selfOffer) {
result.add(entry == null ? ItemStack.EMPTY : entry.stack().copy());
}
return result;
}
public List<ItemStack> otherOfferSnapshot() {
List<ItemStack> result = new ArrayList<>(OFFER_SLOT_COUNT);
for (ItemStack stack : otherOffer) {
result.add(stack.copy());
}
return result;
}
public void sync() {
PacketDistributor.sendToPlayer(player, new TradeStatePayload(view()));
}
@@ -452,22 +468,6 @@ public class DebugTradeSession {
return result;
}
private List<ItemStack> selfOfferSnapshot() {
List<ItemStack> result = new ArrayList<>(OFFER_SLOT_COUNT);
for (TradeEntry entry : selfOffer) {
result.add(entry == null ? ItemStack.EMPTY : entry.stack().copy());
}
return result;
}
private List<ItemStack> otherOfferSnapshot() {
List<ItemStack> result = new ArrayList<>(OFFER_SLOT_COUNT);
for (ItemStack stack : otherOffer) {
result.add(stack.copy());
}
return result;
}
private static List<TradeEntry> blankOffer() {
List<TradeEntry> result = new ArrayList<>(OFFER_SLOT_COUNT);
for (int i = 0; i < OFFER_SLOT_COUNT; i++) {

View File

@@ -1,4 +1,4 @@
package com.trunksbomb.trade.mod.trade;
package com.trunksbomb.trade.trade;
public enum DebugUnsafeState {
DAMAGE,

View File

@@ -1,4 +1,4 @@
package com.trunksbomb.trade.mod.trade;
package com.trunksbomb.trade.trade;
public enum TradeAction {
ADD_ITEM,

View File

@@ -1,4 +1,4 @@
package com.trunksbomb.trade.mod.trade;
package com.trunksbomb.trade.trade;
import net.minecraft.world.item.ItemStack;

View File

@@ -1,5 +1,6 @@
package com.trunksbomb.trade.mod.trade;
package com.trunksbomb.trade.trade;
import com.trunksbomb.trade.mod.TradeAuditLog;
import com.trunksbomb.trade.mod.TradeConfig;
import java.util.ArrayList;
import java.util.HashMap;
@@ -47,6 +48,7 @@ public class TradeManager {
sessionByPlayer.put(first.getUUID(), session.id());
sessionByPlayer.put(second.getUUID(), session.id());
session.syncToPlayers();
TradeAuditLog.log(first.server, "OPEN " + playerName(first) + " <-> " + playerName(second));
first.sendSystemMessage(Component.literal("Trade opened with " + second.getGameProfile().getName() + "."));
second.sendSystemMessage(Component.literal("Trade opened with " + first.getGameProfile().getName() + "."));
return true;
@@ -97,6 +99,7 @@ public class TradeManager {
}
pendingRequestsByTarget.put(target.getUUID(), new TradeRequest(requester.getUUID(), target.getUUID(), target.server.getTickCount()));
TradeAuditLog.log(requester.server, "REQUEST " + playerName(requester) + " -> " + playerName(target));
requester.sendSystemMessage(Component.literal("Trade request sent to " + target.getGameProfile().getName() + "."));
target.sendSystemMessage(Component.literal(requester.getGameProfile().getName() + " would like to trade with you: ")
.append(Component.literal("click to accept")
@@ -137,6 +140,7 @@ public class TradeManager {
List<Component> requesterUnsafe = tradeSafetyFailures(requester, currentTick);
if (!requesterUnsafe.isEmpty()) {
if (canDelayTradeAcceptance(requester, currentTick)) {
TradeAuditLog.log(target.server, "REQUEST ACCEPT DELAYED " + playerName(target) + " -> " + playerName(requester));
beginPendingAcceptance(requester, target, currentTick);
return true;
}
@@ -157,6 +161,7 @@ public class TradeManager {
return false;
}
TradeAuditLog.log(target.server, "REQUEST ACCEPTED " + playerName(target) + " accepted " + playerName(requester));
return true;
}
@@ -174,6 +179,7 @@ public class TradeManager {
public boolean declinePendingTrade(ServerPlayer target) {
DebugTradeRequest debugRequest = pendingDebugRequestsByTarget.remove(target.getUUID());
if (debugRequest != null) {
TradeAuditLog.log(target.server, "DEBUG REQUEST DECLINED by " + playerName(target));
target.sendSystemMessage(Component.literal("Debug trade request declined."));
return true;
}
@@ -186,6 +192,7 @@ public class TradeManager {
ServerPlayer requester = target.server.getPlayerList().getPlayer(request.requester());
if (requester != null) {
TradeAuditLog.log(target.server, "REQUEST DECLINED " + playerName(target) + " declined " + playerName(requester));
requester.sendSystemMessage(Component.literal(target.getGameProfile().getName() + " declined your trade request."));
}
target.sendSystemMessage(Component.literal("Trade request declined."));
@@ -568,6 +575,12 @@ public class TradeManager {
}
private void finish(TradeSession session, Component reason) {
TradeAuditLog.log(
session.firstPlayer().server,
"COMPLETE " + playerName(session.firstPlayer()) + " <-> " + playerName(session.secondPlayer()) + " | "
+ offerSummary(playerName(session.firstPlayer()), session.firstOfferSnapshot()) + " | "
+ offerSummary(playerName(session.secondPlayer()), session.secondOfferSnapshot()) + " | "
+ reason.getString());
session.firstPlayer().sendSystemMessage(reason);
session.secondPlayer().sendSystemMessage(reason);
session.close(reason);
@@ -575,6 +588,12 @@ public class TradeManager {
}
private void cancel(TradeSession session, Component reason) {
TradeAuditLog.log(
session.firstPlayer().server,
"CANCEL " + playerName(session.firstPlayer()) + " <-> " + playerName(session.secondPlayer()) + " | "
+ offerSummary(playerName(session.firstPlayer()), session.firstOfferSnapshot()) + " | "
+ offerSummary(playerName(session.secondPlayer()), session.secondOfferSnapshot()) + " | "
+ reason.getString());
session.firstPlayer().sendSystemMessage(reason);
session.secondPlayer().sendSystemMessage(reason);
session.close(reason);
@@ -588,12 +607,24 @@ public class TradeManager {
}
private void finishDebug(DebugTradeSession session, Component reason) {
TradeAuditLog.log(
session.player().server,
"DEBUG COMPLETE " + playerName(session.player()) + " | "
+ offerSummary(playerName(session.player()), session.selfOfferSnapshot()) + " | "
+ offerSummary("Debug Trader", session.otherOfferSnapshot()) + " | "
+ reason.getString());
session.player().sendSystemMessage(reason);
session.close(reason);
removeDebugSession(session);
}
private void closeDebug(DebugTradeSession session, Component reason) {
TradeAuditLog.log(
session.player().server,
"DEBUG CLOSE " + playerName(session.player()) + " | "
+ offerSummary(playerName(session.player()), session.selfOfferSnapshot()) + " | "
+ offerSummary("Debug Trader", session.otherOfferSnapshot()) + " | "
+ reason.getString());
session.player().sendSystemMessage(reason);
session.close(reason);
removeDebugSession(session);
@@ -621,6 +652,7 @@ public class TradeManager {
if (direct != null) {
ServerPlayer requester = player.server.getPlayerList().getPlayer(direct.requester());
if (requester != null) {
TradeAuditLog.log(player.server, "REQUEST EXPIRED " + playerName(requester) + " -> " + playerName(player));
requester.sendSystemMessage(Component.literal("Your trade request to " + player.getGameProfile().getName() + " expired."));
}
}
@@ -631,6 +663,7 @@ public class TradeManager {
}
ServerPlayer target = player.server.getPlayerList().getPlayer(entry.getValue().target());
if (target != null) {
TradeAuditLog.log(player.server, "REQUEST EXPIRED " + playerName(player) + " -> " + playerName(target));
target.sendSystemMessage(Component.literal("The trade request from " + player.getGameProfile().getName() + " expired."));
}
return true;
@@ -657,6 +690,7 @@ public class TradeManager {
ServerPlayer requester = server.getPlayerList().getPlayer(request.requester());
ServerPlayer target = server.getPlayerList().getPlayer(request.target());
if (requester != null) {
TradeAuditLog.log(server, "REQUEST EXPIRED " + playerName(requester) + " -> " + nameFor(request.target(), server));
requester.sendSystemMessage(Component.literal("Your trade request expired."));
}
if (target != null) {
@@ -680,11 +714,13 @@ public class TradeManager {
}
if (scheduled.autoAccept()) {
TradeAuditLog.log(server, "DEBUG INIT ACCEPT " + playerName(target));
startOrDelayDebugTrade(target);
continue;
}
pendingDebugRequestsByTarget.put(target.getUUID(), new DebugTradeRequest(target.getUUID(), server.getTickCount()));
TradeAuditLog.log(server, "DEBUG REQUEST " + playerName(target));
target.sendSystemMessage(Component.literal("Debug Trader would like to trade with you: ")
.append(Component.literal("click to accept")
.withStyle(Style.EMPTY
@@ -1033,6 +1069,27 @@ public class TradeManager {
return String.format(java.util.Locale.ROOT, "%.1f", tenths / 10.0D);
}
private String playerName(ServerPlayer player) {
return player.getGameProfile().getName() + " [" + player.getUUID() + "]";
}
private String nameFor(UUID playerId, MinecraftServer server) {
ServerPlayer player = server.getPlayerList().getPlayer(playerId);
return player != null ? playerName(player) : playerId.toString();
}
private String offerSummary(String trader, List<ItemStack> offer) {
List<String> entries = new ArrayList<>();
for (ItemStack stack : offer) {
if (stack.isEmpty()) {
continue;
}
String itemId = net.minecraft.core.registries.BuiltInRegistries.ITEM.getKey(stack.getItem()).toString();
entries.add(itemId + " " + stack.getCount());
}
return trader + "=" + (entries.isEmpty() ? "(nothing)" : String.join("; ", entries));
}
private DebugUnsafeState parseDebugUnsafeState(String spec) {
try {
return DebugUnsafeState.valueOf(spec.trim().toUpperCase(java.util.Locale.ROOT));

View File

@@ -1,4 +1,4 @@
package com.trunksbomb.trade.mod.trade;
package com.trunksbomb.trade.trade;
import com.mojang.datafixers.util.Pair;
import java.util.ArrayList;

View File

@@ -1,7 +1,7 @@
package com.trunksbomb.trade.mod.trade;
package com.trunksbomb.trade.trade;
import com.trunksbomb.trade.mod.network.TradeClosePayload;
import com.trunksbomb.trade.mod.network.TradeStatePayload;
import com.trunksbomb.trade.network.TradeClosePayload;
import com.trunksbomb.trade.network.TradeStatePayload;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@@ -45,6 +45,14 @@ public class TradeSession {
return second;
}
public List<ItemStack> firstOfferSnapshot() {
return offerSnapshot(firstOffer);
}
public List<ItemStack> secondOfferSnapshot() {
return offerSnapshot(secondOffer);
}
public boolean involves(ServerPlayer player) {
return player == first || player == second;
}

View File

@@ -1,4 +1,4 @@
package com.trunksbomb.trade.mod.trade;
package com.trunksbomb.trade.trade;
public enum TradeStage {
OFFERING,

View File

@@ -1,4 +1,4 @@
package com.trunksbomb.trade.mod.trade;
package com.trunksbomb.trade.trade;
import java.util.ArrayList;
import java.util.List;