Compare commits
3 Commits
4daed48f0e
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4cd7674996 | ||
|
|
9cb4979662 | ||
|
|
89d6545533 |
61
README.md
61
README.md
@@ -12,9 +12,12 @@ Players can request a trade, review both offers in a shared GUI, accept once to
|
|||||||
- Shared trade screen with separate offer areas for both players
|
- Shared trade screen with separate offer areas for both players
|
||||||
- Two-step confirmation flow
|
- Two-step confirmation flow
|
||||||
- Quantity-based item selection with quick amounts and `Trade X`
|
- Quantity-based item selection with quick amounts and `Trade X`
|
||||||
|
- Warning if items are removed/modified
|
||||||
- Inventory-safe finalization: nothing changes until safety and security checks pass and the trade succeeds
|
- Inventory-safe finalization: nothing changes until safety and security checks pass and the trade succeeds
|
||||||
- Configurable trade safety checks to prevent dangerous mid-combat or mid-movement trading
|
- Configurable trade safety checks to prevent dangerous mid-combat or mid-movement trading
|
||||||
|
- Configurable per-player request cooldowns
|
||||||
- Per-player trade toggles and ignore list support
|
- Per-player trade toggles and ignore list support
|
||||||
|
- Admin-controlled player and item trade blacklists, with config defaults and live commands
|
||||||
- File-based trade audit log for server operators
|
- File-based trade audit log for server operators
|
||||||
|
|
||||||
## Player Usage
|
## Player Usage
|
||||||
@@ -49,6 +52,12 @@ They can also respond manually:
|
|||||||
- Once both players accept, the screen enters the confirmation stage
|
- Once both players accept, the screen enters the confirmation stage
|
||||||
- Click `Confirm` to finalize
|
- Click `Confirm` to finalize
|
||||||
|
|
||||||
|
If a player reduces or removes items from an offer:
|
||||||
|
|
||||||
|
- any pending accepts / confirms are invalidated
|
||||||
|
- the changed offer slot gets a blinking red border and `!`
|
||||||
|
- the trade window shows a red `Trade Modified` warning
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
### Player commands
|
### Player commands
|
||||||
@@ -65,6 +74,23 @@ They can also respond manually:
|
|||||||
/trade ignorelist
|
/trade ignorelist
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Admin commands
|
||||||
|
|
||||||
|
Server operators can manage persistent live blacklists with:
|
||||||
|
|
||||||
|
```mcfunction
|
||||||
|
/trade admin playerblock add <player|uuid>
|
||||||
|
/trade admin playerblock remove <player|uuid>
|
||||||
|
/trade admin playerblock list
|
||||||
|
/trade admin itemblock add item <namespace:item>
|
||||||
|
/trade admin itemblock remove item <namespace:item>
|
||||||
|
/trade admin itemblock add tag <namespace:tag>
|
||||||
|
/trade admin itemblock remove tag <namespace:tag>
|
||||||
|
/trade admin itemblock add mod <modid>
|
||||||
|
/trade admin itemblock remove mod <modid>
|
||||||
|
/trade admin itemblock list
|
||||||
|
```
|
||||||
|
|
||||||
### Command behavior
|
### Command behavior
|
||||||
|
|
||||||
- `/trade <player>` sends a trade request
|
- `/trade <player>` sends a trade request
|
||||||
@@ -109,6 +135,7 @@ The trade verifies:
|
|||||||
- both players are still within configured trade distance, if enabled
|
- both players are still within configured trade distance, if enabled
|
||||||
- both live inventories still match the snapshots taken when the trade started
|
- both live inventories still match the snapshots taken when the trade started
|
||||||
- both players can receive the incoming items
|
- both players can receive the incoming items
|
||||||
|
- both players have re-accepted after any offer changes
|
||||||
|
|
||||||
If any check fails, the trade is cancelled and both players receive an explicit chat message explaining why.
|
If any check fails, the trade is cancelled and both players receive an explicit chat message explaining why.
|
||||||
|
|
||||||
@@ -127,12 +154,26 @@ Server config values live under the `trade` section.
|
|||||||
- default: `30`
|
- default: `30`
|
||||||
- number of seconds before a pending trade request expires
|
- number of seconds before a pending trade request expires
|
||||||
|
|
||||||
|
- `requestCooldownSeconds`
|
||||||
|
- default: `5`
|
||||||
|
- number of seconds a player must wait before sending another trade request
|
||||||
|
|
||||||
### Debug
|
### Debug
|
||||||
|
|
||||||
- `enableDebugFeatures`
|
- `enableDebugFeatures`
|
||||||
- default: `false`
|
- default: `false`
|
||||||
- enables debug commands and debug UI/testing tools
|
- enables debug commands and debug UI/testing tools
|
||||||
|
|
||||||
|
### Trade flow
|
||||||
|
|
||||||
|
- `requireSecondConfirmation`
|
||||||
|
- default: `true`
|
||||||
|
- requires the second confirm step after both players accept the offer
|
||||||
|
|
||||||
|
- `showTradeModifiedWarnings`
|
||||||
|
- default: `true`
|
||||||
|
- shows the `Trade Modified` warning and changed-slot highlights when offers are reduced or removed
|
||||||
|
|
||||||
### Safety
|
### Safety
|
||||||
|
|
||||||
- `requireOnGround`
|
- `requireOnGround`
|
||||||
@@ -168,6 +209,26 @@ Server config values live under the `trade` section.
|
|||||||
- `requireSameDimension`
|
- `requireSameDimension`
|
||||||
- default: `true`
|
- default: `true`
|
||||||
|
|
||||||
|
### Admin blacklists
|
||||||
|
|
||||||
|
- `adminDisabledPlayerUuids`
|
||||||
|
- default: `[]`
|
||||||
|
- UUIDs of players who cannot use the trade system at all
|
||||||
|
|
||||||
|
- `adminBlacklistedItemIds`
|
||||||
|
- default: command blocks, command block minecarts, barriers, bedrock, end portal blocks/frames, structure blocks, jigsaws, lights, and spawners
|
||||||
|
- exact item ids that cannot be traded, like `minecraft:diamond`
|
||||||
|
|
||||||
|
- `adminBlacklistedItemTags`
|
||||||
|
- default: `[]`
|
||||||
|
- item tags that cannot be traded, like `minecraft:logs`
|
||||||
|
|
||||||
|
- `adminBlacklistedMods`
|
||||||
|
- default: `[]`
|
||||||
|
- mod namespaces that cannot be traded, like `minecraft`
|
||||||
|
|
||||||
|
Config blacklist entries are treated as defaults. Live admin commands add and remove separate saved entries that persist across restarts.
|
||||||
|
|
||||||
## Audit Log
|
## Audit Log
|
||||||
|
|
||||||
The mod writes a trade audit log to:
|
The mod writes a trade audit log to:
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import com.trunksbomb.trade.trade.TradeView;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import net.minecraft.ChatFormatting;
|
||||||
import net.minecraft.client.gui.GuiGraphics;
|
import net.minecraft.client.gui.GuiGraphics;
|
||||||
import net.minecraft.client.gui.components.Button;
|
import net.minecraft.client.gui.components.Button;
|
||||||
import net.minecraft.client.gui.components.EditBox;
|
import net.minecraft.client.gui.components.EditBox;
|
||||||
@@ -37,8 +38,9 @@ public class TradeScreen extends AbstractContainerScreen<TradeScreen.TradeMenu>
|
|||||||
private static final int BANNER_X = 58;
|
private static final int BANNER_X = 58;
|
||||||
private static final int BANNER_Y = 6;
|
private static final int BANNER_Y = 6;
|
||||||
private static final int CENTER_COLUMN_X = 115;
|
private static final int CENTER_COLUMN_X = 115;
|
||||||
private static final int STATUS_LABEL_Y = 54;
|
private static final int MODIFIED_LABEL_Y = 17;
|
||||||
private static final int CONFIRM_LABEL_Y = 34;
|
private static final int STATUS_LABEL_Y = 51;
|
||||||
|
private static final int CONFIRM_LABEL_Y = 26;
|
||||||
private static final int ACCEPT_BUTTON_Y = 74;
|
private static final int ACCEPT_BUTTON_Y = 74;
|
||||||
private static final int CANCEL_BUTTON_Y = 98;
|
private static final int CANCEL_BUTTON_Y = 98;
|
||||||
private static final int ACTION_BUTTON_WIDTH = 48;
|
private static final int ACTION_BUTTON_WIDTH = 48;
|
||||||
@@ -196,6 +198,8 @@ public class TradeScreen extends AbstractContainerScreen<TradeScreen.TradeMenu>
|
|||||||
if (hoveredSlot instanceof GhostInventorySlot ghostInventorySlot) {
|
if (hoveredSlot instanceof GhostInventorySlot ghostInventorySlot) {
|
||||||
if (button == 1) {
|
if (button == 1) {
|
||||||
openContextMenu(mouseX, mouseY, TradeAction.ADD_ITEM, ghostInventorySlot.inventoryIndex(), hoveredSlot.getItem(), "Trade");
|
openContextMenu(mouseX, mouseY, TradeAction.ADD_ITEM, ghostInventorySlot.inventoryIndex(), hoveredSlot.getItem(), "Trade");
|
||||||
|
} else if (hasShiftDown()) {
|
||||||
|
sendAction(TradeAction.ADD_ITEM, ghostInventorySlot.inventoryIndex(), hoveredSlot.getItem().getCount());
|
||||||
} else {
|
} else {
|
||||||
sendAction(TradeAction.ADD_ITEM, ghostInventorySlot.inventoryIndex(), 1);
|
sendAction(TradeAction.ADD_ITEM, ghostInventorySlot.inventoryIndex(), 1);
|
||||||
}
|
}
|
||||||
@@ -218,6 +222,9 @@ public class TradeScreen extends AbstractContainerScreen<TradeScreen.TradeMenu>
|
|||||||
protected void renderLabels(GuiGraphics guiGraphics, int mouseX, int mouseY) {
|
protected void renderLabels(GuiGraphics guiGraphics, int mouseX, int mouseY) {
|
||||||
guiGraphics.drawString(font, "Trading with " + menu.view().otherName(), titleLabelX, titleLabelY, 0x404040, false);
|
guiGraphics.drawString(font, "Trading with " + menu.view().otherName(), titleLabelX, titleLabelY, 0x404040, false);
|
||||||
guiGraphics.drawString(font, playerInventoryTitle, inventoryLabelX, inventoryLabelY, 0x404040, false);
|
guiGraphics.drawString(font, playerInventoryTitle, inventoryLabelX, inventoryLabelY, 0x404040, false);
|
||||||
|
if (menu.view().itemsChanged()) {
|
||||||
|
drawScaledCenteredColumnText(guiGraphics, "Trade Modified", MODIFIED_LABEL_Y, 0xB02020, 0.7F);
|
||||||
|
}
|
||||||
if (menu.view().stage() == TradeStage.CONFIRMING) {
|
if (menu.view().stage() == TradeStage.CONFIRMING) {
|
||||||
drawScaledCenteredColumnText(guiGraphics, "Are you", CONFIRM_LABEL_Y, 0xB02020, 0.7F);
|
drawScaledCenteredColumnText(guiGraphics, "Are you", CONFIRM_LABEL_Y, 0xB02020, 0.7F);
|
||||||
drawScaledCenteredColumnText(guiGraphics, "sure you want", CONFIRM_LABEL_Y + 7, 0xB02020, 0.7F);
|
drawScaledCenteredColumnText(guiGraphics, "sure you want", CONFIRM_LABEL_Y + 7, 0xB02020, 0.7F);
|
||||||
@@ -245,15 +252,92 @@ public class TradeScreen extends AbstractContainerScreen<TradeScreen.TradeMenu>
|
|||||||
acceptButton.setMessage(acceptButtonLabel());
|
acceptButton.setMessage(acceptButtonLabel());
|
||||||
}
|
}
|
||||||
super.render(guiGraphics, mouseX, mouseY, partialTick);
|
super.render(guiGraphics, mouseX, mouseY, partialTick);
|
||||||
|
renderProhibitedSlotWarnings(guiGraphics);
|
||||||
|
renderChangedSlotWarnings(guiGraphics);
|
||||||
renderContextMenu(guiGraphics, mouseX, mouseY);
|
renderContextMenu(guiGraphics, mouseX, mouseY);
|
||||||
renderAmountPrompt(guiGraphics);
|
renderAmountPrompt(guiGraphics);
|
||||||
renderTooltip(guiGraphics, mouseX, mouseY);
|
renderTradeTooltip(guiGraphics, mouseX, mouseY);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Component acceptButtonLabel() {
|
private Component acceptButtonLabel() {
|
||||||
return Component.literal(menu.view().stage() == TradeStage.CONFIRMING ? "Confirm" : "Accept");
|
return Component.literal(menu.view().stage() == TradeStage.CONFIRMING ? "Confirm" : "Accept");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void renderChangedSlotWarnings(GuiGraphics guiGraphics) {
|
||||||
|
if (!menu.view().itemsChanged()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean blinkOn = ((System.currentTimeMillis() / 300L) & 1L) == 0L;
|
||||||
|
int color = blinkOn ? 0xFFFF4040 : 0xFF7A1010;
|
||||||
|
for (Slot slot : menu.slots) {
|
||||||
|
if (slot instanceof SelfOfferSlot selfOfferSlot) {
|
||||||
|
if (menu.view().selfChangedSlots().get(selfOfferSlot.offerIndex())) {
|
||||||
|
renderChangedSlotWarning(guiGraphics, slot, color);
|
||||||
|
}
|
||||||
|
} else if (slot instanceof OtherOfferSlot otherOfferSlot) {
|
||||||
|
if (menu.view().otherChangedSlots().get(otherOfferSlot.offerIndex())) {
|
||||||
|
renderChangedSlotWarning(guiGraphics, slot, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void renderChangedSlotWarning(GuiGraphics guiGraphics, Slot slot, int color) {
|
||||||
|
int x = leftPos + slot.x;
|
||||||
|
int y = topPos + slot.y;
|
||||||
|
guiGraphics.fill(x - 1, y - 1, x + 17, y, color);
|
||||||
|
guiGraphics.fill(x - 1, y + 16, x + 17, y + 17, color);
|
||||||
|
guiGraphics.fill(x - 1, y, x, y + 16, color);
|
||||||
|
guiGraphics.fill(x + 16, y, x + 17, y + 16, color);
|
||||||
|
guiGraphics.drawString(font, "!", x + 11, y - 2, color, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void renderProhibitedSlotWarnings(GuiGraphics guiGraphics) {
|
||||||
|
int color = 0xFFE09A24;
|
||||||
|
for (Slot slot : menu.slots) {
|
||||||
|
if (isProhibitedSlot(slot)) {
|
||||||
|
renderProhibitedSlotWarning(guiGraphics, slot, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void renderProhibitedSlotWarning(GuiGraphics guiGraphics, Slot slot, int color) {
|
||||||
|
int x = leftPos + slot.x;
|
||||||
|
int y = topPos + slot.y;
|
||||||
|
guiGraphics.fill(x - 1, y - 1, x + 17, y, color);
|
||||||
|
guiGraphics.fill(x - 1, y + 16, x + 17, y + 17, color);
|
||||||
|
guiGraphics.fill(x - 1, y, x, y + 16, color);
|
||||||
|
guiGraphics.fill(x + 16, y, x + 17, y + 16, color);
|
||||||
|
guiGraphics.drawString(font, "X", x + 9, y - 2, color, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isProhibitedSlot(Slot slot) {
|
||||||
|
if (slot == null || !slot.hasItem()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (slot instanceof GhostInventorySlot ghostInventorySlot) {
|
||||||
|
return menu.view().inventoryBlockedSlots().get(ghostInventorySlot.inventoryIndex());
|
||||||
|
}
|
||||||
|
if (slot instanceof SelfOfferSlot selfOfferSlot) {
|
||||||
|
return menu.view().selfBlockedSlots().get(selfOfferSlot.offerIndex());
|
||||||
|
}
|
||||||
|
if (slot instanceof OtherOfferSlot otherOfferSlot) {
|
||||||
|
return menu.view().otherBlockedSlots().get(otherOfferSlot.offerIndex());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void renderTradeTooltip(GuiGraphics guiGraphics, int mouseX, int mouseY) {
|
||||||
|
if (hoveredSlot != null && hoveredSlot.hasItem() && isProhibitedSlot(hoveredSlot) && minecraft != null) {
|
||||||
|
List<Component> tooltip = new ArrayList<>(getTooltipFromItem(minecraft, hoveredSlot.getItem()));
|
||||||
|
tooltip.add(Component.literal("This item cannot be traded").withStyle(ChatFormatting.RED));
|
||||||
|
guiGraphics.renderTooltip(font, tooltip, hoveredSlot.getItem().getTooltipImage(), mouseX, mouseY);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
renderTooltip(guiGraphics, mouseX, mouseY);
|
||||||
|
}
|
||||||
|
|
||||||
private void sendAction(TradeAction action, int slot, int amount) {
|
private void sendAction(TradeAction action, int slot, int amount) {
|
||||||
if (minecraft == null || minecraft.getConnection() == null) {
|
if (minecraft == null || minecraft.getConnection() == null) {
|
||||||
return;
|
return;
|
||||||
@@ -575,6 +659,10 @@ public class TradeScreen extends AbstractContainerScreen<TradeScreen.TradeMenu>
|
|||||||
super(container, slot, x, y);
|
super(container, slot, x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int offerIndex() {
|
||||||
|
return getSlotIndex();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean mayPickup(Player player) {
|
public boolean mayPickup(Player player) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -8,10 +8,12 @@ import com.trunksbomb.trade.mod.TradeConfig;
|
|||||||
import com.trunksbomb.trade.trade.DebugUnsafeState;
|
import com.trunksbomb.trade.trade.DebugUnsafeState;
|
||||||
import com.trunksbomb.trade.trade.DebugTradeSession;
|
import com.trunksbomb.trade.trade.DebugTradeSession;
|
||||||
import com.trunksbomb.trade.trade.TradeManager;
|
import com.trunksbomb.trade.trade.TradeManager;
|
||||||
|
import java.util.UUID;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import net.minecraft.commands.CommandSourceStack;
|
import net.minecraft.commands.CommandSourceStack;
|
||||||
import net.minecraft.commands.Commands;
|
import net.minecraft.commands.Commands;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraft.server.level.ServerPlayer;
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
|
|
||||||
public final class TradeCommand {
|
public final class TradeCommand {
|
||||||
@@ -53,6 +55,7 @@ public final class TradeCommand {
|
|||||||
.then(Commands.literal("close").executes(context -> closeDebug(context.getSource()))));
|
.then(Commands.literal("close").executes(context -> closeDebug(context.getSource()))));
|
||||||
|
|
||||||
dispatcher.register(trade
|
dispatcher.register(trade
|
||||||
|
.then(adminCommands())
|
||||||
.then(Commands.literal("yes").executes(context -> respondTrade(context.getSource(), true)))
|
.then(Commands.literal("yes").executes(context -> respondTrade(context.getSource(), true)))
|
||||||
.then(Commands.literal("no").executes(context -> respondTrade(context.getSource(), false)))
|
.then(Commands.literal("no").executes(context -> respondTrade(context.getSource(), false)))
|
||||||
.then(Commands.literal("toggle").executes(context -> toggleTrade(context.getSource())))
|
.then(Commands.literal("toggle").executes(context -> toggleTrade(context.getSource())))
|
||||||
@@ -72,6 +75,43 @@ public final class TradeCommand {
|
|||||||
.executes(context -> requestTrade(context.getSource(), StringArgumentType.getString(context, "player")))));
|
.executes(context -> requestTrade(context.getSource(), StringArgumentType.getString(context, "player")))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static com.mojang.brigadier.builder.LiteralArgumentBuilder<CommandSourceStack> adminCommands() {
|
||||||
|
return Commands.literal("admin")
|
||||||
|
.requires(source -> source.hasPermission(2))
|
||||||
|
.then(Commands.literal("playerblock")
|
||||||
|
.then(Commands.literal("add")
|
||||||
|
.then(Commands.argument("player", StringArgumentType.word())
|
||||||
|
.suggests((context, builder) -> suggestPlayers(context.getSource(), builder))
|
||||||
|
.executes(context -> addPlayerBlock(context.getSource(), StringArgumentType.getString(context, "player")))))
|
||||||
|
.then(Commands.literal("remove")
|
||||||
|
.then(Commands.argument("player", StringArgumentType.word())
|
||||||
|
.suggests((context, builder) -> suggestPlayers(context.getSource(), builder))
|
||||||
|
.executes(context -> removePlayerBlock(context.getSource(), StringArgumentType.getString(context, "player")))))
|
||||||
|
.then(Commands.literal("list").executes(context -> listPlayerBlocks(context.getSource()))))
|
||||||
|
.then(Commands.literal("itemblock")
|
||||||
|
.then(Commands.literal("add")
|
||||||
|
.then(Commands.literal("item")
|
||||||
|
.then(Commands.argument("id", StringArgumentType.word())
|
||||||
|
.executes(context -> addItemBlock(context.getSource(), BlacklistKind.ITEM, StringArgumentType.getString(context, "id")))))
|
||||||
|
.then(Commands.literal("tag")
|
||||||
|
.then(Commands.argument("id", StringArgumentType.word())
|
||||||
|
.executes(context -> addItemBlock(context.getSource(), BlacklistKind.TAG, StringArgumentType.getString(context, "id")))))
|
||||||
|
.then(Commands.literal("mod")
|
||||||
|
.then(Commands.argument("id", StringArgumentType.word())
|
||||||
|
.executes(context -> addItemBlock(context.getSource(), BlacklistKind.MOD, StringArgumentType.getString(context, "id"))))))
|
||||||
|
.then(Commands.literal("remove")
|
||||||
|
.then(Commands.literal("item")
|
||||||
|
.then(Commands.argument("id", StringArgumentType.word())
|
||||||
|
.executes(context -> removeItemBlock(context.getSource(), BlacklistKind.ITEM, StringArgumentType.getString(context, "id")))))
|
||||||
|
.then(Commands.literal("tag")
|
||||||
|
.then(Commands.argument("id", StringArgumentType.word())
|
||||||
|
.executes(context -> removeItemBlock(context.getSource(), BlacklistKind.TAG, StringArgumentType.getString(context, "id")))))
|
||||||
|
.then(Commands.literal("mod")
|
||||||
|
.then(Commands.argument("id", StringArgumentType.word())
|
||||||
|
.executes(context -> removeItemBlock(context.getSource(), BlacklistKind.MOD, StringArgumentType.getString(context, "id"))))))
|
||||||
|
.then(Commands.literal("list").executes(context -> listItemBlocks(context.getSource()))));
|
||||||
|
}
|
||||||
|
|
||||||
private static CompletableFuture<com.mojang.brigadier.suggestion.Suggestions> suggestPlayers(CommandSourceStack source, SuggestionsBuilder builder) {
|
private static CompletableFuture<com.mojang.brigadier.suggestion.Suggestions> suggestPlayers(CommandSourceStack source, SuggestionsBuilder builder) {
|
||||||
for (ServerPlayer player : source.getServer().getPlayerList().getPlayers()) {
|
for (ServerPlayer player : source.getServer().getPlayerList().getPlayers()) {
|
||||||
if (!player.getGameProfile().getName().equalsIgnoreCase(source.getTextName())) {
|
if (!player.getGameProfile().getName().equalsIgnoreCase(source.getTextName())) {
|
||||||
@@ -214,6 +254,146 @@ public final class TradeCommand {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int addPlayerBlock(CommandSourceStack source, String targetSpec) {
|
||||||
|
ResolvedPlayer target = resolvePlayerOrUuid(source, targetSpec);
|
||||||
|
if (target == null) {
|
||||||
|
source.sendFailure(Component.literal("That player is not online, and the value is not a valid UUID."));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
TradeManager manager = TradeManager.get(source.getServer());
|
||||||
|
boolean changed = target.player() != null
|
||||||
|
? manager.addAdminDisabledPlayer(target.player())
|
||||||
|
: manager.addAdminDisabledPlayer(target.uuid(), source.getServer());
|
||||||
|
if (!changed) {
|
||||||
|
source.sendFailure(Component.literal(target.label() + " is already blocked from trading."));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
source.sendSuccess(() -> Component.literal(target.label() + " can no longer use the trade system."), true);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int removePlayerBlock(CommandSourceStack source, String targetSpec) {
|
||||||
|
ResolvedPlayer target = resolvePlayerOrUuid(source, targetSpec);
|
||||||
|
if (target == null) {
|
||||||
|
source.sendFailure(Component.literal("That player is not online, and the value is not a valid UUID."));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
TradeManager manager = TradeManager.get(source.getServer());
|
||||||
|
boolean changed = target.player() != null
|
||||||
|
? manager.removeAdminDisabledPlayer(target.player())
|
||||||
|
: manager.removeAdminDisabledPlayer(target.uuid(), source.getServer());
|
||||||
|
if (!changed) {
|
||||||
|
source.sendFailure(Component.literal(target.label() + " is not blocked from trading."));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
source.sendSuccess(() -> Component.literal(target.label() + " can use the trade system again."), true);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int listPlayerBlocks(CommandSourceStack source) {
|
||||||
|
java.util.List<String> blocked = TradeManager.get(source.getServer()).adminDisabledPlayerNames(source.getServer());
|
||||||
|
if (blocked.isEmpty()) {
|
||||||
|
source.sendSuccess(() -> Component.literal("No admin trade-blocked players are stored in data."), false);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
source.sendSuccess(() -> Component.literal("Admin trade-blocked players: " + String.join(", ", blocked)), false);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int addItemBlock(CommandSourceStack source, BlacklistKind kind, String rawValue) {
|
||||||
|
String value = normalizeBlacklistValue(kind, rawValue);
|
||||||
|
if (value == null) {
|
||||||
|
source.sendFailure(Component.literal(kind.invalidMessage()));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
TradeManager manager = TradeManager.get(source.getServer());
|
||||||
|
boolean changed = switch (kind) {
|
||||||
|
case ITEM -> manager.addAdminBlacklistedItemId(source.getServer(), value);
|
||||||
|
case TAG -> manager.addAdminBlacklistedItemTag(source.getServer(), value);
|
||||||
|
case MOD -> manager.addAdminBlacklistedMod(source.getServer(), value);
|
||||||
|
};
|
||||||
|
if (!changed) {
|
||||||
|
source.sendFailure(Component.literal(kind.label() + " blacklist already contains " + value + "."));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
source.sendSuccess(() -> Component.literal("Added " + kind.label() + " blacklist entry: " + value + "."), true);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int removeItemBlock(CommandSourceStack source, BlacklistKind kind, String rawValue) {
|
||||||
|
String value = normalizeBlacklistValue(kind, rawValue);
|
||||||
|
if (value == null) {
|
||||||
|
source.sendFailure(Component.literal(kind.invalidMessage()));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
TradeManager manager = TradeManager.get(source.getServer());
|
||||||
|
boolean changed = switch (kind) {
|
||||||
|
case ITEM -> manager.removeAdminBlacklistedItemId(source.getServer(), value);
|
||||||
|
case TAG -> manager.removeAdminBlacklistedItemTag(source.getServer(), value);
|
||||||
|
case MOD -> manager.removeAdminBlacklistedMod(source.getServer(), value);
|
||||||
|
};
|
||||||
|
if (!changed) {
|
||||||
|
source.sendFailure(Component.literal(kind.label() + " blacklist does not contain " + value + "."));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
source.sendSuccess(() -> Component.literal("Removed " + kind.label() + " blacklist entry: " + value + "."), true);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int listItemBlocks(CommandSourceStack source) {
|
||||||
|
TradeManager manager = TradeManager.get(source.getServer());
|
||||||
|
java.util.List<String> itemIds = manager.adminBlacklistedItemIds(source.getServer());
|
||||||
|
java.util.List<String> tags = manager.adminBlacklistedItemTags(source.getServer());
|
||||||
|
java.util.List<String> mods = manager.adminBlacklistedMods(source.getServer());
|
||||||
|
source.sendSuccess(
|
||||||
|
() -> Component.literal("Admin item blocks | item ids: "
|
||||||
|
+ joinOrNone(itemIds)
|
||||||
|
+ " | tags: "
|
||||||
|
+ joinOrNone(tags)
|
||||||
|
+ " | mods: "
|
||||||
|
+ joinOrNone(mods)),
|
||||||
|
false);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String joinOrNone(java.util.List<String> values) {
|
||||||
|
return values.isEmpty() ? "(none)" : String.join(", ", values);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String normalizeBlacklistValue(BlacklistKind kind, String rawValue) {
|
||||||
|
String value = rawValue.trim().toLowerCase(java.util.Locale.ROOT);
|
||||||
|
if (value.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return switch (kind) {
|
||||||
|
case ITEM, TAG -> ResourceLocation.tryParse(value) != null ? value : null;
|
||||||
|
case MOD -> value.matches("[a-z0-9_.-]+") ? value : null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ResolvedPlayer resolvePlayerOrUuid(CommandSourceStack source, String targetSpec) {
|
||||||
|
ServerPlayer online = source.getServer().getPlayerList().getPlayerByName(targetSpec);
|
||||||
|
if (online != null) {
|
||||||
|
return new ResolvedPlayer(online, online.getUUID(), online.getGameProfile().getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
UUID uuid = UUID.fromString(targetSpec);
|
||||||
|
return new ResolvedPlayer(null, uuid, uuid.toString());
|
||||||
|
} catch (IllegalArgumentException ignored) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static int initDebug(CommandSourceStack source, int delaySeconds) {
|
private static int initDebug(CommandSourceStack source, int delaySeconds) {
|
||||||
if (!(source.getEntity() instanceof ServerPlayer player)) {
|
if (!(source.getEntity() instanceof ServerPlayer player)) {
|
||||||
source.sendFailure(Component.literal("Only players can start debug trades."));
|
source.sendFailure(Component.literal("Only players can start debug trades."));
|
||||||
@@ -260,7 +440,7 @@ public final class TradeCommand {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (!TradeManager.get(source.getServer()).setDebugOffer(player, DebugTradeSession.parseOfferSpec(spec))) {
|
if (!TradeManager.get(source.getServer()).setDebugOffer(player, DebugTradeSession.parseOfferSpec(spec))) {
|
||||||
source.sendFailure(Component.literal("Start a debug trade first with /trade debug init."));
|
source.sendFailure(Component.literal("Start a debug trade first with /trade debug init, and only change offers before confirmation."));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
} catch (IllegalArgumentException exception) {
|
} catch (IllegalArgumentException exception) {
|
||||||
@@ -285,6 +465,10 @@ public final class TradeCommand {
|
|||||||
try {
|
try {
|
||||||
int result = TradeManager.get(source.getServer()).removeDebugOffer(player, spec);
|
int result = TradeManager.get(source.getServer()).removeDebugOffer(player, spec);
|
||||||
if (result < 0) {
|
if (result < 0) {
|
||||||
|
if (result == -2) {
|
||||||
|
source.sendFailure(Component.literal("Trade offers cannot be changed during confirmation."));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
source.sendFailure(Component.literal("Start a debug trade first with /trade debug init."));
|
source.sendFailure(Component.literal("Start a debug trade first with /trade debug init."));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -353,4 +537,28 @@ public final class TradeCommand {
|
|||||||
source.sendSuccess(() -> Component.literal(successMessage), false);
|
source.sendSuccess(() -> Component.literal(successMessage), false);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private enum BlacklistKind {
|
||||||
|
ITEM("item id", "Item ids must look like namespace:path."),
|
||||||
|
TAG("tag", "Tags must look like namespace:path."),
|
||||||
|
MOD("mod", "Mod ids may only contain lowercase letters, numbers, underscore, dash, and dot.");
|
||||||
|
|
||||||
|
private final String label;
|
||||||
|
private final String invalidMessage;
|
||||||
|
|
||||||
|
BlacklistKind(String label, String invalidMessage) {
|
||||||
|
this.label = label;
|
||||||
|
this.invalidMessage = invalidMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String label() {
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String invalidMessage() {
|
||||||
|
return invalidMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private record ResolvedPlayer(ServerPlayer player, UUID uuid, String label) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,10 @@ public final class TradeConfig {
|
|||||||
public static final ModConfigSpec SPEC;
|
public static final ModConfigSpec SPEC;
|
||||||
private static final ModConfigSpec.IntValue TRADE_COMMAND_PROXIMITY;
|
private static final ModConfigSpec.IntValue TRADE_COMMAND_PROXIMITY;
|
||||||
private static final ModConfigSpec.IntValue REQUEST_TIMEOUT_SECONDS;
|
private static final ModConfigSpec.IntValue REQUEST_TIMEOUT_SECONDS;
|
||||||
|
private static final ModConfigSpec.IntValue REQUEST_COOLDOWN_SECONDS;
|
||||||
private static final ModConfigSpec.BooleanValue ENABLE_DEBUG_FEATURES;
|
private static final ModConfigSpec.BooleanValue ENABLE_DEBUG_FEATURES;
|
||||||
|
private static final ModConfigSpec.BooleanValue REQUIRE_SECOND_CONFIRMATION;
|
||||||
|
private static final ModConfigSpec.BooleanValue SHOW_TRADE_MODIFIED_WARNINGS;
|
||||||
private static final ModConfigSpec.BooleanValue REQUIRE_ON_GROUND;
|
private static final ModConfigSpec.BooleanValue REQUIRE_ON_GROUND;
|
||||||
private static final ModConfigSpec.BooleanValue REQUIRE_STATIONARY;
|
private static final ModConfigSpec.BooleanValue REQUIRE_STATIONARY;
|
||||||
private static final ModConfigSpec.DoubleValue STATIONARY_SPEED_THRESHOLD;
|
private static final ModConfigSpec.DoubleValue STATIONARY_SPEED_THRESHOLD;
|
||||||
@@ -18,6 +21,10 @@ public final class TradeConfig {
|
|||||||
private static final ModConfigSpec.BooleanValue REQUIRE_NOT_FALL_FLYING;
|
private static final ModConfigSpec.BooleanValue REQUIRE_NOT_FALL_FLYING;
|
||||||
private static final ModConfigSpec.BooleanValue REQUIRE_NOT_RIDING;
|
private static final ModConfigSpec.BooleanValue REQUIRE_NOT_RIDING;
|
||||||
private static final ModConfigSpec.BooleanValue REQUIRE_SAME_DIMENSION;
|
private static final ModConfigSpec.BooleanValue REQUIRE_SAME_DIMENSION;
|
||||||
|
private static final ModConfigSpec.ConfigValue<java.util.List<? extends String>> ADMIN_DISABLED_PLAYER_UUIDS;
|
||||||
|
private static final ModConfigSpec.ConfigValue<java.util.List<? extends String>> ADMIN_BLACKLISTED_ITEM_IDS;
|
||||||
|
private static final ModConfigSpec.ConfigValue<java.util.List<? extends String>> ADMIN_BLACKLISTED_ITEM_TAGS;
|
||||||
|
private static final ModConfigSpec.ConfigValue<java.util.List<? extends String>> ADMIN_BLACKLISTED_MODS;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
ModConfigSpec.Builder builder = new ModConfigSpec.Builder();
|
ModConfigSpec.Builder builder = new ModConfigSpec.Builder();
|
||||||
@@ -28,8 +35,15 @@ public final class TradeConfig {
|
|||||||
REQUEST_TIMEOUT_SECONDS = builder
|
REQUEST_TIMEOUT_SECONDS = builder
|
||||||
.comment("Seconds before a trade request expires.")
|
.comment("Seconds before a trade request expires.")
|
||||||
.defineInRange("requestTimeoutSeconds", 30, 1, 3600);
|
.defineInRange("requestTimeoutSeconds", 30, 1, 3600);
|
||||||
|
REQUEST_COOLDOWN_SECONDS = builder
|
||||||
|
.comment("Seconds a player must wait between trade requests. 0 disables the cooldown.")
|
||||||
|
.defineInRange("requestCooldownSeconds", 5, 0, 3600);
|
||||||
ENABLE_DEBUG_FEATURES = builder.comment("Enable debug trade commands and debug UI/testing tools.")
|
ENABLE_DEBUG_FEATURES = builder.comment("Enable debug trade commands and debug UI/testing tools.")
|
||||||
.define("enableDebugFeatures", false);
|
.define("enableDebugFeatures", false);
|
||||||
|
REQUIRE_SECOND_CONFIRMATION = builder.comment("Require a second confirmation step after both players accept the initial offer.")
|
||||||
|
.define("requireSecondConfirmation", true);
|
||||||
|
SHOW_TRADE_MODIFIED_WARNINGS = builder.comment("Show Trade Modified warnings and changed-slot highlights when offers are reduced or removed.")
|
||||||
|
.define("showTradeModifiedWarnings", true);
|
||||||
REQUIRE_ON_GROUND = builder.comment("Require players to be on solid ground before requesting or accepting a trade.")
|
REQUIRE_ON_GROUND = builder.comment("Require players to be on solid ground before requesting or accepting a trade.")
|
||||||
.define("requireOnGround", true);
|
.define("requireOnGround", true);
|
||||||
REQUIRE_STATIONARY = builder.comment("Require players to be stationary before requesting or accepting a trade.")
|
REQUIRE_STATIONARY = builder.comment("Require players to be stationary before requesting or accepting a trade.")
|
||||||
@@ -52,6 +66,33 @@ public final class TradeConfig {
|
|||||||
.define("requireNotRiding", true);
|
.define("requireNotRiding", true);
|
||||||
REQUIRE_SAME_DIMENSION = builder.comment("Require both players to be in the same dimension.")
|
REQUIRE_SAME_DIMENSION = builder.comment("Require both players to be in the same dimension.")
|
||||||
.define("requireSameDimension", true);
|
.define("requireSameDimension", true);
|
||||||
|
ADMIN_DISABLED_PLAYER_UUIDS = builder
|
||||||
|
.comment("Players who cannot use the trade system at all, identified by UUID.")
|
||||||
|
.defineListAllowEmpty("adminDisabledPlayerUuids", java.util.List.of(), value -> value instanceof String);
|
||||||
|
ADMIN_BLACKLISTED_ITEM_IDS = builder
|
||||||
|
.comment("Exact item ids that cannot be traded, like minecraft:diamond.")
|
||||||
|
.defineListAllowEmpty(
|
||||||
|
"adminBlacklistedItemIds",
|
||||||
|
java.util.List.of(
|
||||||
|
"minecraft:command_block",
|
||||||
|
"minecraft:chain_command_block",
|
||||||
|
"minecraft:repeating_command_block",
|
||||||
|
"minecraft:command_block_minecart",
|
||||||
|
"minecraft:barrier",
|
||||||
|
"minecraft:bedrock",
|
||||||
|
"minecraft:end_portal_frame",
|
||||||
|
"minecraft:end_portal",
|
||||||
|
"minecraft:structure_block",
|
||||||
|
"minecraft:jigsaw",
|
||||||
|
"minecraft:light",
|
||||||
|
"minecraft:spawner"),
|
||||||
|
value -> value instanceof String);
|
||||||
|
ADMIN_BLACKLISTED_ITEM_TAGS = builder
|
||||||
|
.comment("Item tags that cannot be traded, like c:gems or minecraft:logs.")
|
||||||
|
.defineListAllowEmpty("adminBlacklistedItemTags", java.util.List.of(), value -> value instanceof String);
|
||||||
|
ADMIN_BLACKLISTED_MODS = builder
|
||||||
|
.comment("Item namespaces/mod ids that cannot be traded.")
|
||||||
|
.defineListAllowEmpty("adminBlacklistedMods", java.util.List.of(), value -> value instanceof String);
|
||||||
builder.pop();
|
builder.pop();
|
||||||
SPEC = builder.build();
|
SPEC = builder.build();
|
||||||
}
|
}
|
||||||
@@ -66,6 +107,10 @@ public final class TradeConfig {
|
|||||||
return REQUEST_TIMEOUT_SECONDS.get();
|
return REQUEST_TIMEOUT_SECONDS.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int requestCooldownSeconds() {
|
||||||
|
return REQUEST_COOLDOWN_SECONDS.get();
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean enableDebugFeatures() {
|
public static boolean enableDebugFeatures() {
|
||||||
return ENABLE_DEBUG_FEATURES.get();
|
return ENABLE_DEBUG_FEATURES.get();
|
||||||
}
|
}
|
||||||
@@ -78,6 +123,14 @@ public final class TradeConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean requireSecondConfirmation() {
|
||||||
|
return REQUIRE_SECOND_CONFIRMATION.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean showTradeModifiedWarnings() {
|
||||||
|
return SHOW_TRADE_MODIFIED_WARNINGS.get();
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean requireOnGround() {
|
public static boolean requireOnGround() {
|
||||||
return REQUIRE_ON_GROUND.get();
|
return REQUIRE_ON_GROUND.get();
|
||||||
}
|
}
|
||||||
@@ -121,4 +174,24 @@ public final class TradeConfig {
|
|||||||
public static boolean requireSameDimension() {
|
public static boolean requireSameDimension() {
|
||||||
return REQUIRE_SAME_DIMENSION.get();
|
return REQUIRE_SAME_DIMENSION.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static java.util.List<String> adminDisabledPlayerUuids() {
|
||||||
|
return copyStringList(ADMIN_DISABLED_PLAYER_UUIDS.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static java.util.List<String> adminBlacklistedItemIds() {
|
||||||
|
return copyStringList(ADMIN_BLACKLISTED_ITEM_IDS.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static java.util.List<String> adminBlacklistedItemTags() {
|
||||||
|
return copyStringList(ADMIN_BLACKLISTED_ITEM_TAGS.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static java.util.List<String> adminBlacklistedMods() {
|
||||||
|
return copyStringList(ADMIN_BLACKLISTED_MODS.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static java.util.List<String> copyStringList(java.util.List<? extends String> values) {
|
||||||
|
return new java.util.ArrayList<>(values);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.trunksbomb.trade.trade;
|
|||||||
|
|
||||||
import com.trunksbomb.trade.network.TradeClosePayload;
|
import com.trunksbomb.trade.network.TradeClosePayload;
|
||||||
import com.trunksbomb.trade.network.TradeStatePayload;
|
import com.trunksbomb.trade.network.TradeStatePayload;
|
||||||
|
import com.trunksbomb.trade.mod.TradeConfig;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -26,6 +27,8 @@ public class DebugTradeSession {
|
|||||||
private final List<ItemStack> inventorySnapshot;
|
private final List<ItemStack> inventorySnapshot;
|
||||||
private final List<TradeEntry> selfOffer = blankOffer();
|
private final List<TradeEntry> selfOffer = blankOffer();
|
||||||
private final List<ItemStack> otherOffer = blankStacks();
|
private final List<ItemStack> otherOffer = blankStacks();
|
||||||
|
private final List<Boolean> selfChangedSlots = blankChangedSlots();
|
||||||
|
private final List<Boolean> otherChangedSlots = blankChangedSlots();
|
||||||
private final EnumSet<DebugUnsafeState> unsafeStates = EnumSet.noneOf(DebugUnsafeState.class);
|
private final EnumSet<DebugUnsafeState> unsafeStates = EnumSet.noneOf(DebugUnsafeState.class);
|
||||||
private boolean selfAccepted;
|
private boolean selfAccepted;
|
||||||
private boolean otherAccepted;
|
private boolean otherAccepted;
|
||||||
@@ -90,7 +93,7 @@ public class DebugTradeSession {
|
|||||||
ItemStack merged = entry.stack().copy();
|
ItemStack merged = entry.stack().copy();
|
||||||
merged.grow(moveAmount);
|
merged.grow(moveAmount);
|
||||||
selfOffer.set(i, new TradeEntry(inventorySlot, merged));
|
selfOffer.set(i, new TradeEntry(inventorySlot, merged));
|
||||||
clearAccepts();
|
clearAccepts(false, false, -1);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -101,7 +104,7 @@ public class DebugTradeSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
selfOffer.set(freeSlot, new TradeEntry(inventorySlot, sourceStack.copyWithCount(moveAmount)));
|
selfOffer.set(freeSlot, new TradeEntry(inventorySlot, sourceStack.copyWithCount(moveAmount)));
|
||||||
clearAccepts();
|
clearAccepts(false, false, -1);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,7 +127,7 @@ public class DebugTradeSession {
|
|||||||
selfOffer.set(offerSlot, null);
|
selfOffer.set(offerSlot, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
clearAccepts();
|
clearAccepts(true, false, offerSlot);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,7 +143,7 @@ public class DebugTradeSession {
|
|||||||
for (int i = 0; i < OFFER_SLOT_COUNT; i++) {
|
for (int i = 0; i < OFFER_SLOT_COUNT; i++) {
|
||||||
otherOffer.set(i, i < offer.size() ? offer.get(i).copy() : ItemStack.EMPTY);
|
otherOffer.set(i, i < offer.size() ? offer.get(i).copy() : ItemStack.EMPTY);
|
||||||
}
|
}
|
||||||
clearAccepts();
|
clearAccepts(false, false, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean appendOtherOffer(List<ItemStack> offer) {
|
public boolean appendOtherOffer(List<ItemStack> offer) {
|
||||||
@@ -156,18 +159,25 @@ public class DebugTradeSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (changed) {
|
if (changed) {
|
||||||
clearAccepts();
|
clearAccepts(false, false, -1);
|
||||||
}
|
}
|
||||||
return changed;
|
return changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean removeOtherOffer(String spec) {
|
public boolean removeOtherOffer(String spec) {
|
||||||
if ("all".equalsIgnoreCase(spec)) {
|
if ("all".equalsIgnoreCase(spec)) {
|
||||||
|
boolean changed = false;
|
||||||
for (int i = 0; i < OFFER_SLOT_COUNT; i++) {
|
for (int i = 0; i < OFFER_SLOT_COUNT; i++) {
|
||||||
|
if (!otherOffer.get(i).isEmpty()) {
|
||||||
|
otherChangedSlots.set(i, true);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
otherOffer.set(i, ItemStack.EMPTY);
|
otherOffer.set(i, ItemStack.EMPTY);
|
||||||
}
|
}
|
||||||
clearAccepts();
|
if (changed) {
|
||||||
return true;
|
clearAccepts(false, false, -1);
|
||||||
|
}
|
||||||
|
return changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
int split = spec.indexOf(':');
|
int split = spec.indexOf(':');
|
||||||
@@ -194,7 +204,7 @@ public class DebugTradeSession {
|
|||||||
otherOffer.set(slot, updated);
|
otherOffer.set(slot, updated);
|
||||||
}
|
}
|
||||||
|
|
||||||
clearAccepts();
|
clearAccepts(false, true, slot);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,7 +214,7 @@ public class DebugTradeSession {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
otherOffer.set(i, ItemStack.EMPTY);
|
otherOffer.set(i, ItemStack.EMPTY);
|
||||||
clearAccepts();
|
clearAccepts(false, true, i);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -263,6 +273,7 @@ public class DebugTradeSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public TradeView view() {
|
public TradeView view() {
|
||||||
|
boolean showModifiedWarnings = TradeConfig.showTradeModifiedWarnings();
|
||||||
return new TradeView(
|
return new TradeView(
|
||||||
id,
|
id,
|
||||||
player.getGameProfile().getName(),
|
player.getGameProfile().getName(),
|
||||||
@@ -271,10 +282,16 @@ public class DebugTradeSession {
|
|||||||
stage,
|
stage,
|
||||||
selfAccepted,
|
selfAccepted,
|
||||||
otherAccepted,
|
otherAccepted,
|
||||||
|
showModifiedWarnings && hasChangedSlots(),
|
||||||
inventoryDisplay(),
|
inventoryDisplay(),
|
||||||
emptyReservedSnapshot(),
|
emptyReservedSnapshot(),
|
||||||
|
blankInventoryBlockedSlots(),
|
||||||
selfOfferSnapshot(),
|
selfOfferSnapshot(),
|
||||||
otherOfferSnapshot());
|
otherOfferSnapshot(),
|
||||||
|
blankChangedSlots(),
|
||||||
|
blankChangedSlots(),
|
||||||
|
showModifiedWarnings ? changedSlotSnapshot(selfChangedSlots) : blankChangedSlots(),
|
||||||
|
showModifiedWarnings ? changedSlotSnapshot(otherChangedSlots) : blankChangedSlots());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<ItemStack> parseOfferSpec(String spec) {
|
public static List<ItemStack> parseOfferSpec(String spec) {
|
||||||
@@ -418,9 +435,26 @@ public class DebugTradeSession {
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void clearAccepts() {
|
private void clearAccepts(boolean markSelfChanged, boolean markOtherChanged, int changedSlot) {
|
||||||
selfAccepted = false;
|
selfAccepted = false;
|
||||||
otherAccepted = false;
|
otherAccepted = false;
|
||||||
|
if (changedSlot >= 0) {
|
||||||
|
if (markSelfChanged) {
|
||||||
|
selfChangedSlots.set(changedSlot, true);
|
||||||
|
}
|
||||||
|
if (markOtherChanged) {
|
||||||
|
otherChangedSlots.set(changedSlot, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasChangedSlots() {
|
||||||
|
for (int i = 0; i < OFFER_SLOT_COUNT; i++) {
|
||||||
|
if (selfChangedSlots.get(i) || otherChangedSlots.get(i)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<ItemStack> inventorySnapshotCopy() {
|
private List<ItemStack> inventorySnapshotCopy() {
|
||||||
@@ -459,6 +493,26 @@ public class DebugTradeSession {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static List<Boolean> blankInventoryBlockedSlots() {
|
||||||
|
List<Boolean> result = new ArrayList<>(INVENTORY_SLOT_COUNT);
|
||||||
|
for (int i = 0; i < INVENTORY_SLOT_COUNT; i++) {
|
||||||
|
result.add(false);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Boolean> blankChangedSlots() {
|
||||||
|
List<Boolean> result = new ArrayList<>(OFFER_SLOT_COUNT);
|
||||||
|
for (int i = 0; i < OFFER_SLOT_COUNT; i++) {
|
||||||
|
result.add(false);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Boolean> changedSlotSnapshot(List<Boolean> changedSlots) {
|
||||||
|
return new ArrayList<>(changedSlots);
|
||||||
|
}
|
||||||
|
|
||||||
private static List<ItemStack> inventorySnapshot(ServerPlayer player) {
|
private static List<ItemStack> inventorySnapshot(ServerPlayer player) {
|
||||||
List<ItemStack> result = new ArrayList<>(INVENTORY_SLOT_COUNT);
|
List<ItemStack> result = new ArrayList<>(INVENTORY_SLOT_COUNT);
|
||||||
Inventory inventory = player.getInventory();
|
Inventory inventory = player.getInventory();
|
||||||
|
|||||||
165
src/main/java/com/trunksbomb/trade/trade/TradeAdminData.java
Normal file
165
src/main/java/com/trunksbomb/trade/trade/TradeAdminData.java
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
package com.trunksbomb.trade.trade;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
import net.minecraft.core.HolderLookup;
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
import net.minecraft.nbt.ListTag;
|
||||||
|
import net.minecraft.nbt.StringTag;
|
||||||
|
import net.minecraft.nbt.Tag;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
import net.minecraft.world.level.saveddata.SavedData;
|
||||||
|
|
||||||
|
public class TradeAdminData extends SavedData {
|
||||||
|
private static final String DATA_NAME = "trade_admin";
|
||||||
|
|
||||||
|
private final Set<UUID> disabledPlayers = new HashSet<>();
|
||||||
|
private final Set<String> blacklistedItemIds = new HashSet<>();
|
||||||
|
private final Set<String> blacklistedItemTags = new HashSet<>();
|
||||||
|
private final Set<String> blacklistedMods = new HashSet<>();
|
||||||
|
|
||||||
|
public static TradeAdminData get(MinecraftServer server) {
|
||||||
|
return server.overworld().getDataStorage().computeIfAbsent(factory(), DATA_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Factory<TradeAdminData> factory() {
|
||||||
|
return new Factory<>(TradeAdminData::new, TradeAdminData::load);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TradeAdminData load(CompoundTag tag, HolderLookup.Provider registries) {
|
||||||
|
TradeAdminData data = new TradeAdminData();
|
||||||
|
readUuidSet(tag.getList("disabled_players", Tag.TAG_STRING), data.disabledPlayers);
|
||||||
|
readStringSet(tag.getList("blacklisted_item_ids", Tag.TAG_STRING), data.blacklistedItemIds);
|
||||||
|
readStringSet(tag.getList("blacklisted_item_tags", Tag.TAG_STRING), data.blacklistedItemTags);
|
||||||
|
readStringSet(tag.getList("blacklisted_mods", Tag.TAG_STRING), data.blacklistedMods);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPlayerDisabled(UUID playerId) {
|
||||||
|
return disabledPlayers.contains(playerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean addDisabledPlayer(UUID playerId) {
|
||||||
|
boolean changed = disabledPlayers.add(playerId);
|
||||||
|
if (changed) {
|
||||||
|
setDirty();
|
||||||
|
}
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean removeDisabledPlayer(UUID playerId) {
|
||||||
|
boolean changed = disabledPlayers.remove(playerId);
|
||||||
|
if (changed) {
|
||||||
|
setDirty();
|
||||||
|
}
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<UUID> disabledPlayers() {
|
||||||
|
return new ArrayList<>(disabledPlayers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean addBlacklistedItemId(String itemId) {
|
||||||
|
return addNormalized(blacklistedItemIds, itemId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean removeBlacklistedItemId(String itemId) {
|
||||||
|
return removeNormalized(blacklistedItemIds, itemId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> blacklistedItemIds() {
|
||||||
|
return sortedCopy(blacklistedItemIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean addBlacklistedItemTag(String tagId) {
|
||||||
|
return addNormalized(blacklistedItemTags, tagId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean removeBlacklistedItemTag(String tagId) {
|
||||||
|
return removeNormalized(blacklistedItemTags, tagId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> blacklistedItemTags() {
|
||||||
|
return sortedCopy(blacklistedItemTags);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean addBlacklistedMod(String modId) {
|
||||||
|
return addNormalized(blacklistedMods, modId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean removeBlacklistedMod(String modId) {
|
||||||
|
return removeNormalized(blacklistedMods, modId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> blacklistedMods() {
|
||||||
|
return sortedCopy(blacklistedMods);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompoundTag save(CompoundTag tag, HolderLookup.Provider registries) {
|
||||||
|
tag.put("disabled_players", writeUuidSet(disabledPlayers));
|
||||||
|
tag.put("blacklisted_item_ids", writeStringSet(blacklistedItemIds));
|
||||||
|
tag.put("blacklisted_item_tags", writeStringSet(blacklistedItemTags));
|
||||||
|
tag.put("blacklisted_mods", writeStringSet(blacklistedMods));
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean addNormalized(Set<String> values, String value) {
|
||||||
|
String normalized = value.trim().toLowerCase(java.util.Locale.ROOT);
|
||||||
|
boolean changed = !normalized.isEmpty() && values.add(normalized);
|
||||||
|
if (changed) {
|
||||||
|
setDirty();
|
||||||
|
}
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean removeNormalized(Set<String> values, String value) {
|
||||||
|
boolean changed = values.remove(value.trim().toLowerCase(java.util.Locale.ROOT));
|
||||||
|
if (changed) {
|
||||||
|
setDirty();
|
||||||
|
}
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void readUuidSet(ListTag list, Set<UUID> target) {
|
||||||
|
for (Tag value : list) {
|
||||||
|
try {
|
||||||
|
target.add(UUID.fromString(value.getAsString()));
|
||||||
|
} catch (IllegalArgumentException ignored) {
|
||||||
|
// Ignore invalid persisted values so bad data does not break loading.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void readStringSet(ListTag list, Set<String> target) {
|
||||||
|
for (Tag value : list) {
|
||||||
|
String normalized = value.getAsString().trim().toLowerCase(java.util.Locale.ROOT);
|
||||||
|
if (!normalized.isEmpty()) {
|
||||||
|
target.add(normalized);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ListTag writeUuidSet(Set<UUID> values) {
|
||||||
|
ListTag list = new ListTag();
|
||||||
|
for (UUID value : values.stream().sorted().toList()) {
|
||||||
|
list.add(StringTag.valueOf(value.toString()));
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ListTag writeStringSet(Set<String> values) {
|
||||||
|
ListTag list = new ListTag();
|
||||||
|
for (String value : values.stream().sorted().toList()) {
|
||||||
|
list.add(StringTag.valueOf(value));
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<String> sortedCopy(Set<String> values) {
|
||||||
|
return values.stream().sorted().toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,20 +2,28 @@ package com.trunksbomb.trade.trade;
|
|||||||
|
|
||||||
import com.trunksbomb.trade.mod.TradeAuditLog;
|
import com.trunksbomb.trade.mod.TradeAuditLog;
|
||||||
import com.trunksbomb.trade.mod.TradeConfig;
|
import com.trunksbomb.trade.mod.TradeConfig;
|
||||||
|
import com.trunksbomb.trade.network.TradeStatePayload;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.WeakHashMap;
|
import java.util.WeakHashMap;
|
||||||
|
import net.minecraft.core.Holder;
|
||||||
|
import net.minecraft.core.registries.BuiltInRegistries;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
import net.minecraft.network.chat.ClickEvent;
|
import net.minecraft.network.chat.ClickEvent;
|
||||||
import net.minecraft.network.chat.MutableComponent;
|
import net.minecraft.network.chat.MutableComponent;
|
||||||
import net.minecraft.network.chat.Style;
|
import net.minecraft.network.chat.Style;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.minecraft.tags.TagKey;
|
||||||
import net.minecraft.server.MinecraftServer;
|
import net.minecraft.server.MinecraftServer;
|
||||||
import net.minecraft.server.level.ServerPlayer;
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
import net.minecraft.world.item.ItemStack;
|
import net.minecraft.world.item.ItemStack;
|
||||||
import net.minecraft.world.phys.Vec3;
|
import net.minecraft.world.phys.Vec3;
|
||||||
|
import net.neoforged.neoforge.network.PacketDistributor;
|
||||||
|
|
||||||
public class TradeManager {
|
public class TradeManager {
|
||||||
private static final long ACCEPT_GRACE_TICKS = 5L * 20L;
|
private static final long ACCEPT_GRACE_TICKS = 5L * 20L;
|
||||||
@@ -33,6 +41,7 @@ public class TradeManager {
|
|||||||
private final Map<UUID, PendingAcceptance> pendingAcceptancesByRequester = new HashMap<>();
|
private final Map<UUID, PendingAcceptance> pendingAcceptancesByRequester = new HashMap<>();
|
||||||
private final Map<UUID, UUID> pendingAcceptanceByPlayer = new HashMap<>();
|
private final Map<UUID, UUID> pendingAcceptanceByPlayer = new HashMap<>();
|
||||||
private final Map<UUID, Long> lastDamageTickByPlayer = new HashMap<>();
|
private final Map<UUID, Long> lastDamageTickByPlayer = new HashMap<>();
|
||||||
|
private final Map<UUID, Long> lastRequestTickByPlayer = new HashMap<>();
|
||||||
|
|
||||||
public static TradeManager get(MinecraftServer server) {
|
public static TradeManager get(MinecraftServer server) {
|
||||||
return INSTANCES.computeIfAbsent(server, ignored -> new TradeManager());
|
return INSTANCES.computeIfAbsent(server, ignored -> new TradeManager());
|
||||||
@@ -43,11 +52,18 @@ public class TradeManager {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Component disabledFailure = tradeDisabledFailure(first, second);
|
||||||
|
if (disabledFailure != null) {
|
||||||
|
first.sendSystemMessage(disabledFailure);
|
||||||
|
second.sendSystemMessage(disabledFailure);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
TradeSession session = new TradeSession(first, second);
|
TradeSession session = new TradeSession(first, second);
|
||||||
sessionsById.put(session.id(), session);
|
sessionsById.put(session.id(), session);
|
||||||
sessionByPlayer.put(first.getUUID(), session.id());
|
sessionByPlayer.put(first.getUUID(), session.id());
|
||||||
sessionByPlayer.put(second.getUUID(), session.id());
|
sessionByPlayer.put(second.getUUID(), session.id());
|
||||||
session.syncToPlayers();
|
syncSession(session);
|
||||||
TradeAuditLog.log(first.server, "OPEN " + playerAudit(first) + " <-> " + playerAudit(second));
|
TradeAuditLog.log(first.server, "OPEN " + playerAudit(first) + " <-> " + playerAudit(second));
|
||||||
first.sendSystemMessage(Component.literal("Trade opened with " + second.getGameProfile().getName() + "."));
|
first.sendSystemMessage(Component.literal("Trade opened with " + second.getGameProfile().getName() + "."));
|
||||||
second.sendSystemMessage(Component.literal("Trade opened with " + first.getGameProfile().getName() + "."));
|
second.sendSystemMessage(Component.literal("Trade opened with " + first.getGameProfile().getName() + "."));
|
||||||
@@ -65,6 +81,12 @@ public class TradeManager {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Component disabledFailure = tradeDisabledFailure(requester, target);
|
||||||
|
if (disabledFailure != null) {
|
||||||
|
requester.sendSystemMessage(disabledFailure);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!preferences(requester.server).isTradeEnabled(target.getUUID())) {
|
if (!preferences(requester.server).isTradeEnabled(target.getUUID())) {
|
||||||
requester.sendSystemMessage(Component.literal(target.getGameProfile().getName() + " is not accepting trade requests."));
|
requester.sendSystemMessage(Component.literal(target.getGameProfile().getName() + " is not accepting trade requests."));
|
||||||
return false;
|
return false;
|
||||||
@@ -99,7 +121,14 @@ public class TradeManager {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
long cooldownRemainingTicks = remainingRequestCooldownTicks(requester);
|
||||||
|
if (cooldownRemainingTicks > 0L) {
|
||||||
|
requester.sendSystemMessage(Component.literal("You must wait " + formatSecondsTenths(cooldownRemainingTicks) + "s before sending another trade request."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
pendingRequestsByTarget.put(target.getUUID(), new TradeRequest(requester.getUUID(), target.getUUID(), target.server.getTickCount()));
|
pendingRequestsByTarget.put(target.getUUID(), new TradeRequest(requester.getUUID(), target.getUUID(), target.server.getTickCount()));
|
||||||
|
lastRequestTickByPlayer.put(requester.getUUID(), (long) target.server.getTickCount());
|
||||||
TradeAuditLog.log(requester.server, "REQUEST " + playerAudit(requester) + " -> " + playerAudit(target));
|
TradeAuditLog.log(requester.server, "REQUEST " + playerAudit(requester) + " -> " + playerAudit(target));
|
||||||
requester.sendSystemMessage(Component.literal("Trade request sent to " + target.getGameProfile().getName() + "."));
|
requester.sendSystemMessage(Component.literal("Trade request sent to " + target.getGameProfile().getName() + "."));
|
||||||
target.sendSystemMessage(Component.literal(requester.getGameProfile().getName() + " would like to trade with you: ")
|
target.sendSystemMessage(Component.literal(requester.getGameProfile().getName() + " would like to trade with you: ")
|
||||||
@@ -125,6 +154,13 @@ public class TradeManager {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Component disabledFailure = tradeDisabledFailure(requester, target);
|
||||||
|
if (disabledFailure != null) {
|
||||||
|
requester.sendSystemMessage(disabledFailure);
|
||||||
|
target.sendSystemMessage(disabledFailure);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!preferences(target.server).isTradeEnabled(target.getUUID())) {
|
if (!preferences(target.server).isTradeEnabled(target.getUUID())) {
|
||||||
target.sendSystemMessage(Component.literal("You are not accepting trade requests right now."));
|
target.sendSystemMessage(Component.literal("You are not accepting trade requests right now."));
|
||||||
requester.sendSystemMessage(Component.literal(target.getGameProfile().getName() + " is not accepting trade requests."));
|
requester.sendSystemMessage(Component.literal(target.getGameProfile().getName() + " is not accepting trade requests."));
|
||||||
@@ -216,6 +252,15 @@ public class TradeManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void handleRealAction(ServerPlayer player, TradeSession session, TradeAction action, int slot, int amount) {
|
private void handleRealAction(ServerPlayer player, TradeSession session, TradeAction action, int slot, int amount) {
|
||||||
|
if (action == TradeAction.ADD_ITEM) {
|
||||||
|
ItemStack stack = session.offeredInventoryStack(player, slot);
|
||||||
|
ItemBlockResult blockedItem = blockedItem(player.server, stack);
|
||||||
|
if (blockedItem != null) {
|
||||||
|
player.sendSystemMessage(Component.literal("That item cannot be traded: " + blockedItem.reason() + "."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
boolean changed = switch (action) {
|
boolean changed = switch (action) {
|
||||||
case ADD_ITEM -> session.addFromInventory(player, slot, amount);
|
case ADD_ITEM -> session.addFromInventory(player, slot, amount);
|
||||||
case REMOVE_ITEM -> session.removeFromOffer(player, slot, amount);
|
case REMOVE_ITEM -> session.removeFromOffer(player, slot, amount);
|
||||||
@@ -233,14 +278,28 @@ public class TradeManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!session.isConfirmationStage()) {
|
if (!session.bothAccepted()) {
|
||||||
session.advanceToConfirmation();
|
syncSession(session);
|
||||||
session.syncToPlayers();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!session.bothAccepted()) {
|
if (!session.isConfirmationStage()) {
|
||||||
session.syncToPlayers();
|
if (TradeConfig.requireSecondConfirmation()) {
|
||||||
|
session.advanceToConfirmation();
|
||||||
|
syncSession(session);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemBlockResult selfBlocked = blockedOffer(player.server, session.firstOfferSnapshot());
|
||||||
|
if (selfBlocked != null) {
|
||||||
|
cancel(session, Component.literal("Trade cancelled because a blocked item was offered: " + selfBlocked.reason() + "."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemBlockResult otherBlocked = blockedOffer(player.server, session.secondOfferSnapshot());
|
||||||
|
if (otherBlocked != null) {
|
||||||
|
cancel(session, Component.literal("Trade cancelled because a blocked item was offered: " + otherBlocked.reason() + "."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,6 +312,13 @@ public class TradeManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void handleDebugAction(DebugTradeSession session, TradeAction action, int slot, int amount) {
|
private void handleDebugAction(DebugTradeSession session, TradeAction action, int slot, int amount) {
|
||||||
|
if (action == TradeAction.ADD_ITEM) {
|
||||||
|
ItemBlockResult blockedItem = blockedItem(session.player().server, session.player().getInventory().getItem(slot));
|
||||||
|
if (blockedItem != null) {
|
||||||
|
session.player().sendSystemMessage(Component.literal("That item cannot be traded: " + blockedItem.reason() + "."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (action == TradeAction.ACCEPT) {
|
if (action == TradeAction.ACCEPT) {
|
||||||
List<Component> unsafe = tradeSafetyFailures(session, session.player(), session.player().server.getTickCount());
|
List<Component> unsafe = tradeSafetyFailures(session, session.player(), session.player().server.getTickCount());
|
||||||
if (!unsafe.isEmpty()) {
|
if (!unsafe.isEmpty()) {
|
||||||
@@ -278,14 +344,28 @@ public class TradeManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!session.isConfirmationStage()) {
|
if (!session.bothAccepted()) {
|
||||||
session.advanceToConfirmation();
|
syncDebugSession(session);
|
||||||
session.sync();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!session.bothAccepted()) {
|
if (!session.isConfirmationStage()) {
|
||||||
session.sync();
|
if (TradeConfig.requireSecondConfirmation()) {
|
||||||
|
session.advanceToConfirmation();
|
||||||
|
syncDebugSession(session);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemBlockResult selfBlocked = blockedOffer(session.player().server, session.selfOfferSnapshot());
|
||||||
|
if (selfBlocked != null) {
|
||||||
|
closeDebug(session, Component.literal("Debug trade cancelled because your offer contains a blocked item: " + selfBlocked.reason() + "."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemBlockResult otherBlocked = blockedOffer(session.player().server, session.otherOfferSnapshot());
|
||||||
|
if (otherBlocked != null) {
|
||||||
|
closeDebug(session, Component.literal("Debug trade cancelled because the debug offer contains a blocked item: " + otherBlocked.reason() + "."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,6 +380,7 @@ public class TradeManager {
|
|||||||
clearPendingRequests(player);
|
clearPendingRequests(player);
|
||||||
clearDebugRequests(player);
|
clearDebugRequests(player);
|
||||||
clearPendingAcceptance(player, null, null);
|
clearPendingAcceptance(player, null, null);
|
||||||
|
lastRequestTickByPlayer.remove(player.getUUID());
|
||||||
|
|
||||||
TradeSession session = getSession(player);
|
TradeSession session = getSession(player);
|
||||||
if (session != null) {
|
if (session != null) {
|
||||||
@@ -363,10 +444,76 @@ public class TradeManager {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean addAdminDisabledPlayer(ServerPlayer player) {
|
||||||
|
return adminData(player.server).addDisabledPlayer(player.getUUID());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean addAdminDisabledPlayer(UUID playerId, MinecraftServer server) {
|
||||||
|
return adminData(server).addDisabledPlayer(playerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean removeAdminDisabledPlayer(ServerPlayer player) {
|
||||||
|
return adminData(player.server).removeDisabledPlayer(player.getUUID());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean removeAdminDisabledPlayer(UUID playerId, MinecraftServer server) {
|
||||||
|
return adminData(server).removeDisabledPlayer(playerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> adminDisabledPlayerNames(MinecraftServer server) {
|
||||||
|
List<String> result = new ArrayList<>();
|
||||||
|
for (UUID playerId : adminData(server).disabledPlayers()) {
|
||||||
|
result.add(nameFor(playerId, server));
|
||||||
|
}
|
||||||
|
result.sort(String::compareToIgnoreCase);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean addAdminBlacklistedItemId(MinecraftServer server, String itemId) {
|
||||||
|
return adminData(server).addBlacklistedItemId(itemId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean removeAdminBlacklistedItemId(MinecraftServer server, String itemId) {
|
||||||
|
return adminData(server).removeBlacklistedItemId(itemId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> adminBlacklistedItemIds(MinecraftServer server) {
|
||||||
|
return adminData(server).blacklistedItemIds();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean addAdminBlacklistedItemTag(MinecraftServer server, String tagId) {
|
||||||
|
return adminData(server).addBlacklistedItemTag(tagId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean removeAdminBlacklistedItemTag(MinecraftServer server, String tagId) {
|
||||||
|
return adminData(server).removeBlacklistedItemTag(tagId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> adminBlacklistedItemTags(MinecraftServer server) {
|
||||||
|
return adminData(server).blacklistedItemTags();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean addAdminBlacklistedMod(MinecraftServer server, String modId) {
|
||||||
|
return adminData(server).addBlacklistedMod(modId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean removeAdminBlacklistedMod(MinecraftServer server, String modId) {
|
||||||
|
return adminData(server).removeBlacklistedMod(modId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> adminBlacklistedMods(MinecraftServer server) {
|
||||||
|
return adminData(server).blacklistedMods();
|
||||||
|
}
|
||||||
|
|
||||||
public boolean initDebugTrade(ServerPlayer player) {
|
public boolean initDebugTrade(ServerPlayer player) {
|
||||||
if (!TradeConfig.enableDebugFeatures()) {
|
if (!TradeConfig.enableDebugFeatures()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
Component disabledFailure = tradeDisabledFailure(player);
|
||||||
|
if (disabledFailure != null) {
|
||||||
|
player.sendSystemMessage(disabledFailure);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (isBusy(player)) {
|
if (isBusy(player)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -374,7 +521,7 @@ public class TradeManager {
|
|||||||
DebugTradeSession session = new DebugTradeSession(player);
|
DebugTradeSession session = new DebugTradeSession(player);
|
||||||
debugSessionsById.put(session.id(), session);
|
debugSessionsById.put(session.id(), session);
|
||||||
debugSessionByPlayer.put(player.getUUID(), session.id());
|
debugSessionByPlayer.put(player.getUUID(), session.id());
|
||||||
session.sync();
|
syncDebugSession(session);
|
||||||
player.sendSystemMessage(Component.literal("Debug trade opened."));
|
player.sendSystemMessage(Component.literal("Debug trade opened."));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -390,6 +537,11 @@ public class TradeManager {
|
|||||||
if (!TradeConfig.enableDebugFeatures()) {
|
if (!TradeConfig.enableDebugFeatures()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
Component disabledFailure = tradeDisabledFailure(player);
|
||||||
|
if (disabledFailure != null) {
|
||||||
|
player.sendSystemMessage(disabledFailure);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (isBusy(player) || hasPendingDebugRequest(player)) {
|
if (isBusy(player) || hasPendingDebugRequest(player)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -404,12 +556,18 @@ public class TradeManager {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
DebugTradeSession session = getDebugSession(player);
|
DebugTradeSession session = getDebugSession(player);
|
||||||
if (session == null) {
|
if (session == null || session.isConfirmationStage()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemBlockResult blockedItem = blockedOffer(player.server, offer);
|
||||||
|
if (blockedItem != null) {
|
||||||
|
player.sendSystemMessage(Component.literal("That debug offer contains a blocked item: " + blockedItem.reason() + "."));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
session.setOtherOffer(offer);
|
session.setOtherOffer(offer);
|
||||||
session.sync();
|
syncDebugSession(session);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -421,10 +579,13 @@ public class TradeManager {
|
|||||||
if (session == null) {
|
if (session == null) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
if (session.isConfirmationStage()) {
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
|
||||||
boolean changed = session.removeOtherOffer(spec);
|
boolean changed = session.removeOtherOffer(spec);
|
||||||
if (changed) {
|
if (changed) {
|
||||||
session.sync();
|
syncDebugSession(session);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -447,20 +608,25 @@ public class TradeManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
session.acceptOther();
|
session.acceptOther();
|
||||||
if (!session.isConfirmationStage()) {
|
if (!session.bothAccepted()) {
|
||||||
session.advanceToConfirmation();
|
syncDebugSession(session);
|
||||||
session.sync();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!session.isConfirmationStage()) {
|
||||||
|
if (TradeConfig.requireSecondConfirmation()) {
|
||||||
|
session.advanceToConfirmation();
|
||||||
|
syncDebugSession(session);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (session.bothAccepted()) {
|
if (session.bothAccepted()) {
|
||||||
if (session.completeTrade()) {
|
if (session.completeTrade()) {
|
||||||
finishDebug(session, Component.literal("Debug trade completed."));
|
finishDebug(session, Component.literal("Debug trade completed."));
|
||||||
} else {
|
} else {
|
||||||
closeDebug(session, Component.literal("Debug trade cancelled because the items would not fit."));
|
closeDebug(session, Component.literal("Debug trade cancelled because the items would not fit."));
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
session.sync();
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -475,7 +641,7 @@ public class TradeManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
session.setUnsafeState(state, true);
|
session.setUnsafeState(state, true);
|
||||||
session.sync();
|
syncDebugSession(session);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -489,7 +655,7 @@ public class TradeManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
session.clearUnsafeStates();
|
session.clearUnsafeStates();
|
||||||
session.sync();
|
syncDebugSession(session);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -531,32 +697,48 @@ public class TradeManager {
|
|||||||
try {
|
try {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case SET_OFFER -> {
|
case SET_OFFER -> {
|
||||||
|
if (session.isConfirmationStage()) {
|
||||||
|
player.sendSystemMessage(Component.literal("Trade offers cannot be changed during confirmation."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
session.setOtherOffer(DebugTradeSession.parseOfferSpec(spec));
|
session.setOtherOffer(DebugTradeSession.parseOfferSpec(spec));
|
||||||
session.sync();
|
syncDebugSession(session);
|
||||||
}
|
}
|
||||||
case APPEND_RANDOM -> {
|
case APPEND_RANDOM -> {
|
||||||
|
if (session.isConfirmationStage()) {
|
||||||
|
player.sendSystemMessage(Component.literal("Trade offers cannot be changed during confirmation."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (session.appendOtherOffer(DebugTradeSession.randomSingleStackOffer())) {
|
if (session.appendOtherOffer(DebugTradeSession.randomSingleStackOffer())) {
|
||||||
session.sync();
|
syncDebugSession(session);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case REMOVE_OFFER -> {
|
case REMOVE_OFFER -> {
|
||||||
|
if (session.isConfirmationStage()) {
|
||||||
|
player.sendSystemMessage(Component.literal("Trade offers cannot be changed during confirmation."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (session.removeOtherOffer(spec)) {
|
if (session.removeOtherOffer(spec)) {
|
||||||
session.sync();
|
syncDebugSession(session);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case REMOVE_LAST -> {
|
case REMOVE_LAST -> {
|
||||||
|
if (session.isConfirmationStage()) {
|
||||||
|
player.sendSystemMessage(Component.literal("Trade offers cannot be changed during confirmation."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (session.removeLastOtherOffer()) {
|
if (session.removeLastOtherOffer()) {
|
||||||
session.sync();
|
syncDebugSession(session);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case SET_UNSAFE -> {
|
case SET_UNSAFE -> {
|
||||||
DebugUnsafeState state = parseDebugUnsafeState(spec);
|
DebugUnsafeState state = parseDebugUnsafeState(spec);
|
||||||
session.setUnsafeState(state, true);
|
session.setUnsafeState(state, true);
|
||||||
session.sync();
|
syncDebugSession(session);
|
||||||
}
|
}
|
||||||
case CLEAR_UNSAFE -> {
|
case CLEAR_UNSAFE -> {
|
||||||
session.clearUnsafeStates();
|
session.clearUnsafeStates();
|
||||||
session.sync();
|
syncDebugSession(session);
|
||||||
}
|
}
|
||||||
case ACCEPT -> acceptDebug(player);
|
case ACCEPT -> acceptDebug(player);
|
||||||
case CANCEL -> cancelDebug(player);
|
case CANCEL -> cancelDebug(player);
|
||||||
@@ -742,6 +924,12 @@ public class TradeManager {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Component disabledFailure = tradeDisabledFailure(first, second);
|
||||||
|
if (disabledFailure != null) {
|
||||||
|
cancel(session, disabledFailure);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
List<Component> firstFailures = tradeSafetyFailures(first, server.getTickCount());
|
List<Component> firstFailures = tradeSafetyFailures(first, server.getTickCount());
|
||||||
if (!firstFailures.isEmpty()) {
|
if (!firstFailures.isEmpty()) {
|
||||||
cancel(session, Component.literal("Trade cancelled because " + first.getGameProfile().getName() + " is no longer in a safe state to trade: ").append(joinReasons(firstFailures)));
|
cancel(session, Component.literal("Trade cancelled because " + first.getGameProfile().getName() + " is no longer in a safe state to trade: ").append(joinReasons(firstFailures)));
|
||||||
@@ -751,6 +939,18 @@ public class TradeManager {
|
|||||||
List<Component> secondFailures = tradeSafetyFailures(second, server.getTickCount());
|
List<Component> secondFailures = tradeSafetyFailures(second, server.getTickCount());
|
||||||
if (!secondFailures.isEmpty()) {
|
if (!secondFailures.isEmpty()) {
|
||||||
cancel(session, Component.literal("Trade cancelled because " + second.getGameProfile().getName() + " is no longer in a safe state to trade: ").append(joinReasons(secondFailures)));
|
cancel(session, Component.literal("Trade cancelled because " + second.getGameProfile().getName() + " is no longer in a safe state to trade: ").append(joinReasons(secondFailures)));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemBlockResult firstBlocked = blockedOffer(server, session.firstOfferSnapshot());
|
||||||
|
if (firstBlocked != null) {
|
||||||
|
cancel(session, Component.literal("Trade cancelled because " + first.getGameProfile().getName() + " offered a blocked item: " + firstBlocked.reason() + "."));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemBlockResult secondBlocked = blockedOffer(server, session.secondOfferSnapshot());
|
||||||
|
if (secondBlocked != null) {
|
||||||
|
cancel(session, Component.literal("Trade cancelled because " + second.getGameProfile().getName() + " offered a blocked item: " + secondBlocked.reason() + "."));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -762,9 +962,27 @@ public class TradeManager {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Component disabledFailure = tradeDisabledFailure(player);
|
||||||
|
if (disabledFailure != null) {
|
||||||
|
closeDebug(session, disabledFailure);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
List<Component> failures = tradeSafetyFailures(session, player, server.getTickCount());
|
List<Component> failures = tradeSafetyFailures(session, player, server.getTickCount());
|
||||||
if (!failures.isEmpty()) {
|
if (!failures.isEmpty()) {
|
||||||
closeDebug(session, Component.literal("Debug trade cancelled because you are no longer in a safe state to trade: ").append(joinReasons(failures)));
|
closeDebug(session, Component.literal("Debug trade cancelled because you are no longer in a safe state to trade: ").append(joinReasons(failures)));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemBlockResult selfBlocked = blockedOffer(server, session.selfOfferSnapshot());
|
||||||
|
if (selfBlocked != null) {
|
||||||
|
closeDebug(session, Component.literal("Debug trade cancelled because your offer contains a blocked item: " + selfBlocked.reason() + "."));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemBlockResult otherBlocked = blockedOffer(server, session.otherOfferSnapshot());
|
||||||
|
if (otherBlocked != null) {
|
||||||
|
closeDebug(session, Component.literal("Debug trade cancelled because the debug offer contains a blocked item: " + otherBlocked.reason() + "."));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -784,6 +1002,15 @@ public class TradeManager {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Component disabledFailure = target == null ? tradeDisabledFailure(requester) : tradeDisabledFailure(requester, target);
|
||||||
|
if (disabledFailure != null) {
|
||||||
|
clearPendingAcceptance(
|
||||||
|
requester,
|
||||||
|
disabledFailure,
|
||||||
|
target == null ? null : disabledFailure);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
Component rangeFailure = target == null ? null : tradeRangeFailure(requester, target);
|
Component rangeFailure = target == null ? null : tradeRangeFailure(requester, target);
|
||||||
if (rangeFailure != null) {
|
if (rangeFailure != null) {
|
||||||
clearPendingAcceptance(
|
clearPendingAcceptance(
|
||||||
@@ -1036,6 +1263,11 @@ public class TradeManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean startOrDelayDebugTrade(ServerPlayer player) {
|
private boolean startOrDelayDebugTrade(ServerPlayer player) {
|
||||||
|
Component disabledFailure = tradeDisabledFailure(player);
|
||||||
|
if (disabledFailure != null) {
|
||||||
|
player.sendSystemMessage(disabledFailure);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
long currentTick = player.server.getTickCount();
|
long currentTick = player.server.getTickCount();
|
||||||
List<Component> unsafe = tradeSafetyFailures(player, currentTick);
|
List<Component> unsafe = tradeSafetyFailures(player, currentTick);
|
||||||
if (!unsafe.isEmpty()) {
|
if (!unsafe.isEmpty()) {
|
||||||
@@ -1111,6 +1343,165 @@ public class TradeManager {
|
|||||||
return trader + "=" + (entries.isEmpty() ? "(nothing)" : String.join("; ", entries));
|
return trader + "=" + (entries.isEmpty() ? "(nothing)" : String.join("; ", entries));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private long remainingRequestCooldownTicks(ServerPlayer player) {
|
||||||
|
int cooldownSeconds = TradeConfig.requestCooldownSeconds();
|
||||||
|
if (cooldownSeconds <= 0) {
|
||||||
|
return 0L;
|
||||||
|
}
|
||||||
|
long lastTick = lastRequestTickByPlayer.getOrDefault(player.getUUID(), Long.MIN_VALUE / 4);
|
||||||
|
long elapsed = player.server.getTickCount() - lastTick;
|
||||||
|
return Math.max(0L, cooldownSeconds * 20L - elapsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Component tradeDisabledFailure(ServerPlayer player) {
|
||||||
|
if (isAdminTradeDisabled(player)) {
|
||||||
|
return Component.literal("You cannot use the trade system on this server.");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Component tradeDisabledFailure(ServerPlayer first, ServerPlayer second) {
|
||||||
|
if (isAdminTradeDisabled(first)) {
|
||||||
|
return Component.literal(first.getGameProfile().getName() + " cannot use the trade system on this server.");
|
||||||
|
}
|
||||||
|
if (isAdminTradeDisabled(second)) {
|
||||||
|
return Component.literal(second.getGameProfile().getName() + " cannot use the trade system on this server.");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isAdminTradeDisabled(ServerPlayer player) {
|
||||||
|
if (adminData(player.server).isPlayerDisabled(player.getUUID())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
for (String configured : TradeConfig.adminDisabledPlayerUuids()) {
|
||||||
|
try {
|
||||||
|
if (UUID.fromString(configured).equals(player.getUUID())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (IllegalArgumentException ignored) {
|
||||||
|
// Ignore invalid config entries instead of failing live servers.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ItemBlockResult blockedOffer(MinecraftServer server, List<ItemStack> offer) {
|
||||||
|
for (ItemStack stack : offer) {
|
||||||
|
ItemBlockResult result = blockedItem(server, stack);
|
||||||
|
if (result != null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ItemBlockResult blockedItem(MinecraftServer server, ItemStack stack) {
|
||||||
|
if (stack == null || stack.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourceLocation itemId = BuiltInRegistries.ITEM.getKey(stack.getItem());
|
||||||
|
if (itemId == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String itemKey = itemId.toString();
|
||||||
|
if (effectiveBlacklistedItemIds(server).contains(itemKey)) {
|
||||||
|
return new ItemBlockResult(itemKey, "item " + itemKey + " is blacklisted");
|
||||||
|
}
|
||||||
|
|
||||||
|
String namespace = itemId.getNamespace().toLowerCase(java.util.Locale.ROOT);
|
||||||
|
if (effectiveBlacklistedMods(server).contains(namespace)) {
|
||||||
|
return new ItemBlockResult(itemKey, "mod " + namespace + " is blacklisted");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String tagId : effectiveBlacklistedItemTags(server)) {
|
||||||
|
ResourceLocation parsed = ResourceLocation.tryParse(tagId);
|
||||||
|
if (parsed == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
TagKey<net.minecraft.world.item.Item> tagKey = TagKey.create(net.minecraft.core.registries.Registries.ITEM, parsed);
|
||||||
|
Holder<net.minecraft.world.item.Item> holder = stack.getItemHolder();
|
||||||
|
if (holder.is(tagKey)) {
|
||||||
|
return new ItemBlockResult(itemKey, "tag #" + tagId + " is blacklisted");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<String> effectiveBlacklistedItemIds(MinecraftServer server) {
|
||||||
|
Set<String> values = new HashSet<>(adminData(server).blacklistedItemIds());
|
||||||
|
values.addAll(normalizedConfigEntries(TradeConfig.adminBlacklistedItemIds()));
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<String> effectiveBlacklistedItemTags(MinecraftServer server) {
|
||||||
|
Set<String> values = new HashSet<>(adminData(server).blacklistedItemTags());
|
||||||
|
values.addAll(normalizedConfigEntries(TradeConfig.adminBlacklistedItemTags()));
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<String> effectiveBlacklistedMods(MinecraftServer server) {
|
||||||
|
Set<String> values = new HashSet<>(adminData(server).blacklistedMods());
|
||||||
|
values.addAll(normalizedConfigEntries(TradeConfig.adminBlacklistedMods()));
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<String> normalizedConfigEntries(List<String> values) {
|
||||||
|
Set<String> result = new HashSet<>();
|
||||||
|
for (String value : values) {
|
||||||
|
String normalized = value.trim().toLowerCase(java.util.Locale.ROOT);
|
||||||
|
if (!normalized.isEmpty()) {
|
||||||
|
result.add(normalized);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void syncSession(TradeSession session) {
|
||||||
|
sendTradeView(session.firstPlayer(), decorateView(session.firstPlayer().server, session.viewFor(session.firstPlayer())));
|
||||||
|
sendTradeView(session.secondPlayer(), decorateView(session.secondPlayer().server, session.viewFor(session.secondPlayer())));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void syncDebugSession(DebugTradeSession session) {
|
||||||
|
sendTradeView(session.player(), decorateView(session.player().server, session.view()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendTradeView(ServerPlayer player, TradeView view) {
|
||||||
|
PacketDistributor.sendToPlayer(player, new TradeStatePayload(view));
|
||||||
|
}
|
||||||
|
|
||||||
|
private TradeView decorateView(MinecraftServer server, TradeView view) {
|
||||||
|
return new TradeView(
|
||||||
|
view.sessionId(),
|
||||||
|
view.selfName(),
|
||||||
|
view.otherName(),
|
||||||
|
view.debugMode(),
|
||||||
|
view.stage(),
|
||||||
|
view.selfAccepted(),
|
||||||
|
view.otherAccepted(),
|
||||||
|
view.itemsChanged(),
|
||||||
|
view.inventory(),
|
||||||
|
view.reservedCounts(),
|
||||||
|
blockedSlots(server, view.inventory()),
|
||||||
|
view.selfOffer(),
|
||||||
|
view.otherOffer(),
|
||||||
|
blockedSlots(server, view.selfOffer()),
|
||||||
|
blockedSlots(server, view.otherOffer()),
|
||||||
|
view.selfChangedSlots(),
|
||||||
|
view.otherChangedSlots());
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Boolean> blockedSlots(MinecraftServer server, List<ItemStack> stacks) {
|
||||||
|
List<Boolean> result = new ArrayList<>(stacks.size());
|
||||||
|
for (ItemStack stack : stacks) {
|
||||||
|
result.add(blockedItem(server, stack) != null);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
private DebugUnsafeState parseDebugUnsafeState(String spec) {
|
private DebugUnsafeState parseDebugUnsafeState(String spec) {
|
||||||
try {
|
try {
|
||||||
return DebugUnsafeState.valueOf(spec.trim().toUpperCase(java.util.Locale.ROOT));
|
return DebugUnsafeState.valueOf(spec.trim().toUpperCase(java.util.Locale.ROOT));
|
||||||
@@ -1130,12 +1521,18 @@ public class TradeManager {
|
|||||||
return TradePreferencesData.get(server);
|
return TradePreferencesData.get(server);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private TradeAdminData adminData(MinecraftServer server) {
|
||||||
|
return TradeAdminData.get(server);
|
||||||
|
}
|
||||||
|
|
||||||
private record TradeRequest(UUID requester, UUID target, long createdTick) {}
|
private record TradeRequest(UUID requester, UUID target, long createdTick) {}
|
||||||
|
|
||||||
private record DebugTradeRequest(UUID target, long createdTick) {}
|
private record DebugTradeRequest(UUID target, long createdTick) {}
|
||||||
|
|
||||||
private record ScheduledDebugRequest(UUID target, long triggerTick, boolean autoAccept) {}
|
private record ScheduledDebugRequest(UUID target, long triggerTick, boolean autoAccept) {}
|
||||||
|
|
||||||
|
private record ItemBlockResult(String itemId, String reason) {}
|
||||||
|
|
||||||
private static final class PendingAcceptance {
|
private static final class PendingAcceptance {
|
||||||
private final UUID requester;
|
private final UUID requester;
|
||||||
private final UUID target;
|
private final UUID target;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.trunksbomb.trade.trade;
|
package com.trunksbomb.trade.trade;
|
||||||
|
|
||||||
|
import com.trunksbomb.trade.mod.TradeConfig;
|
||||||
import com.trunksbomb.trade.network.TradeClosePayload;
|
import com.trunksbomb.trade.network.TradeClosePayload;
|
||||||
import com.trunksbomb.trade.network.TradeStatePayload;
|
import com.trunksbomb.trade.network.TradeStatePayload;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -10,7 +11,6 @@ import net.minecraft.server.level.ServerPlayer;
|
|||||||
import net.minecraft.world.entity.player.Inventory;
|
import net.minecraft.world.entity.player.Inventory;
|
||||||
import net.minecraft.world.item.ItemStack;
|
import net.minecraft.world.item.ItemStack;
|
||||||
import net.neoforged.neoforge.network.PacketDistributor;
|
import net.neoforged.neoforge.network.PacketDistributor;
|
||||||
import com.trunksbomb.trade.mod.TradeConfig;
|
|
||||||
|
|
||||||
public class TradeSession {
|
public class TradeSession {
|
||||||
private static final int INVENTORY_SLOT_COUNT = TradeView.INVENTORY_SLOT_COUNT;
|
private static final int INVENTORY_SLOT_COUNT = TradeView.INVENTORY_SLOT_COUNT;
|
||||||
@@ -23,6 +23,8 @@ public class TradeSession {
|
|||||||
private final List<ItemStack> secondInventory;
|
private final List<ItemStack> secondInventory;
|
||||||
private final List<TradeEntry> firstOffer = blankOffer();
|
private final List<TradeEntry> firstOffer = blankOffer();
|
||||||
private final List<TradeEntry> secondOffer = blankOffer();
|
private final List<TradeEntry> secondOffer = blankOffer();
|
||||||
|
private final List<Boolean> firstChangedSlots = blankChangedSlots();
|
||||||
|
private final List<Boolean> secondChangedSlots = blankChangedSlots();
|
||||||
private boolean firstAccepted;
|
private boolean firstAccepted;
|
||||||
private boolean secondAccepted;
|
private boolean secondAccepted;
|
||||||
private TradeStage stage = TradeStage.OFFERING;
|
private TradeStage stage = TradeStage.OFFERING;
|
||||||
@@ -72,6 +74,13 @@ public class TradeSession {
|
|||||||
PacketDistributor.sendToPlayer(second, new TradeClosePayload(id, reason));
|
PacketDistributor.sendToPlayer(second, new TradeClosePayload(id, reason));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ItemStack offeredInventoryStack(ServerPlayer player, int inventorySlot) {
|
||||||
|
if (inventorySlot < 0 || inventorySlot >= INVENTORY_SLOT_COUNT) {
|
||||||
|
return ItemStack.EMPTY;
|
||||||
|
}
|
||||||
|
return inventoryFor(player).get(inventorySlot);
|
||||||
|
}
|
||||||
|
|
||||||
public boolean addFromInventory(ServerPlayer player, int inventorySlot, int amount) {
|
public boolean addFromInventory(ServerPlayer player, int inventorySlot, int amount) {
|
||||||
if (stage != TradeStage.OFFERING || inventorySlot < 0 || inventorySlot >= INVENTORY_SLOT_COUNT) {
|
if (stage != TradeStage.OFFERING || inventorySlot < 0 || inventorySlot >= INVENTORY_SLOT_COUNT) {
|
||||||
return false;
|
return false;
|
||||||
@@ -95,7 +104,7 @@ public class TradeSession {
|
|||||||
ItemStack merged = entry.stack().copy();
|
ItemStack merged = entry.stack().copy();
|
||||||
merged.grow(moveAmount);
|
merged.grow(moveAmount);
|
||||||
offer.set(i, new TradeEntry(inventorySlot, merged));
|
offer.set(i, new TradeEntry(inventorySlot, merged));
|
||||||
clearAccepts();
|
clearAccepts(false, false, -1);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -106,7 +115,7 @@ public class TradeSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
offer.set(freeSlot, new TradeEntry(inventorySlot, sourceStack.copyWithCount(moveAmount)));
|
offer.set(freeSlot, new TradeEntry(inventorySlot, sourceStack.copyWithCount(moveAmount)));
|
||||||
clearAccepts();
|
clearAccepts(false, false, -1);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,7 +139,7 @@ public class TradeSession {
|
|||||||
offer.set(offerSlot, null);
|
offer.set(offerSlot, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
clearAccepts();
|
clearAccepts(player == first, player == second, offerSlot);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,6 +202,7 @@ public class TradeSession {
|
|||||||
public TradeView viewFor(ServerPlayer player) {
|
public TradeView viewFor(ServerPlayer player) {
|
||||||
boolean isFirst = player == first;
|
boolean isFirst = player == first;
|
||||||
ServerPlayer other = isFirst ? second : first;
|
ServerPlayer other = isFirst ? second : first;
|
||||||
|
boolean showModifiedWarnings = TradeConfig.showTradeModifiedWarnings();
|
||||||
return new TradeView(
|
return new TradeView(
|
||||||
id,
|
id,
|
||||||
player.getGameProfile().getName(),
|
player.getGameProfile().getName(),
|
||||||
@@ -201,10 +211,16 @@ public class TradeSession {
|
|||||||
stage,
|
stage,
|
||||||
isFirst ? firstAccepted : secondAccepted,
|
isFirst ? firstAccepted : secondAccepted,
|
||||||
isFirst ? secondAccepted : firstAccepted,
|
isFirst ? secondAccepted : firstAccepted,
|
||||||
|
showModifiedWarnings && hasChangedSlots(),
|
||||||
inventoryDisplayFor(player),
|
inventoryDisplayFor(player),
|
||||||
emptyReservedSnapshot(),
|
emptyReservedSnapshot(),
|
||||||
|
blankInventoryBlockedSlots(),
|
||||||
offerSnapshot(isFirst ? firstOffer : secondOffer),
|
offerSnapshot(isFirst ? firstOffer : secondOffer),
|
||||||
offerSnapshot(isFirst ? secondOffer : firstOffer));
|
offerSnapshot(isFirst ? secondOffer : firstOffer),
|
||||||
|
blankChangedSlots(),
|
||||||
|
blankChangedSlots(),
|
||||||
|
showModifiedWarnings ? changedSlotSnapshot(isFirst ? firstChangedSlots : secondChangedSlots) : blankChangedSlots(),
|
||||||
|
showModifiedWarnings ? changedSlotSnapshot(isFirst ? secondChangedSlots : firstChangedSlots) : blankChangedSlots());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendState(ServerPlayer player) {
|
public void sendState(ServerPlayer player) {
|
||||||
@@ -318,9 +334,26 @@ public class TradeSession {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void clearAccepts() {
|
private void clearAccepts(boolean markFirstChanged, boolean markSecondChanged, int changedSlot) {
|
||||||
firstAccepted = false;
|
firstAccepted = false;
|
||||||
secondAccepted = false;
|
secondAccepted = false;
|
||||||
|
if (changedSlot >= 0) {
|
||||||
|
if (markFirstChanged) {
|
||||||
|
firstChangedSlots.set(changedSlot, true);
|
||||||
|
}
|
||||||
|
if (markSecondChanged) {
|
||||||
|
secondChangedSlots.set(changedSlot, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasChangedSlots() {
|
||||||
|
for (int i = 0; i < OFFER_SLOT_COUNT; i++) {
|
||||||
|
if (firstChangedSlots.get(i) || secondChangedSlots.get(i)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int reservedCount(ServerPlayer player, int inventorySlot) {
|
private int reservedCount(ServerPlayer player, int inventorySlot) {
|
||||||
@@ -377,6 +410,26 @@ public class TradeSession {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static List<Boolean> blankInventoryBlockedSlots() {
|
||||||
|
List<Boolean> result = new ArrayList<>(INVENTORY_SLOT_COUNT);
|
||||||
|
for (int i = 0; i < INVENTORY_SLOT_COUNT; i++) {
|
||||||
|
result.add(false);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Boolean> blankChangedSlots() {
|
||||||
|
List<Boolean> result = new ArrayList<>(OFFER_SLOT_COUNT);
|
||||||
|
for (int i = 0; i < OFFER_SLOT_COUNT; i++) {
|
||||||
|
result.add(false);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Boolean> changedSlotSnapshot(List<Boolean> changedSlots) {
|
||||||
|
return new ArrayList<>(changedSlots);
|
||||||
|
}
|
||||||
|
|
||||||
private static List<ItemStack> inventorySnapshot(ServerPlayer player) {
|
private static List<ItemStack> inventorySnapshot(ServerPlayer player) {
|
||||||
List<ItemStack> result = new ArrayList<>(INVENTORY_SLOT_COUNT);
|
List<ItemStack> result = new ArrayList<>(INVENTORY_SLOT_COUNT);
|
||||||
Inventory inventory = player.getInventory();
|
Inventory inventory = player.getInventory();
|
||||||
|
|||||||
@@ -15,10 +15,16 @@ public record TradeView(
|
|||||||
TradeStage stage,
|
TradeStage stage,
|
||||||
boolean selfAccepted,
|
boolean selfAccepted,
|
||||||
boolean otherAccepted,
|
boolean otherAccepted,
|
||||||
|
boolean itemsChanged,
|
||||||
List<ItemStack> inventory,
|
List<ItemStack> inventory,
|
||||||
List<Integer> reservedCounts,
|
List<Integer> reservedCounts,
|
||||||
|
List<Boolean> inventoryBlockedSlots,
|
||||||
List<ItemStack> selfOffer,
|
List<ItemStack> selfOffer,
|
||||||
List<ItemStack> otherOffer) {
|
List<ItemStack> otherOffer,
|
||||||
|
List<Boolean> selfBlockedSlots,
|
||||||
|
List<Boolean> otherBlockedSlots,
|
||||||
|
List<Boolean> selfChangedSlots,
|
||||||
|
List<Boolean> otherChangedSlots) {
|
||||||
|
|
||||||
public static final int INVENTORY_SLOT_COUNT = 36;
|
public static final int INVENTORY_SLOT_COUNT = 36;
|
||||||
public static final int OFFER_SLOT_COUNT = 36;
|
public static final int OFFER_SLOT_COUNT = 36;
|
||||||
@@ -33,10 +39,16 @@ public record TradeView(
|
|||||||
buf.writeEnum(value.stage);
|
buf.writeEnum(value.stage);
|
||||||
buf.writeBoolean(value.selfAccepted);
|
buf.writeBoolean(value.selfAccepted);
|
||||||
buf.writeBoolean(value.otherAccepted);
|
buf.writeBoolean(value.otherAccepted);
|
||||||
|
buf.writeBoolean(value.itemsChanged);
|
||||||
writeStacks(buf, value.inventory, INVENTORY_SLOT_COUNT);
|
writeStacks(buf, value.inventory, INVENTORY_SLOT_COUNT);
|
||||||
writeInts(buf, value.reservedCounts, INVENTORY_SLOT_COUNT);
|
writeInts(buf, value.reservedCounts, INVENTORY_SLOT_COUNT);
|
||||||
|
writeBooleans(buf, value.inventoryBlockedSlots, INVENTORY_SLOT_COUNT);
|
||||||
writeStacks(buf, value.selfOffer, OFFER_SLOT_COUNT);
|
writeStacks(buf, value.selfOffer, OFFER_SLOT_COUNT);
|
||||||
writeStacks(buf, value.otherOffer, OFFER_SLOT_COUNT);
|
writeStacks(buf, value.otherOffer, OFFER_SLOT_COUNT);
|
||||||
|
writeBooleans(buf, value.selfBlockedSlots, OFFER_SLOT_COUNT);
|
||||||
|
writeBooleans(buf, value.otherBlockedSlots, OFFER_SLOT_COUNT);
|
||||||
|
writeBooleans(buf, value.selfChangedSlots, OFFER_SLOT_COUNT);
|
||||||
|
writeBooleans(buf, value.otherChangedSlots, OFFER_SLOT_COUNT);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -49,10 +61,16 @@ public record TradeView(
|
|||||||
buf.readEnum(TradeStage.class),
|
buf.readEnum(TradeStage.class),
|
||||||
buf.readBoolean(),
|
buf.readBoolean(),
|
||||||
buf.readBoolean(),
|
buf.readBoolean(),
|
||||||
|
buf.readBoolean(),
|
||||||
readStacks(buf, INVENTORY_SLOT_COUNT),
|
readStacks(buf, INVENTORY_SLOT_COUNT),
|
||||||
readInts(buf, INVENTORY_SLOT_COUNT),
|
readInts(buf, INVENTORY_SLOT_COUNT),
|
||||||
|
readBooleans(buf, INVENTORY_SLOT_COUNT),
|
||||||
readStacks(buf, OFFER_SLOT_COUNT),
|
readStacks(buf, OFFER_SLOT_COUNT),
|
||||||
readStacks(buf, OFFER_SLOT_COUNT));
|
readStacks(buf, OFFER_SLOT_COUNT),
|
||||||
|
readBooleans(buf, OFFER_SLOT_COUNT),
|
||||||
|
readBooleans(buf, OFFER_SLOT_COUNT),
|
||||||
|
readBooleans(buf, OFFER_SLOT_COUNT),
|
||||||
|
readBooleans(buf, OFFER_SLOT_COUNT));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -83,4 +101,18 @@ public record TradeView(
|
|||||||
}
|
}
|
||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void writeBooleans(RegistryFriendlyByteBuf buf, List<Boolean> values, int expectedSize) {
|
||||||
|
for (int i = 0; i < expectedSize; i++) {
|
||||||
|
buf.writeBoolean(values.get(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Boolean> readBooleans(RegistryFriendlyByteBuf buf, int expectedSize) {
|
||||||
|
List<Boolean> values = new ArrayList<>(expectedSize);
|
||||||
|
for (int i = 0; i < expectedSize; i++) {
|
||||||
|
values.add(buf.readBoolean());
|
||||||
|
}
|
||||||
|
return values;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user