UI cleanup, trade implementation via hotkey and via /trade player|yes|no
This commit is contained in:
@@ -18,6 +18,13 @@ public final class TradeClientState {
|
||||
return minecraft;
|
||||
}
|
||||
|
||||
public static void showOrUpdate(TradeView view) {
|
||||
if (minecraft == null) {
|
||||
minecraft = Minecraft.getInstance();
|
||||
}
|
||||
showOrUpdate(minecraft, view);
|
||||
}
|
||||
|
||||
public static void showOrUpdate(Minecraft minecraftInstance, TradeView view) {
|
||||
minecraft = minecraftInstance;
|
||||
if (minecraft.screen instanceof TradeScreen screen && screen.sessionId().equals(view.sessionId())) {
|
||||
@@ -32,6 +39,13 @@ public final class TradeClientState {
|
||||
minecraft.setScreen(new TradeScreen(new TradeScreen.TradeMenu(0, minecraft.player.getInventory(), view), minecraft.player.getInventory(), Component.literal("Trade")));
|
||||
}
|
||||
|
||||
public static void closeTrade(UUID sessionId, Component reason) {
|
||||
if (minecraft == null) {
|
||||
minecraft = Minecraft.getInstance();
|
||||
}
|
||||
closeTrade(minecraft, sessionId, reason);
|
||||
}
|
||||
|
||||
public static void closeTrade(Minecraft minecraftInstance, UUID sessionId, Component reason) {
|
||||
minecraft = minecraftInstance;
|
||||
if (minecraft.screen instanceof TradeScreen screen && screen.sessionId().equals(sessionId)) {
|
||||
|
||||
@@ -31,9 +31,11 @@ public final class TradeCommand {
|
||||
.then(Commands.literal("accept").executes(context -> acceptDebug(context.getSource())))
|
||||
.then(Commands.literal("cancel").executes(context -> cancelDebug(context.getSource())))
|
||||
.then(Commands.literal("close").executes(context -> closeDebug(context.getSource()))))
|
||||
.then(Commands.literal("yes").executes(context -> respondTrade(context.getSource(), true)))
|
||||
.then(Commands.literal("no").executes(context -> respondTrade(context.getSource(), false)))
|
||||
.then(Commands.argument("player", StringArgumentType.word())
|
||||
.suggests((context, builder) -> suggestPlayers(context.getSource(), builder))
|
||||
.executes(context -> startTrade(context.getSource(), StringArgumentType.getString(context, "player")))));
|
||||
.executes(context -> requestTrade(context.getSource(), StringArgumentType.getString(context, "player")))));
|
||||
}
|
||||
|
||||
private static CompletableFuture<com.mojang.brigadier.suggestion.Suggestions> suggestPlayers(CommandSourceStack source, SuggestionsBuilder builder) {
|
||||
@@ -45,7 +47,7 @@ public final class TradeCommand {
|
||||
return builder.buildFuture();
|
||||
}
|
||||
|
||||
private static int startTrade(CommandSourceStack source, String targetName) {
|
||||
private static int requestTrade(CommandSourceStack source, String targetName) {
|
||||
if (!(source.getEntity() instanceof ServerPlayer player)) {
|
||||
source.sendFailure(Component.literal("Only players can start trades."));
|
||||
return 0;
|
||||
@@ -62,15 +64,25 @@ public final class TradeCommand {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!TradeManager.get(source.getServer()).startTrade(player, target)) {
|
||||
source.sendFailure(Component.literal("Trade could not be started. One of you is already trading."));
|
||||
if (!TradeManager.get(source.getServer()).requestTrade(player, target)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
source.sendSuccess(() -> Component.literal("Trade opened with " + target.getGameProfile().getName() + "."), false);
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static int respondTrade(CommandSourceStack source, boolean accept) {
|
||||
if (!(source.getEntity() instanceof ServerPlayer player)) {
|
||||
source.sendFailure(Component.literal("Only players can answer trade requests."));
|
||||
return 0;
|
||||
}
|
||||
|
||||
boolean result = accept
|
||||
? TradeManager.get(source.getServer()).acceptPendingTrade(player)
|
||||
: TradeManager.get(source.getServer()).declinePendingTrade(player);
|
||||
return result ? 1 : 0;
|
||||
}
|
||||
|
||||
private static int initDebug(CommandSourceStack source) {
|
||||
if (!(source.getEntity() instanceof ServerPlayer player)) {
|
||||
source.sendFailure(Component.literal("Only players can start debug trades."));
|
||||
|
||||
@@ -1,18 +1,52 @@
|
||||
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 net.minecraft.client.KeyMapping;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.neoforged.api.distmarker.Dist;
|
||||
import net.neoforged.bus.api.SubscribeEvent;
|
||||
import net.neoforged.fml.common.EventBusSubscriber;
|
||||
import net.neoforged.bus.api.IEventBus;
|
||||
import net.neoforged.fml.common.Mod;
|
||||
import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent;
|
||||
import net.neoforged.neoforge.client.event.ClientTickEvent;
|
||||
import net.neoforged.neoforge.client.event.RegisterKeyMappingsEvent;
|
||||
import net.neoforged.neoforge.common.NeoForge;
|
||||
import net.neoforged.neoforge.network.PacketDistributor;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
@Mod(value = TradeMod.MODID, dist = Dist.CLIENT)
|
||||
@EventBusSubscriber(modid = TradeMod.MODID, value = Dist.CLIENT)
|
||||
public class TradeClientMod {
|
||||
@SubscribeEvent
|
||||
static void onClientSetup(FMLClientSetupEvent event) {
|
||||
event.enqueueWork(() -> TradeClientState.init(Minecraft.getInstance()));
|
||||
private static final KeyMapping REQUEST_TRADE = new KeyMapping(
|
||||
"key.trade.request_trade",
|
||||
InputConstants.Type.KEYSYM,
|
||||
GLFW.GLFW_KEY_G,
|
||||
"key.categories.gameplay");
|
||||
|
||||
public TradeClientMod(IEventBus modEventBus) {
|
||||
modEventBus.addListener(this::registerKeyMappings);
|
||||
NeoForge.EVENT_BUS.addListener(this::onClientTick);
|
||||
TradeClientState.init(Minecraft.getInstance());
|
||||
}
|
||||
|
||||
private void registerKeyMappings(RegisterKeyMappingsEvent event) {
|
||||
event.register(REQUEST_TRADE);
|
||||
}
|
||||
|
||||
private void onClientTick(ClientTickEvent.Post event) {
|
||||
Minecraft minecraft = Minecraft.getInstance();
|
||||
while (REQUEST_TRADE.consumeClick()) {
|
||||
if (minecraft.player == null || minecraft.screen != null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!(minecraft.crosshairPickEntity instanceof Player target) || target == minecraft.player) {
|
||||
minecraft.player.displayClientMessage(Component.literal("Look at a player to send a trade request."), true);
|
||||
continue;
|
||||
}
|
||||
|
||||
PacketDistributor.sendToServer(new TradeRequestPayload(target.getId()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
24
src/main/java/com/trunksbomb/trade/mod/TradeConfig.java
Normal file
24
src/main/java/com/trunksbomb/trade/mod/TradeConfig.java
Normal file
@@ -0,0 +1,24 @@
|
||||
package com.trunksbomb.trade.mod;
|
||||
|
||||
import net.neoforged.neoforge.common.ModConfigSpec;
|
||||
|
||||
public final class TradeConfig {
|
||||
public static final ModConfigSpec SPEC;
|
||||
private static final ModConfigSpec.IntValue TRADE_COMMAND_PROXIMITY;
|
||||
|
||||
static {
|
||||
ModConfigSpec.Builder builder = new ModConfigSpec.Builder();
|
||||
builder.push("trade");
|
||||
TRADE_COMMAND_PROXIMITY = builder
|
||||
.comment("Maximum distance in blocks to initiate or accept a trade request. 0 disables the distance check.")
|
||||
.defineInRange("tradeCommandProximity", 0, 0, 1024);
|
||||
builder.pop();
|
||||
SPEC = builder.build();
|
||||
}
|
||||
|
||||
private TradeConfig() {}
|
||||
|
||||
public static int tradeCommandProximity() {
|
||||
return TRADE_COMMAND_PROXIMITY.get();
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import com.trunksbomb.trade.mod.command.TradeCommand;
|
||||
import com.trunksbomb.trade.mod.network.TradeNetworking;
|
||||
import com.trunksbomb.trade.mod.trade.TradeManager;
|
||||
import net.neoforged.bus.api.IEventBus;
|
||||
import net.neoforged.fml.ModContainer;
|
||||
import net.neoforged.fml.common.Mod;
|
||||
import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent;
|
||||
import net.neoforged.neoforge.common.NeoForge;
|
||||
@@ -18,9 +19,10 @@ public class TradeMod {
|
||||
public static final String MODID = "trade";
|
||||
public static final Logger LOGGER = LogUtils.getLogger();
|
||||
|
||||
public TradeMod(IEventBus modEventBus) {
|
||||
public TradeMod(IEventBus modEventBus, ModContainer modContainer) {
|
||||
modEventBus.addListener(this::commonSetup);
|
||||
modEventBus.addListener(this::registerPayloads);
|
||||
modContainer.registerConfig(net.neoforged.fml.config.ModConfig.Type.SERVER, TradeConfig.SPEC);
|
||||
NeoForge.EVENT_BUS.addListener(this::registerCommands);
|
||||
NeoForge.EVENT_BUS.addListener(this::onPlayerLogout);
|
||||
NeoForge.EVENT_BUS.addListener(this::onItemPickup);
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package com.trunksbomb.trade.mod.network;
|
||||
|
||||
import com.trunksbomb.trade.mod.client.TradeScreen;
|
||||
import com.trunksbomb.trade.mod.trade.TradeManager;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.neoforged.neoforge.network.handling.DirectionalPayloadHandler;
|
||||
@@ -25,6 +23,10 @@ public final class TradeNetworking {
|
||||
TradeActionPayload.TYPE,
|
||||
TradeActionPayload.STREAM_CODEC,
|
||||
TradeNetworking::handleTradeActionServer);
|
||||
registrar.playToServer(
|
||||
TradeRequestPayload.TYPE,
|
||||
TradeRequestPayload.STREAM_CODEC,
|
||||
TradeNetworking::handleTradeRequestServer);
|
||||
registrar.playToServer(
|
||||
DebugTradeControlPayload.TYPE,
|
||||
DebugTradeControlPayload.STREAM_CODEC,
|
||||
@@ -32,31 +34,11 @@ public final class TradeNetworking {
|
||||
}
|
||||
|
||||
private static void handleTradeStateClient(TradeStatePayload payload, IPayloadContext context) {
|
||||
Minecraft minecraft = Minecraft.getInstance();
|
||||
if (minecraft.player == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (minecraft.screen instanceof TradeScreen screen && screen.sessionId().equals(payload.view().sessionId())) {
|
||||
screen.updateView(payload.view());
|
||||
return;
|
||||
}
|
||||
|
||||
minecraft.setScreen(new TradeScreen(
|
||||
new TradeScreen.TradeMenu(0, minecraft.player.getInventory(), payload.view()),
|
||||
minecraft.player.getInventory(),
|
||||
Component.literal("Trade")));
|
||||
invokeClientState("showOrUpdate", new Class<?>[] {payload.view().getClass()}, payload.view());
|
||||
}
|
||||
|
||||
private static void handleTradeCloseClient(TradeClosePayload payload, IPayloadContext context) {
|
||||
Minecraft minecraft = Minecraft.getInstance();
|
||||
if (minecraft.screen instanceof TradeScreen screen && screen.sessionId().equals(payload.sessionId())) {
|
||||
minecraft.setScreen(null);
|
||||
}
|
||||
|
||||
if (minecraft.player != null) {
|
||||
minecraft.player.displayClientMessage(payload.reason(), false);
|
||||
}
|
||||
invokeClientState("closeTrade", new Class<?>[] {java.util.UUID.class, Component.class}, payload.sessionId(), payload.reason());
|
||||
}
|
||||
|
||||
private static void handleTradeActionServer(TradeActionPayload payload, IPayloadContext context) {
|
||||
@@ -65,6 +47,19 @@ public final class TradeNetworking {
|
||||
}
|
||||
}
|
||||
|
||||
private static void handleTradeRequestServer(TradeRequestPayload payload, IPayloadContext context) {
|
||||
if (!(context.player() instanceof ServerPlayer player)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(player.serverLevel().getEntity(payload.targetEntityId()) instanceof ServerPlayer target)) {
|
||||
player.sendSystemMessage(Component.literal("You must be looking at a player to trade."));
|
||||
return;
|
||||
}
|
||||
|
||||
TradeManager.get(player.server).requestTrade(player, target);
|
||||
}
|
||||
|
||||
private static void handleDebugTradeControlServer(DebugTradeControlPayload payload, IPayloadContext context) {
|
||||
if (context.player() instanceof ServerPlayer player) {
|
||||
TradeManager.get(player.server).handleDebugControl(player, payload.sessionId(), payload.action(), payload.spec());
|
||||
@@ -74,4 +69,13 @@ public final class TradeNetworking {
|
||||
private static void ignoreTradeStateServer(TradeStatePayload payload, IPayloadContext context) {}
|
||||
|
||||
private static void ignoreTradeCloseServer(TradeClosePayload payload, IPayloadContext context) {}
|
||||
|
||||
private static void invokeClientState(String methodName, Class<?>[] parameterTypes, Object... arguments) {
|
||||
try {
|
||||
Class<?> clientState = Class.forName("com.trunksbomb.trade.mod.client.TradeClientState");
|
||||
clientState.getMethod(methodName, parameterTypes).invoke(null, arguments);
|
||||
} catch (ReflectiveOperationException exception) {
|
||||
throw new RuntimeException("Failed to invoke TradeClientState#" + methodName, exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.trunksbomb.trade.mod.network;
|
||||
|
||||
import com.trunksbomb.trade.mod.TradeMod;
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
public record TradeRequestPayload(int targetEntityId) implements CustomPacketPayload {
|
||||
public static final Type<TradeRequestPayload> TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(TradeMod.MODID, "trade_request"));
|
||||
public static final StreamCodec<RegistryFriendlyByteBuf, TradeRequestPayload> STREAM_CODEC = new StreamCodec<>() {
|
||||
@Override
|
||||
public void encode(RegistryFriendlyByteBuf buf, TradeRequestPayload value) {
|
||||
buf.writeVarInt(value.targetEntityId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public TradeRequestPayload decode(RegistryFriendlyByteBuf buf) {
|
||||
return new TradeRequestPayload(buf.readVarInt());
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public Type<? extends CustomPacketPayload> type() {
|
||||
return TYPE;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,14 @@
|
||||
package com.trunksbomb.trade.mod.trade;
|
||||
|
||||
import com.trunksbomb.trade.mod.TradeConfig;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.WeakHashMap;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.chat.ClickEvent;
|
||||
import net.minecraft.network.chat.Style;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
@@ -17,6 +20,7 @@ public class TradeManager {
|
||||
private final Map<UUID, UUID> sessionByPlayer = new HashMap<>();
|
||||
private final Map<UUID, DebugTradeSession> debugSessionsById = new HashMap<>();
|
||||
private final Map<UUID, UUID> debugSessionByPlayer = new HashMap<>();
|
||||
private final Map<UUID, TradeRequest> pendingRequestsByTarget = new HashMap<>();
|
||||
|
||||
public static TradeManager get(MinecraftServer server) {
|
||||
return INSTANCES.computeIfAbsent(server, ignored -> new TradeManager());
|
||||
@@ -37,6 +41,82 @@ public class TradeManager {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean requestTrade(ServerPlayer requester, ServerPlayer target) {
|
||||
if (requester == target) {
|
||||
requester.sendSystemMessage(Component.literal("You cannot trade with yourself."));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isTrading(requester) || isTrading(target)) {
|
||||
requester.sendSystemMessage(Component.literal("Trade could not be started. One of you is already trading."));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!withinTradeRange(requester, target)) {
|
||||
requester.sendSystemMessage(Component.literal("That player is too far away to trade."));
|
||||
return false;
|
||||
}
|
||||
|
||||
TradeRequest existing = pendingRequestsByTarget.get(target.getUUID());
|
||||
if (existing != null && existing.requester().equals(requester.getUUID())) {
|
||||
requester.sendSystemMessage(Component.literal("Trade request already sent to " + target.getGameProfile().getName() + "."));
|
||||
return false;
|
||||
}
|
||||
|
||||
pendingRequestsByTarget.put(target.getUUID(), new TradeRequest(requester.getUUID(), target.getUUID()));
|
||||
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")
|
||||
.withStyle(Style.EMPTY
|
||||
.withColor(0x55FF55)
|
||||
.withUnderlined(true)
|
||||
.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/trade yes")))));
|
||||
target.sendSystemMessage(Component.literal("You can also use /trade yes or /trade no."));
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean acceptPendingTrade(ServerPlayer target) {
|
||||
TradeRequest request = pendingRequestsByTarget.remove(target.getUUID());
|
||||
if (request == null) {
|
||||
target.sendSystemMessage(Component.literal("You have no pending trade request."));
|
||||
return false;
|
||||
}
|
||||
|
||||
ServerPlayer requester = target.server.getPlayerList().getPlayer(request.requester());
|
||||
if (requester == null) {
|
||||
target.sendSystemMessage(Component.literal("That trade request is no longer valid."));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!withinTradeRange(requester, target)) {
|
||||
requester.sendSystemMessage(Component.literal(target.getGameProfile().getName() + " is too far away to trade."));
|
||||
target.sendSystemMessage(Component.literal(requester.getGameProfile().getName() + " is too far away to trade."));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!startTrade(requester, target)) {
|
||||
target.sendSystemMessage(Component.literal("Trade could not be started. One of you is already trading."));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean declinePendingTrade(ServerPlayer target) {
|
||||
TradeRequest request = pendingRequestsByTarget.remove(target.getUUID());
|
||||
if (request == null) {
|
||||
target.sendSystemMessage(Component.literal("You have no pending trade request."));
|
||||
return false;
|
||||
}
|
||||
|
||||
ServerPlayer requester = target.server.getPlayerList().getPlayer(request.requester());
|
||||
if (requester != null) {
|
||||
requester.sendSystemMessage(Component.literal(target.getGameProfile().getName() + " declined your trade request."));
|
||||
}
|
||||
target.sendSystemMessage(Component.literal("Trade request declined."));
|
||||
return true;
|
||||
}
|
||||
|
||||
public void handleAction(ServerPlayer player, UUID sessionId, TradeAction action, int slot, int amount) {
|
||||
TradeSession session = sessionsById.get(sessionId);
|
||||
if (session != null && session.involves(player)) {
|
||||
@@ -124,6 +204,8 @@ public class TradeManager {
|
||||
}
|
||||
|
||||
public void handleDisconnect(ServerPlayer player) {
|
||||
clearPendingRequests(player);
|
||||
|
||||
TradeSession session = getSession(player);
|
||||
if (session != null) {
|
||||
cancel(session, Component.literal(player.getGameProfile().getName() + " left the game."));
|
||||
@@ -306,4 +388,39 @@ public class TradeManager {
|
||||
debugSessionsById.remove(session.id());
|
||||
debugSessionByPlayer.remove(session.player().getUUID());
|
||||
}
|
||||
|
||||
private boolean withinTradeRange(ServerPlayer first, ServerPlayer second) {
|
||||
int maxDistance = TradeConfig.tradeCommandProximity();
|
||||
if (maxDistance <= 0) {
|
||||
return true;
|
||||
}
|
||||
if (first.level() != second.level()) {
|
||||
return false;
|
||||
}
|
||||
double maxDistanceSquared = maxDistance * maxDistance;
|
||||
return first.distanceToSqr(second) <= maxDistanceSquared;
|
||||
}
|
||||
|
||||
private void clearPendingRequests(ServerPlayer player) {
|
||||
TradeRequest direct = pendingRequestsByTarget.remove(player.getUUID());
|
||||
if (direct != null) {
|
||||
ServerPlayer requester = player.server.getPlayerList().getPlayer(direct.requester());
|
||||
if (requester != null) {
|
||||
requester.sendSystemMessage(Component.literal("Your trade request to " + player.getGameProfile().getName() + " expired."));
|
||||
}
|
||||
}
|
||||
|
||||
pendingRequestsByTarget.entrySet().removeIf(entry -> {
|
||||
if (!entry.getValue().requester().equals(player.getUUID())) {
|
||||
return false;
|
||||
}
|
||||
ServerPlayer target = player.server.getPlayerList().getPlayer(entry.getValue().target());
|
||||
if (target != null) {
|
||||
target.sendSystemMessage(Component.literal("The trade request from " + player.getGameProfile().getName() + " expired."));
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private record TradeRequest(UUID requester, UUID target) {}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"key.trade.request_trade": "Request Trade",
|
||||
"trade.trade.title": "Trading Screen",
|
||||
"trade.trade.offer": "Offer screen",
|
||||
"trade.trade.confirm": "Confirmation screen"
|
||||
|
||||
Reference in New Issue
Block a user