Compare commits
8 Commits
4cd60e4fac
...
905bb986f1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
905bb986f1 | ||
|
|
95d98fb721 | ||
|
|
b845d76cd0 | ||
|
|
a60b41a774 | ||
|
|
109d832ac7 | ||
|
|
e5d3b5d233 | ||
|
|
316ffebe1f | ||
|
|
261f540317 |
@@ -1,5 +1,6 @@
|
|||||||
package com.trunksbomb.minetriad;
|
package com.trunksbomb.minetriad;
|
||||||
|
|
||||||
|
import com.trunksbomb.minetriad.client.hud.LocalDuelHudOverlay;
|
||||||
import com.trunksbomb.minetriad.client.render.FirstPersonCardHandRenderer;
|
import com.trunksbomb.minetriad.client.render.FirstPersonCardHandRenderer;
|
||||||
import com.trunksbomb.minetriad.client.render.DuelTableBlockEntityRenderer;
|
import com.trunksbomb.minetriad.client.render.DuelTableBlockEntityRenderer;
|
||||||
import com.trunksbomb.minetriad.client.screen.CardBinderScreen;
|
import com.trunksbomb.minetriad.client.screen.CardBinderScreen;
|
||||||
@@ -16,7 +17,9 @@ import net.neoforged.bus.api.IEventBus;
|
|||||||
import net.neoforged.fml.common.EventBusSubscriber;
|
import net.neoforged.fml.common.EventBusSubscriber;
|
||||||
import net.neoforged.neoforge.client.event.EntityRenderersEvent;
|
import net.neoforged.neoforge.client.event.EntityRenderersEvent;
|
||||||
import net.neoforged.neoforge.client.event.RegisterMenuScreensEvent;
|
import net.neoforged.neoforge.client.event.RegisterMenuScreensEvent;
|
||||||
|
import net.neoforged.neoforge.client.event.ClientTickEvent;
|
||||||
import net.neoforged.neoforge.client.event.RenderHandEvent;
|
import net.neoforged.neoforge.client.event.RenderHandEvent;
|
||||||
|
import net.neoforged.neoforge.client.event.RenderGuiLayerEvent;
|
||||||
|
|
||||||
@Mod(value = MineTriad.MOD_ID, dist = Dist.CLIENT)
|
@Mod(value = MineTriad.MOD_ID, dist = Dist.CLIENT)
|
||||||
public final class MineTriadClient {
|
public final class MineTriadClient {
|
||||||
@@ -55,5 +58,15 @@ public final class MineTriadClient {
|
|||||||
event.getEquipProgress(),
|
event.getEquipProgress(),
|
||||||
event.getItemStack());
|
event.getItemStack());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SubscribeEvent
|
||||||
|
public static void onClientTick(ClientTickEvent.Post event) {
|
||||||
|
LocalDuelHudOverlay.onClientTick(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SubscribeEvent
|
||||||
|
public static void onRenderGui(RenderGuiLayerEvent.Post event) {
|
||||||
|
LocalDuelHudOverlay.onRenderGui(event);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,115 @@
|
|||||||
|
package com.trunksbomb.minetriad.blockentity;
|
||||||
|
|
||||||
|
import com.trunksbomb.minetriad.game.DuelSession;
|
||||||
|
import com.trunksbomb.minetriad.game.DuelSessionManager;
|
||||||
|
import com.trunksbomb.minetriad.game.MoveResult;
|
||||||
|
import com.trunksbomb.minetriad.item.CardItem;
|
||||||
|
import com.trunksbomb.minetriad.registry.TriadItems;
|
||||||
|
|
||||||
|
import net.minecraft.ChatFormatting;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
|
||||||
|
public final class DuelTableAnimationResolver {
|
||||||
|
private DuelTableAnimationResolver() {
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handleAfterPlayerAnimation(Level level, BlockPos pos, DuelTableBlockEntity table, DuelSession session) {
|
||||||
|
ServerPlayer player = getPlayer(level, session);
|
||||||
|
if (player == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (session.isComplete()) {
|
||||||
|
enterOverview(table, session, player);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (session.isAiVsAi()) {
|
||||||
|
startAiTurn(table, session, player);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MoveResult opponentMove = session.playOpponentTurn();
|
||||||
|
if (!opponentMove.valid()) {
|
||||||
|
player.displayClientMessage(Component.literal(opponentMove.errorMessage()).withStyle(ChatFormatting.RED), false);
|
||||||
|
table.clearBoard();
|
||||||
|
DuelSessionManager.end(player, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!table.setCard(opponentMove.playedCell().index(), CardItem.createCardStack(opponentMove.playedCardId(), TriadItems.TRIAD_CARD.get()), DuelTableBlockEntity.OWNER_SECOND)) {
|
||||||
|
player.displayClientMessage(Component.literal("The Duel Table could not stage the opponent move.").withStyle(ChatFormatting.RED), false);
|
||||||
|
table.clearBoard();
|
||||||
|
DuelSessionManager.end(player, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
session.beginOpponentAnimation();
|
||||||
|
table.updateHudState(session);
|
||||||
|
table.startMoveAnimation(opponentMove, DuelTableBlockEntity.OWNER_SECOND);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handleAfterOpponentAnimation(Level level, BlockPos pos, DuelTableBlockEntity table, DuelSession session) {
|
||||||
|
ServerPlayer player = getPlayer(level, session);
|
||||||
|
if (player == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (session.isComplete()) {
|
||||||
|
enterOverview(table, session, player);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (session.isAiVsAi()) {
|
||||||
|
startAiTurn(table, session, player);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
session.returnToPlaying();
|
||||||
|
table.updateHudState(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void startAiTurn(DuelTableBlockEntity table, DuelSession session, ServerPlayer player) {
|
||||||
|
MoveResult aiMove = session.playAiTurn();
|
||||||
|
if (!aiMove.valid()) {
|
||||||
|
player.displayClientMessage(Component.literal(aiMove.errorMessage()).withStyle(ChatFormatting.RED), false);
|
||||||
|
table.clearBoard();
|
||||||
|
DuelSessionManager.end(player, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int owner = session.isPlayerTurn() ? DuelTableBlockEntity.OWNER_SECOND : DuelTableBlockEntity.OWNER_FIRST;
|
||||||
|
if (!table.setCard(aiMove.playedCell().index(), CardItem.createCardStack(aiMove.playedCardId(), TriadItems.TRIAD_CARD.get()), owner)) {
|
||||||
|
player.displayClientMessage(Component.literal("The Duel Table could not stage the AI move.").withStyle(ChatFormatting.RED), false);
|
||||||
|
table.clearBoard();
|
||||||
|
DuelSessionManager.end(player, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (owner == DuelTableBlockEntity.OWNER_FIRST) {
|
||||||
|
session.beginPlayerAnimation();
|
||||||
|
} else {
|
||||||
|
session.beginOpponentAnimation();
|
||||||
|
}
|
||||||
|
table.updateHudState(session);
|
||||||
|
table.startMoveAnimation(aiMove, owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void enterOverview(DuelTableBlockEntity table, DuelSession session, ServerPlayer player) {
|
||||||
|
session.enterOverview();
|
||||||
|
table.setRewardCards(session.opponentRewardCards().stream()
|
||||||
|
.map(cardId -> CardItem.createCardStack(cardId, TriadItems.TRIAD_CARD.get()))
|
||||||
|
.toList());
|
||||||
|
table.removeBoardCardsOwnedBy(DuelTableBlockEntity.OWNER_SECOND);
|
||||||
|
table.updateHudState(session);
|
||||||
|
table.startOverviewSweepAnimation();
|
||||||
|
player.displayClientMessage(session.resultSummary().copy().withStyle(ChatFormatting.AQUA), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ServerPlayer getPlayer(Level level, DuelSession session) {
|
||||||
|
return level.getServer() == null ? null : level.getServer().getPlayerList().getPlayer(session.playerParticipantId());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,34 +1,81 @@
|
|||||||
package com.trunksbomb.minetriad.blockentity;
|
package com.trunksbomb.minetriad.blockentity;
|
||||||
|
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import com.trunksbomb.minetriad.game.BoardCell;
|
||||||
|
import com.trunksbomb.minetriad.game.DuelSession;
|
||||||
|
import com.trunksbomb.minetriad.game.DuelSessionManager;
|
||||||
|
import com.trunksbomb.minetriad.game.MoveResult;
|
||||||
import com.trunksbomb.minetriad.registry.TriadBlockEntities;
|
import com.trunksbomb.minetriad.registry.TriadBlockEntities;
|
||||||
import com.trunksbomb.minetriad.registry.TriadItems;
|
import com.trunksbomb.minetriad.registry.TriadItems;
|
||||||
|
|
||||||
|
import net.minecraft.ChatFormatting;
|
||||||
import net.minecraft.core.BlockPos;
|
import net.minecraft.core.BlockPos;
|
||||||
import net.minecraft.core.HolderLookup;
|
import net.minecraft.core.HolderLookup;
|
||||||
import net.minecraft.core.NonNullList;
|
import net.minecraft.core.NonNullList;
|
||||||
import net.minecraft.nbt.CompoundTag;
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
import net.minecraft.nbt.Tag;
|
||||||
import net.minecraft.network.Connection;
|
import net.minecraft.network.Connection;
|
||||||
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
|
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.world.entity.EntityType;
|
||||||
|
import net.minecraft.world.entity.Interaction;
|
||||||
import net.minecraft.world.ContainerHelper;
|
import net.minecraft.world.ContainerHelper;
|
||||||
import net.minecraft.world.item.ItemStack;
|
import net.minecraft.world.item.ItemStack;
|
||||||
import net.minecraft.world.level.Level;
|
import net.minecraft.world.level.Level;
|
||||||
import net.minecraft.world.level.block.Block;
|
import net.minecraft.world.level.block.Block;
|
||||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||||
import net.minecraft.world.level.block.state.BlockState;
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
import net.minecraft.world.phys.AABB;
|
||||||
|
|
||||||
|
import com.trunksbomb.minetriad.game.BoardLocalSpace;
|
||||||
|
import com.trunksbomb.minetriad.world.DuelTableBlock;
|
||||||
|
|
||||||
public class DuelTableBlockEntity extends BlockEntity {
|
public class DuelTableBlockEntity extends BlockEntity {
|
||||||
|
public static final String REWARD_PROXY_TAG = "minetriad.reward_proxy";
|
||||||
|
public static final String REWARD_PROXY_POS_PREFIX = "minetriad.reward_pos:";
|
||||||
|
public static final String REWARD_PROXY_SLOT_PREFIX = "minetriad.reward_slot:";
|
||||||
public static final int SLOT_COUNT = 9;
|
public static final int SLOT_COUNT = 9;
|
||||||
|
public static final int REWARD_SLOT_COUNT = 5;
|
||||||
public static final int OWNER_NONE = 0;
|
public static final int OWNER_NONE = 0;
|
||||||
public static final int OWNER_FIRST = 1;
|
public static final int OWNER_FIRST = 1;
|
||||||
public static final int OWNER_SECOND = 2;
|
public static final int OWNER_SECOND = 2;
|
||||||
|
private static final int PLACEMENT_DURATION = 10;
|
||||||
|
private static final int CAPTURE_DURATION = 10;
|
||||||
|
private static final int OVERVIEW_SWEEP_DURATION = 18;
|
||||||
|
private static final int CLEAR_WAVE_DURATION = 10;
|
||||||
|
private static final int REWARD_TAKE_DURATION = 14;
|
||||||
|
|
||||||
private final NonNullList<ItemStack> boardCards = NonNullList.withSize(SLOT_COUNT, ItemStack.EMPTY);
|
private final NonNullList<ItemStack> boardCards = NonNullList.withSize(SLOT_COUNT, ItemStack.EMPTY);
|
||||||
|
private final NonNullList<ItemStack> rewardCards = NonNullList.withSize(REWARD_SLOT_COUNT, ItemStack.EMPTY);
|
||||||
|
private final List<RewardTakeAnimation> rewardTakeAnimations = new ArrayList<>();
|
||||||
private final int[] ownerSlots = new int[SLOT_COUNT];
|
private final int[] ownerSlots = new int[SLOT_COUNT];
|
||||||
|
private final int[] slotAges = new int[SLOT_COUNT];
|
||||||
|
private final ArrayDeque<AnimationStep> pendingAnimations = new ArrayDeque<>();
|
||||||
private UUID firstParticipantId;
|
private UUID firstParticipantId;
|
||||||
private UUID secondParticipantId;
|
private UUID secondParticipantId;
|
||||||
|
private AnimationType animationType = AnimationType.NONE;
|
||||||
|
private int[] animationSlots = new int[0];
|
||||||
|
private int animationTargetOwner = OWNER_NONE;
|
||||||
|
private int animationAge;
|
||||||
|
private int animationDuration;
|
||||||
|
private boolean animationOwnerApplied;
|
||||||
|
private TableAnimationType tableAnimationType = TableAnimationType.NONE;
|
||||||
|
private int tableAnimationAge;
|
||||||
|
private int tableAnimationDuration;
|
||||||
|
private boolean hudActive;
|
||||||
|
private int hudP1HandCount;
|
||||||
|
private int hudP2HandCount;
|
||||||
|
private int hudP1Score;
|
||||||
|
private int hudP2Score;
|
||||||
|
private String hudRules = "";
|
||||||
|
private String hudGameState = "";
|
||||||
|
private String hudTurn = "";
|
||||||
|
private String hudInstruction = "";
|
||||||
|
|
||||||
public DuelTableBlockEntity(BlockPos pos, BlockState blockState) {
|
public DuelTableBlockEntity(BlockPos pos, BlockState blockState) {
|
||||||
super(TriadBlockEntities.DUEL_TABLE.get(), pos, blockState);
|
super(TriadBlockEntities.DUEL_TABLE.get(), pos, blockState);
|
||||||
@@ -38,10 +85,32 @@ public class DuelTableBlockEntity extends BlockEntity {
|
|||||||
return boardCards.get(slot);
|
return boardCards.get(slot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ItemStack getRewardCard(int slot) {
|
||||||
|
return slot >= 0 && slot < rewardCards.size() ? rewardCards.get(slot) : ItemStack.EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int rewardCardCount() {
|
||||||
|
int count = 0;
|
||||||
|
for (ItemStack rewardCard : rewardCards) {
|
||||||
|
if (!rewardCard.isEmpty()) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<RewardTakeAnimation> rewardTakeAnimations() {
|
||||||
|
return List.copyOf(rewardTakeAnimations);
|
||||||
|
}
|
||||||
|
|
||||||
public int ownerAt(int slot) {
|
public int ownerAt(int slot) {
|
||||||
return ownerSlots[slot];
|
return ownerSlots[slot];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int slotAge(int slot) {
|
||||||
|
return slot >= 0 && slot < slotAges.length ? slotAges[slot] : 0;
|
||||||
|
}
|
||||||
|
|
||||||
public Optional<UUID> firstParticipantId() {
|
public Optional<UUID> firstParticipantId() {
|
||||||
return Optional.ofNullable(firstParticipantId);
|
return Optional.ofNullable(firstParticipantId);
|
||||||
}
|
}
|
||||||
@@ -50,12 +119,131 @@ public class DuelTableBlockEntity extends BlockEntity {
|
|||||||
return Optional.ofNullable(secondParticipantId);
|
return Optional.ofNullable(secondParticipantId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean hudActive() {
|
||||||
|
return hudActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int hudP1HandCount() {
|
||||||
|
return hudP1HandCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int hudP2HandCount() {
|
||||||
|
return hudP2HandCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int hudP1Score() {
|
||||||
|
return hudP1Score;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int hudP2Score() {
|
||||||
|
return hudP2Score;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String hudRules() {
|
||||||
|
return hudRules;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String hudGameState() {
|
||||||
|
return hudGameState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String hudTurn() {
|
||||||
|
return hudTurn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String hudInstruction() {
|
||||||
|
return hudInstruction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasActiveAnimation() {
|
||||||
|
return animationType != AnimationType.NONE || tableAnimationType != TableAnimationType.NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String animationTypeName() {
|
||||||
|
return animationType.name();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPlacementAnimation() {
|
||||||
|
return animationType == AnimationType.PLACEMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCaptureAnimation() {
|
||||||
|
return animationType == AnimationType.CAPTURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int animationSlot() {
|
||||||
|
return animationSlots.length > 0 ? animationSlots[0] : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAnimatingSlot(int slot) {
|
||||||
|
for (int animatedSlot : animationSlots) {
|
||||||
|
if (animatedSlot == slot) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float animationProgress(float partialTick) {
|
||||||
|
if (!hasActiveAnimation() || animationDuration <= 0) {
|
||||||
|
return 0.0F;
|
||||||
|
}
|
||||||
|
return Math.clamp((animationAge + partialTick) / animationDuration, 0.0F, 1.0F);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasOverviewSweepAnimation() {
|
||||||
|
return tableAnimationType == TableAnimationType.OVERVIEW_SWEEP;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasClearWaveAnimation() {
|
||||||
|
return tableAnimationType == TableAnimationType.CLEARING;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float tableAnimationProgress(float partialTick) {
|
||||||
|
if (tableAnimationType == TableAnimationType.NONE || tableAnimationDuration <= 0) {
|
||||||
|
return 0.0F;
|
||||||
|
}
|
||||||
|
return Math.clamp((tableAnimationAge + partialTick) / tableAnimationDuration, 0.0F, 1.0F);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean animationOwnerApplied() {
|
||||||
|
return animationOwnerApplied;
|
||||||
|
}
|
||||||
|
|
||||||
public void setParticipants(UUID firstParticipantId, UUID secondParticipantId) {
|
public void setParticipants(UUID firstParticipantId, UUID secondParticipantId) {
|
||||||
this.firstParticipantId = firstParticipantId;
|
this.firstParticipantId = firstParticipantId;
|
||||||
this.secondParticipantId = secondParticipantId;
|
this.secondParticipantId = secondParticipantId;
|
||||||
sync();
|
sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void updateHudState(DuelSession session) {
|
||||||
|
hudActive = true;
|
||||||
|
hudP1HandCount = session.playerHandCount();
|
||||||
|
hudP2HandCount = session.opponentHandCount();
|
||||||
|
hudP1Score = session.playerScore();
|
||||||
|
hudP2Score = session.opponentScore();
|
||||||
|
hudRules = session.rulesSummary();
|
||||||
|
hudGameState = session.gameStateLabel();
|
||||||
|
hudTurn = session.turnIndicator();
|
||||||
|
hudInstruction = session.isInOverview()
|
||||||
|
? rewardCardCount() > 0 ? "P1 choosing 1 cards to keep" : "Ready for next game"
|
||||||
|
: session.nextStepInstruction();
|
||||||
|
sync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearHudState() {
|
||||||
|
hudActive = false;
|
||||||
|
hudP1HandCount = 0;
|
||||||
|
hudP2HandCount = 0;
|
||||||
|
hudP1Score = 0;
|
||||||
|
hudP2Score = 0;
|
||||||
|
hudRules = "";
|
||||||
|
hudGameState = "";
|
||||||
|
hudTurn = "";
|
||||||
|
hudInstruction = "";
|
||||||
|
sync();
|
||||||
|
}
|
||||||
|
|
||||||
public boolean setCard(int slot, ItemStack stack, int owner) {
|
public boolean setCard(int slot, ItemStack stack, int owner) {
|
||||||
if (slot < 0 || slot >= boardCards.size() || !boardCards.get(slot).isEmpty() || !stack.is(TriadItems.TRIAD_CARD.get())) {
|
if (slot < 0 || slot >= boardCards.size() || !boardCards.get(slot).isEmpty() || !stack.is(TriadItems.TRIAD_CARD.get())) {
|
||||||
return false;
|
return false;
|
||||||
@@ -63,6 +251,7 @@ public class DuelTableBlockEntity extends BlockEntity {
|
|||||||
|
|
||||||
boardCards.set(slot, stack.copyWithCount(1));
|
boardCards.set(slot, stack.copyWithCount(1));
|
||||||
ownerSlots[slot] = owner;
|
ownerSlots[slot] = owner;
|
||||||
|
slotAges[slot] = 0;
|
||||||
sync();
|
sync();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -79,12 +268,33 @@ public class DuelTableBlockEntity extends BlockEntity {
|
|||||||
for (int index = 0; index < boardCards.size(); index++) {
|
for (int index = 0; index < boardCards.size(); index++) {
|
||||||
boardCards.set(index, ItemStack.EMPTY);
|
boardCards.set(index, ItemStack.EMPTY);
|
||||||
ownerSlots[index] = OWNER_NONE;
|
ownerSlots[index] = OWNER_NONE;
|
||||||
|
slotAges[index] = 0;
|
||||||
}
|
}
|
||||||
|
pendingAnimations.clear();
|
||||||
|
clearAnimationState();
|
||||||
|
clearRewardCards();
|
||||||
firstParticipantId = null;
|
firstParticipantId = null;
|
||||||
secondParticipantId = null;
|
secondParticipantId = null;
|
||||||
|
clearHudState();
|
||||||
sync();
|
sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void removeBoardCardsOwnedBy(int owner) {
|
||||||
|
boolean changed = false;
|
||||||
|
for (int index = 0; index < boardCards.size(); index++) {
|
||||||
|
if (ownerSlots[index] != owner || boardCards.get(index).isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
boardCards.set(index, ItemStack.EMPTY);
|
||||||
|
ownerSlots[index] = OWNER_NONE;
|
||||||
|
slotAges[index] = 0;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
if (changed) {
|
||||||
|
sync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void dropBoardCards(Level level, BlockPos pos) {
|
public void dropBoardCards(Level level, BlockPos pos) {
|
||||||
for (int slot = 0; slot < boardCards.size(); slot++) {
|
for (int slot = 0; slot < boardCards.size(); slot++) {
|
||||||
ItemStack stack = boardCards.get(slot);
|
ItemStack stack = boardCards.get(slot);
|
||||||
@@ -92,18 +302,267 @@ public class DuelTableBlockEntity extends BlockEntity {
|
|||||||
Block.popResource(level, pos, stack);
|
Block.popResource(level, pos, stack);
|
||||||
boardCards.set(slot, ItemStack.EMPTY);
|
boardCards.set(slot, ItemStack.EMPTY);
|
||||||
ownerSlots[slot] = OWNER_NONE;
|
ownerSlots[slot] = OWNER_NONE;
|
||||||
|
slotAges[slot] = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pendingAnimations.clear();
|
||||||
|
clearAnimationState();
|
||||||
|
dropRewardCards(level, pos);
|
||||||
firstParticipantId = null;
|
firstParticipantId = null;
|
||||||
secondParticipantId = null;
|
secondParticipantId = null;
|
||||||
|
clearHudState();
|
||||||
sync();
|
sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setRewardCards(List<ItemStack> stacks) {
|
||||||
|
clearRewardCards();
|
||||||
|
for (int index = 0; index < rewardCards.size() && index < stacks.size(); index++) {
|
||||||
|
ItemStack stack = stacks.get(index);
|
||||||
|
if (!stack.isEmpty()) {
|
||||||
|
rewardCards.set(index, stack.copyWithCount(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
refreshRewardInteractions();
|
||||||
|
DuelSession session = level == null ? null : DuelSessionManager.getAt(worldPosition);
|
||||||
|
if (session != null) {
|
||||||
|
updateHudState(session);
|
||||||
|
}
|
||||||
|
sync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ItemStack takeRewardCard(int slot) {
|
||||||
|
if (slot < 0 || slot >= rewardCards.size()) {
|
||||||
|
return ItemStack.EMPTY;
|
||||||
|
}
|
||||||
|
ItemStack taken = rewardCards.get(slot);
|
||||||
|
if (taken.isEmpty()) {
|
||||||
|
return ItemStack.EMPTY;
|
||||||
|
}
|
||||||
|
int visibleIndex = visibleRewardIndex(slot);
|
||||||
|
int rewardCountBeforeTake = rewardCardCount();
|
||||||
|
if (visibleIndex >= 0 && rewardCountBeforeTake > 0) {
|
||||||
|
BoardLocalSpace.SlotCenter center = BoardLocalSpace.rewardCenter(
|
||||||
|
visibleIndex,
|
||||||
|
getBlockState().getValue(DuelTableBlock.FACING),
|
||||||
|
rewardCountBeforeTake);
|
||||||
|
rewardTakeAnimations.add(new RewardTakeAnimation(taken.copyWithCount(1), center.x(), center.z(), 0, REWARD_TAKE_DURATION));
|
||||||
|
}
|
||||||
|
rewardCards.set(slot, ItemStack.EMPTY);
|
||||||
|
refreshRewardInteractions();
|
||||||
|
DuelSession session = level == null ? null : DuelSessionManager.getAt(worldPosition);
|
||||||
|
if (session != null) {
|
||||||
|
updateHudState(session);
|
||||||
|
}
|
||||||
|
sync();
|
||||||
|
return taken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearRewardCards() {
|
||||||
|
for (int index = 0; index < rewardCards.size(); index++) {
|
||||||
|
rewardCards.set(index, ItemStack.EMPTY);
|
||||||
|
}
|
||||||
|
rewardTakeAnimations.clear();
|
||||||
|
removeRewardInteractions();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void dropRewardCards(Level level, BlockPos pos) {
|
||||||
|
for (int slot = 0; slot < rewardCards.size(); slot++) {
|
||||||
|
ItemStack stack = rewardCards.get(slot);
|
||||||
|
if (!stack.isEmpty()) {
|
||||||
|
Block.popResource(level, pos, stack);
|
||||||
|
rewardCards.set(slot, ItemStack.EMPTY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rewardTakeAnimations.clear();
|
||||||
|
removeRewardInteractions();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void refreshRewardInteractions() {
|
||||||
|
if (level == null || level.isClientSide) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
removeRewardInteractions();
|
||||||
|
int visibleIndex = 0;
|
||||||
|
var facing = getBlockState().getValue(DuelTableBlock.FACING);
|
||||||
|
for (int slot = 0; slot < rewardCards.size(); slot++) {
|
||||||
|
if (rewardCards.get(slot).isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
BoardLocalSpace.SlotCenter center = BoardLocalSpace.rewardCenter(visibleIndex, facing, rewardCardCount());
|
||||||
|
Interaction interaction = new Interaction(EntityType.INTERACTION, level);
|
||||||
|
interaction.setPos(worldPosition.getX() + center.x(), worldPosition.getY() + 1.12D, worldPosition.getZ() + center.z());
|
||||||
|
interaction.setNoGravity(true);
|
||||||
|
interaction.addTag(REWARD_PROXY_TAG);
|
||||||
|
interaction.addTag(REWARD_PROXY_POS_PREFIX + worldPosition.getX() + "," + worldPosition.getY() + "," + worldPosition.getZ());
|
||||||
|
interaction.addTag(REWARD_PROXY_SLOT_PREFIX + slot);
|
||||||
|
level.addFreshEntity(interaction);
|
||||||
|
visibleIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeRewardInteractions() {
|
||||||
|
if (level == null || level.isClientSide) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
AABB searchBox = new AABB(worldPosition).inflate(2.0D, 2.0D, 2.0D);
|
||||||
|
for (Interaction interaction : level.getEntitiesOfClass(Interaction.class, searchBox, DuelTableBlockEntity::isRewardProxy)) {
|
||||||
|
if (worldPosition.equals(rewardProxyPos(interaction))) {
|
||||||
|
interaction.discard();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isRewardProxy(Interaction interaction) {
|
||||||
|
return interaction.getTags().contains(REWARD_PROXY_TAG);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BlockPos rewardProxyPos(Interaction interaction) {
|
||||||
|
for (String tag : interaction.getTags()) {
|
||||||
|
if (!tag.startsWith(REWARD_PROXY_POS_PREFIX)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String[] parts = tag.substring(REWARD_PROXY_POS_PREFIX.length()).split(",");
|
||||||
|
if (parts.length == 3) {
|
||||||
|
try {
|
||||||
|
return new BlockPos(Integer.parseInt(parts[0]), Integer.parseInt(parts[1]), Integer.parseInt(parts[2]));
|
||||||
|
} catch (NumberFormatException ignored) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int rewardProxySlot(Interaction interaction) {
|
||||||
|
for (String tag : interaction.getTags()) {
|
||||||
|
if (tag.startsWith(REWARD_PROXY_SLOT_PREFIX)) {
|
||||||
|
try {
|
||||||
|
return Integer.parseInt(tag.substring(REWARD_PROXY_SLOT_PREFIX.length()));
|
||||||
|
} catch (NumberFormatException ignored) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startMoveAnimation(MoveResult moveResult, int owner) {
|
||||||
|
pendingAnimations.clear();
|
||||||
|
clearAnimationState();
|
||||||
|
clearTableAnimationState();
|
||||||
|
if (moveResult.playedCell() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pendingAnimations.addLast(new AnimationStep(AnimationType.PLACEMENT, new int[] {moveResult.playedCell().index()}, owner));
|
||||||
|
for (List<BoardCell> captureWave : moveResult.captureWaves()) {
|
||||||
|
int[] captureSlots = captureWave.stream()
|
||||||
|
.mapToInt(BoardCell::index)
|
||||||
|
.toArray();
|
||||||
|
if (captureSlots.length > 0) {
|
||||||
|
pendingAnimations.addLast(new AnimationStep(AnimationType.CAPTURE, captureSlots, owner));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
advanceAnimationStep();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startOverviewSweepAnimation() {
|
||||||
|
clearAnimationState();
|
||||||
|
tableAnimationType = TableAnimationType.OVERVIEW_SWEEP;
|
||||||
|
tableAnimationAge = 0;
|
||||||
|
tableAnimationDuration = OVERVIEW_SWEEP_DURATION;
|
||||||
|
sync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startClearWaveAnimation() {
|
||||||
|
clearAnimationState();
|
||||||
|
tableAnimationType = TableAnimationType.CLEARING;
|
||||||
|
tableAnimationAge = 0;
|
||||||
|
tableAnimationDuration = CLEAR_WAVE_DURATION;
|
||||||
|
sync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void tick(Level level, BlockPos pos, BlockState state, DuelTableBlockEntity blockEntity) {
|
||||||
|
if (blockEntity.hasActiveAnimation()) {
|
||||||
|
if (blockEntity.animationType != AnimationType.NONE) {
|
||||||
|
blockEntity.animationAge++;
|
||||||
|
if (!level.isClientSide
|
||||||
|
&& blockEntity.animationType == AnimationType.CAPTURE
|
||||||
|
&& !blockEntity.animationOwnerApplied
|
||||||
|
&& blockEntity.animationAge >= Math.max(1, blockEntity.animationDuration / 2)) {
|
||||||
|
for (int slot : blockEntity.animationSlots) {
|
||||||
|
blockEntity.setOwner(slot, blockEntity.animationTargetOwner);
|
||||||
|
}
|
||||||
|
blockEntity.animationOwnerApplied = true;
|
||||||
|
blockEntity.sync();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blockEntity.animationAge >= blockEntity.animationDuration) {
|
||||||
|
if (!level.isClientSide) {
|
||||||
|
blockEntity.advanceAnimationStep();
|
||||||
|
if (blockEntity.animationType == AnimationType.NONE && blockEntity.tableAnimationType == TableAnimationType.NONE) {
|
||||||
|
blockEntity.resolvePostAnimation(level, pos);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
blockEntity.clearAnimationState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blockEntity.tableAnimationType != TableAnimationType.NONE) {
|
||||||
|
blockEntity.tableAnimationAge++;
|
||||||
|
if (!level.isClientSide
|
||||||
|
&& blockEntity.tableAnimationType == TableAnimationType.CLEARING) {
|
||||||
|
blockEntity.updateClearWaveState(level, pos);
|
||||||
|
}
|
||||||
|
if (blockEntity.tableAnimationAge >= blockEntity.tableAnimationDuration) {
|
||||||
|
if (!level.isClientSide) {
|
||||||
|
blockEntity.clearTableAnimationState();
|
||||||
|
blockEntity.resolvePostAnimation(level, pos);
|
||||||
|
} else {
|
||||||
|
blockEntity.clearTableAnimationState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int slot = 0; slot < SLOT_COUNT; slot++) {
|
||||||
|
if (!blockEntity.boardCards.get(slot).isEmpty()) {
|
||||||
|
blockEntity.slotAges[slot]++;
|
||||||
|
} else {
|
||||||
|
blockEntity.slotAges[slot] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!blockEntity.rewardTakeAnimations.isEmpty()) {
|
||||||
|
boolean changed = false;
|
||||||
|
for (int index = blockEntity.rewardTakeAnimations.size() - 1; index >= 0; index--) {
|
||||||
|
RewardTakeAnimation animation = blockEntity.rewardTakeAnimations.get(index);
|
||||||
|
animation.age++;
|
||||||
|
if (animation.age >= animation.duration) {
|
||||||
|
blockEntity.rewardTakeAnimations.remove(index);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!level.isClientSide && changed) {
|
||||||
|
blockEntity.sync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void saveAdditional(CompoundTag tag, HolderLookup.Provider registries) {
|
protected void saveAdditional(CompoundTag tag, HolderLookup.Provider registries) {
|
||||||
super.saveAdditional(tag, registries);
|
super.saveAdditional(tag, registries);
|
||||||
ContainerHelper.saveAllItems(tag, boardCards, registries);
|
ContainerHelper.saveAllItems(tag, boardCards, registries);
|
||||||
|
CompoundTag rewardTag = new CompoundTag();
|
||||||
|
ContainerHelper.saveAllItems(rewardTag, rewardCards, registries);
|
||||||
|
tag.put("RewardCards", rewardTag);
|
||||||
|
saveRewardTakeAnimations(tag, registries);
|
||||||
|
saveHudState(tag);
|
||||||
tag.putIntArray("OwnerSlots", ownerSlots);
|
tag.putIntArray("OwnerSlots", ownerSlots);
|
||||||
|
tag.putIntArray("SlotAges", slotAges);
|
||||||
|
saveAnimation(tag);
|
||||||
|
saveTableAnimation(tag);
|
||||||
if (firstParticipantId != null) {
|
if (firstParticipantId != null) {
|
||||||
tag.putUUID("FirstParticipantId", firstParticipantId);
|
tag.putUUID("FirstParticipantId", firstParticipantId);
|
||||||
}
|
}
|
||||||
@@ -117,14 +576,29 @@ public class DuelTableBlockEntity extends BlockEntity {
|
|||||||
super.loadAdditional(tag, registries);
|
super.loadAdditional(tag, registries);
|
||||||
clearBoardContents();
|
clearBoardContents();
|
||||||
ContainerHelper.loadAllItems(tag, boardCards, registries);
|
ContainerHelper.loadAllItems(tag, boardCards, registries);
|
||||||
|
if (tag.contains("RewardCards", Tag.TAG_COMPOUND)) {
|
||||||
|
ContainerHelper.loadAllItems(tag.getCompound("RewardCards"), rewardCards, registries);
|
||||||
|
}
|
||||||
|
loadRewardTakeAnimations(tag, registries);
|
||||||
|
loadHudState(tag);
|
||||||
loadOwnerData(tag);
|
loadOwnerData(tag);
|
||||||
|
loadAnimation(tag);
|
||||||
|
loadTableAnimation(tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompoundTag getUpdateTag(HolderLookup.Provider registries) {
|
public CompoundTag getUpdateTag(HolderLookup.Provider registries) {
|
||||||
CompoundTag tag = super.getUpdateTag(registries);
|
CompoundTag tag = super.getUpdateTag(registries);
|
||||||
ContainerHelper.saveAllItems(tag, boardCards, registries);
|
ContainerHelper.saveAllItems(tag, boardCards, registries);
|
||||||
|
CompoundTag rewardTag = new CompoundTag();
|
||||||
|
ContainerHelper.saveAllItems(rewardTag, rewardCards, registries);
|
||||||
|
tag.put("RewardCards", rewardTag);
|
||||||
|
saveRewardTakeAnimations(tag, registries);
|
||||||
|
saveHudState(tag);
|
||||||
tag.putIntArray("OwnerSlots", ownerSlots);
|
tag.putIntArray("OwnerSlots", ownerSlots);
|
||||||
|
tag.putIntArray("SlotAges", slotAges);
|
||||||
|
saveAnimation(tag);
|
||||||
|
saveTableAnimation(tag);
|
||||||
if (firstParticipantId != null) {
|
if (firstParticipantId != null) {
|
||||||
tag.putUUID("FirstParticipantId", firstParticipantId);
|
tag.putUUID("FirstParticipantId", firstParticipantId);
|
||||||
}
|
}
|
||||||
@@ -145,7 +619,14 @@ public class DuelTableBlockEntity extends BlockEntity {
|
|||||||
if (tag != null) {
|
if (tag != null) {
|
||||||
clearBoardContents();
|
clearBoardContents();
|
||||||
ContainerHelper.loadAllItems(tag, boardCards, registries);
|
ContainerHelper.loadAllItems(tag, boardCards, registries);
|
||||||
|
if (tag.contains("RewardCards", Tag.TAG_COMPOUND)) {
|
||||||
|
ContainerHelper.loadAllItems(tag.getCompound("RewardCards"), rewardCards, registries);
|
||||||
|
}
|
||||||
|
loadRewardTakeAnimations(tag, registries);
|
||||||
|
loadHudState(tag);
|
||||||
loadOwnerData(tag);
|
loadOwnerData(tag);
|
||||||
|
loadAnimation(tag);
|
||||||
|
loadTableAnimation(tag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,9 +634,22 @@ public class DuelTableBlockEntity extends BlockEntity {
|
|||||||
for (int index = 0; index < boardCards.size(); index++) {
|
for (int index = 0; index < boardCards.size(); index++) {
|
||||||
boardCards.set(index, ItemStack.EMPTY);
|
boardCards.set(index, ItemStack.EMPTY);
|
||||||
ownerSlots[index] = OWNER_NONE;
|
ownerSlots[index] = OWNER_NONE;
|
||||||
|
slotAges[index] = 0;
|
||||||
}
|
}
|
||||||
|
pendingAnimations.clear();
|
||||||
|
clearAnimationState();
|
||||||
|
clearRewardCards();
|
||||||
firstParticipantId = null;
|
firstParticipantId = null;
|
||||||
secondParticipantId = null;
|
secondParticipantId = null;
|
||||||
|
hudActive = false;
|
||||||
|
hudP1HandCount = 0;
|
||||||
|
hudP2HandCount = 0;
|
||||||
|
hudP1Score = 0;
|
||||||
|
hudP2Score = 0;
|
||||||
|
hudRules = "";
|
||||||
|
hudGameState = "";
|
||||||
|
hudTurn = "";
|
||||||
|
hudInstruction = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadOwnerData(CompoundTag tag) {
|
private void loadOwnerData(CompoundTag tag) {
|
||||||
@@ -163,14 +657,265 @@ public class DuelTableBlockEntity extends BlockEntity {
|
|||||||
for (int index = 0; index < ownerSlots.length; index++) {
|
for (int index = 0; index < ownerSlots.length; index++) {
|
||||||
ownerSlots[index] = index < loadedOwners.length ? loadedOwners[index] : OWNER_NONE;
|
ownerSlots[index] = index < loadedOwners.length ? loadedOwners[index] : OWNER_NONE;
|
||||||
}
|
}
|
||||||
|
int[] loadedAges = tag.getIntArray("SlotAges");
|
||||||
|
for (int index = 0; index < slotAges.length; index++) {
|
||||||
|
slotAges[index] = index < loadedAges.length ? loadedAges[index] : 0;
|
||||||
|
}
|
||||||
firstParticipantId = tag.hasUUID("FirstParticipantId") ? tag.getUUID("FirstParticipantId") : null;
|
firstParticipantId = tag.hasUUID("FirstParticipantId") ? tag.getUUID("FirstParticipantId") : null;
|
||||||
secondParticipantId = tag.hasUUID("SecondParticipantId") ? tag.getUUID("SecondParticipantId") : null;
|
secondParticipantId = tag.hasUUID("SecondParticipantId") ? tag.getUUID("SecondParticipantId") : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void saveRewardTakeAnimations(CompoundTag tag, HolderLookup.Provider registries) {
|
||||||
|
CompoundTag animationsTag = new CompoundTag();
|
||||||
|
for (int index = 0; index < rewardTakeAnimations.size(); index++) {
|
||||||
|
RewardTakeAnimation animation = rewardTakeAnimations.get(index);
|
||||||
|
CompoundTag animationTag = new CompoundTag();
|
||||||
|
animationTag.put("Stack", animation.stack.saveOptional(registries));
|
||||||
|
animationTag.putFloat("LocalX", animation.localX);
|
||||||
|
animationTag.putFloat("LocalZ", animation.localZ);
|
||||||
|
animationTag.putInt("Age", animation.age);
|
||||||
|
animationTag.putInt("Duration", animation.duration);
|
||||||
|
animationsTag.put("Animation" + index, animationTag);
|
||||||
|
}
|
||||||
|
animationsTag.putInt("Count", rewardTakeAnimations.size());
|
||||||
|
tag.put("RewardTakeAnimations", animationsTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveHudState(CompoundTag tag) {
|
||||||
|
tag.putBoolean("HudActive", hudActive);
|
||||||
|
tag.putInt("HudP1HandCount", hudP1HandCount);
|
||||||
|
tag.putInt("HudP2HandCount", hudP2HandCount);
|
||||||
|
tag.putInt("HudP1Score", hudP1Score);
|
||||||
|
tag.putInt("HudP2Score", hudP2Score);
|
||||||
|
tag.putString("HudRules", hudRules);
|
||||||
|
tag.putString("HudGameState", hudGameState);
|
||||||
|
tag.putString("HudTurn", hudTurn);
|
||||||
|
tag.putString("HudInstruction", hudInstruction);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadHudState(CompoundTag tag) {
|
||||||
|
hudActive = tag.getBoolean("HudActive");
|
||||||
|
hudP1HandCount = tag.getInt("HudP1HandCount");
|
||||||
|
hudP2HandCount = tag.getInt("HudP2HandCount");
|
||||||
|
hudP1Score = tag.getInt("HudP1Score");
|
||||||
|
hudP2Score = tag.getInt("HudP2Score");
|
||||||
|
hudRules = tag.getString("HudRules");
|
||||||
|
hudGameState = tag.getString("HudGameState");
|
||||||
|
hudTurn = tag.getString("HudTurn");
|
||||||
|
hudInstruction = tag.getString("HudInstruction");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadRewardTakeAnimations(CompoundTag tag, HolderLookup.Provider registries) {
|
||||||
|
rewardTakeAnimations.clear();
|
||||||
|
if (!tag.contains("RewardTakeAnimations", Tag.TAG_COMPOUND)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
CompoundTag animationsTag = tag.getCompound("RewardTakeAnimations");
|
||||||
|
int count = animationsTag.getInt("Count");
|
||||||
|
for (int index = 0; index < count; index++) {
|
||||||
|
CompoundTag animationTag = animationsTag.getCompound("Animation" + index);
|
||||||
|
ItemStack stack = ItemStack.parseOptional(registries, animationTag.getCompound("Stack"));
|
||||||
|
if (stack.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
rewardTakeAnimations.add(new RewardTakeAnimation(
|
||||||
|
stack,
|
||||||
|
animationTag.getFloat("LocalX"),
|
||||||
|
animationTag.getFloat("LocalZ"),
|
||||||
|
animationTag.getInt("Age"),
|
||||||
|
animationTag.getInt("Duration")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveAnimation(CompoundTag tag) {
|
||||||
|
tag.putString("AnimationType", animationType.name());
|
||||||
|
tag.putIntArray("AnimationSlots", animationSlots);
|
||||||
|
tag.putInt("AnimationTargetOwner", animationTargetOwner);
|
||||||
|
tag.putInt("AnimationAge", animationAge);
|
||||||
|
tag.putInt("AnimationDuration", animationDuration);
|
||||||
|
tag.putBoolean("AnimationOwnerApplied", animationOwnerApplied);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadAnimation(CompoundTag tag) {
|
||||||
|
animationType = AnimationType.valueOf(tag.getString("AnimationType").isEmpty() ? AnimationType.NONE.name() : tag.getString("AnimationType"));
|
||||||
|
animationSlots = tag.getIntArray("AnimationSlots");
|
||||||
|
animationTargetOwner = tag.getInt("AnimationTargetOwner");
|
||||||
|
animationAge = tag.getInt("AnimationAge");
|
||||||
|
animationDuration = tag.getInt("AnimationDuration");
|
||||||
|
animationOwnerApplied = tag.getBoolean("AnimationOwnerApplied");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveTableAnimation(CompoundTag tag) {
|
||||||
|
tag.putString("TableAnimationType", tableAnimationType.name());
|
||||||
|
tag.putInt("TableAnimationAge", tableAnimationAge);
|
||||||
|
tag.putInt("TableAnimationDuration", tableAnimationDuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadTableAnimation(CompoundTag tag) {
|
||||||
|
tableAnimationType = TableAnimationType.valueOf(tag.getString("TableAnimationType").isEmpty() ? TableAnimationType.NONE.name() : tag.getString("TableAnimationType"));
|
||||||
|
tableAnimationAge = tag.getInt("TableAnimationAge");
|
||||||
|
tableAnimationDuration = tag.getInt("TableAnimationDuration");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void advanceAnimationStep() {
|
||||||
|
AnimationStep nextStep = pendingAnimations.pollFirst();
|
||||||
|
if (nextStep == null) {
|
||||||
|
clearAnimationState();
|
||||||
|
sync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
animationType = nextStep.type();
|
||||||
|
animationSlots = nextStep.slots();
|
||||||
|
animationTargetOwner = nextStep.targetOwner();
|
||||||
|
animationAge = 0;
|
||||||
|
animationDuration = nextStep.type() == AnimationType.PLACEMENT ? PLACEMENT_DURATION : CAPTURE_DURATION;
|
||||||
|
animationOwnerApplied = nextStep.type() != AnimationType.CAPTURE;
|
||||||
|
sync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearAnimationState() {
|
||||||
|
animationType = AnimationType.NONE;
|
||||||
|
animationSlots = new int[0];
|
||||||
|
animationTargetOwner = OWNER_NONE;
|
||||||
|
animationAge = 0;
|
||||||
|
animationDuration = 0;
|
||||||
|
animationOwnerApplied = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearTableAnimationState() {
|
||||||
|
tableAnimationType = TableAnimationType.NONE;
|
||||||
|
tableAnimationAge = 0;
|
||||||
|
tableAnimationDuration = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resolvePostAnimation(Level level, BlockPos pos) {
|
||||||
|
DuelSession session = DuelSessionManager.getAt(pos);
|
||||||
|
if (session == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (session.isClosingAnimation()) {
|
||||||
|
clearBoard();
|
||||||
|
var player = level.getServer() == null ? null : level.getServer().getPlayerList().getPlayer(session.playerParticipantId());
|
||||||
|
if (player != null) {
|
||||||
|
DuelSessionManager.end(player, true);
|
||||||
|
player.displayClientMessage(Component.literal("Duel finished. All played cards have been returned.").withStyle(ChatFormatting.GREEN), false);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (session.isAwaitingPlayerAnimation()) {
|
||||||
|
DuelTableAnimationResolver.handleAfterPlayerAnimation(level, pos, this, session);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (session.isAwaitingOpponentAnimation()) {
|
||||||
|
DuelTableAnimationResolver.handleAfterOpponentAnimation(level, pos, this, session);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateClearWaveState(Level level, BlockPos pos) {
|
||||||
|
for (int slot = 0; slot < SLOT_COUNT; slot++) {
|
||||||
|
if (boardCards.get(slot).isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (clearWaveSlotProgress(slot, 0.0F) >= 1.0F) {
|
||||||
|
boardCards.set(slot, ItemStack.EMPTY);
|
||||||
|
ownerSlots[slot] = OWNER_NONE;
|
||||||
|
slotAges[slot] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public float overviewSweepSlotProgress(int slot, float partialTick) {
|
||||||
|
return waveSlotProgress(slot, partialTick, SLOT_COUNT, tableAnimationAge, tableAnimationDuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float clearWaveSlotProgress(int slot, float partialTick) {
|
||||||
|
return waveSlotProgress(slot, partialTick, SLOT_COUNT, tableAnimationAge, tableAnimationDuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
private float waveSlotProgress(int slot, float partialTick, int slotCount, int age, int duration) {
|
||||||
|
if (slot < 0 || slot >= slotCount || duration <= 0) {
|
||||||
|
return 0.0F;
|
||||||
|
}
|
||||||
|
float global = Math.clamp((age + partialTick) / duration, 0.0F, 1.0F);
|
||||||
|
float delay = slot / (float) slotCount;
|
||||||
|
float window = 0.28F;
|
||||||
|
return Math.clamp((global - delay) / window, 0.0F, 1.0F);
|
||||||
|
}
|
||||||
|
|
||||||
private void sync() {
|
private void sync() {
|
||||||
setChanged();
|
setChanged();
|
||||||
if (level != null) {
|
if (level != null) {
|
||||||
level.sendBlockUpdated(worldPosition, getBlockState(), getBlockState(), Block.UPDATE_ALL);
|
level.sendBlockUpdated(worldPosition, getBlockState(), getBlockState(), Block.UPDATE_ALL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private enum AnimationType {
|
||||||
|
NONE,
|
||||||
|
PLACEMENT,
|
||||||
|
CAPTURE
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum TableAnimationType {
|
||||||
|
NONE,
|
||||||
|
OVERVIEW_SWEEP,
|
||||||
|
CLEARING
|
||||||
|
}
|
||||||
|
|
||||||
|
private record AnimationStep(AnimationType type, int[] slots, int targetOwner) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class RewardTakeAnimation {
|
||||||
|
private final ItemStack stack;
|
||||||
|
private final float localX;
|
||||||
|
private final float localZ;
|
||||||
|
private int age;
|
||||||
|
private final int duration;
|
||||||
|
|
||||||
|
private RewardTakeAnimation(ItemStack stack, float localX, float localZ, int age, int duration) {
|
||||||
|
this.stack = stack;
|
||||||
|
this.localX = localX;
|
||||||
|
this.localZ = localZ;
|
||||||
|
this.age = age;
|
||||||
|
this.duration = duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ItemStack stack() {
|
||||||
|
return stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float localX() {
|
||||||
|
return localX;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float localZ() {
|
||||||
|
return localZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int age() {
|
||||||
|
return age;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int duration() {
|
||||||
|
return duration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int visibleRewardIndex(int slot) {
|
||||||
|
int visibleIndex = 0;
|
||||||
|
for (int index = 0; index < rewardCards.size(); index++) {
|
||||||
|
if (rewardCards.get(index).isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (index == slot) {
|
||||||
|
return visibleIndex;
|
||||||
|
}
|
||||||
|
visibleIndex++;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,130 @@
|
|||||||
|
package com.trunksbomb.minetriad.client.hud;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.trunksbomb.minetriad.blockentity.DuelTableBlockEntity;
|
||||||
|
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.client.gui.GuiGraphics;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||||
|
import net.neoforged.neoforge.client.event.ClientTickEvent;
|
||||||
|
import net.neoforged.neoforge.client.event.RenderGuiLayerEvent;
|
||||||
|
|
||||||
|
public final class LocalDuelHudOverlay {
|
||||||
|
private static final int SCAN_INTERVAL_TICKS = 10;
|
||||||
|
private static final int STALE_TIMEOUT_TICKS = 20;
|
||||||
|
private static final double MAX_DISTANCE_SQ = 10.0D * 10.0D;
|
||||||
|
|
||||||
|
private static BlockPos trackedTablePos;
|
||||||
|
private static int nextScanIn;
|
||||||
|
private static int staleTicksRemaining;
|
||||||
|
|
||||||
|
private LocalDuelHudOverlay() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void onClientTick(ClientTickEvent.Post event) {
|
||||||
|
Minecraft minecraft = Minecraft.getInstance();
|
||||||
|
if (minecraft.level == null || minecraft.player == null) {
|
||||||
|
clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trackedTablePos != null && !isValidTrackedTable(minecraft, trackedTablePos)) {
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextScanIn > 0) {
|
||||||
|
nextScanIn--;
|
||||||
|
} else {
|
||||||
|
trackedTablePos = findNearestActiveTable(minecraft);
|
||||||
|
nextScanIn = SCAN_INTERVAL_TICKS;
|
||||||
|
staleTicksRemaining = trackedTablePos == null ? 0 : STALE_TIMEOUT_TICKS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trackedTablePos != null) {
|
||||||
|
staleTicksRemaining--;
|
||||||
|
if (staleTicksRemaining <= 0 && !isValidTrackedTable(minecraft, trackedTablePos)) {
|
||||||
|
clear();
|
||||||
|
} else if (isValidTrackedTable(minecraft, trackedTablePos)) {
|
||||||
|
staleTicksRemaining = STALE_TIMEOUT_TICKS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void onRenderGui(RenderGuiLayerEvent.Post event) {
|
||||||
|
Minecraft minecraft = Minecraft.getInstance();
|
||||||
|
if (minecraft.level == null || minecraft.player == null || trackedTablePos == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DuelTableBlockEntity table = duelTableAt(minecraft, trackedTablePos);
|
||||||
|
if (table == null || !table.hudActive()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Component> lines = new ArrayList<>();
|
||||||
|
lines.add(Component.literal("P1 Cards in hand: " + table.hudP1HandCount()));
|
||||||
|
lines.add(Component.literal("P2 Cards in hand: " + table.hudP2HandCount()));
|
||||||
|
lines.add(Component.literal("Score: " + table.hudP1Score() + ":" + table.hudP2Score()));
|
||||||
|
lines.add(Component.literal("Rules: " + table.hudRules()));
|
||||||
|
lines.add(Component.literal("Game state: " + table.hudGameState()));
|
||||||
|
lines.add(Component.literal("Turn: " + table.hudTurn()));
|
||||||
|
lines.add(Component.literal("Next: " + table.hudInstruction()));
|
||||||
|
|
||||||
|
GuiGraphics guiGraphics = event.getGuiGraphics();
|
||||||
|
int x = 8;
|
||||||
|
int y = 8;
|
||||||
|
int lineHeight = 10;
|
||||||
|
int width = 0;
|
||||||
|
for (Component line : lines) {
|
||||||
|
width = Math.max(width, minecraft.font.width(line));
|
||||||
|
}
|
||||||
|
int height = lines.size() * lineHeight + 8;
|
||||||
|
guiGraphics.fill(x - 4, y - 4, x + width + 4, y + height - 4, 0x80000000);
|
||||||
|
for (int index = 0; index < lines.size(); index++) {
|
||||||
|
guiGraphics.drawString(minecraft.font, lines.get(index), x, y + index * lineHeight, 0xFFFFFF, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BlockPos findNearestActiveTable(Minecraft minecraft) {
|
||||||
|
BlockPos origin = minecraft.player.blockPosition();
|
||||||
|
BlockPos nearest = null;
|
||||||
|
double nearestDistance = MAX_DISTANCE_SQ;
|
||||||
|
for (BlockPos pos : BlockPos.betweenClosed(origin.offset(-8, -4, -8), origin.offset(8, 4, 8))) {
|
||||||
|
DuelTableBlockEntity table = duelTableAt(minecraft, pos);
|
||||||
|
if (table == null || !table.hudActive()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
double distance = pos.distSqr(minecraft.player.blockPosition());
|
||||||
|
if (distance <= nearestDistance) {
|
||||||
|
nearestDistance = distance;
|
||||||
|
nearest = pos.immutable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nearest;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isValidTrackedTable(Minecraft minecraft, BlockPos pos) {
|
||||||
|
DuelTableBlockEntity table = duelTableAt(minecraft, pos);
|
||||||
|
return table != null
|
||||||
|
&& table.hudActive()
|
||||||
|
&& pos.distSqr(minecraft.player.blockPosition()) <= MAX_DISTANCE_SQ;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DuelTableBlockEntity duelTableAt(Minecraft minecraft, BlockPos pos) {
|
||||||
|
if (minecraft.level == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
BlockEntity blockEntity = minecraft.level.getBlockEntity(pos);
|
||||||
|
return blockEntity instanceof DuelTableBlockEntity duelTableBlockEntity ? duelTableBlockEntity : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void clear() {
|
||||||
|
trackedTablePos = null;
|
||||||
|
nextScanIn = 0;
|
||||||
|
staleTicksRemaining = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,12 +8,21 @@ import com.trunksbomb.minetriad.game.BoardLocalSpace;
|
|||||||
import com.trunksbomb.minetriad.world.DuelTableBlock;
|
import com.trunksbomb.minetriad.world.DuelTableBlock;
|
||||||
|
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.client.renderer.LevelRenderer;
|
||||||
import net.minecraft.client.renderer.MultiBufferSource;
|
import net.minecraft.client.renderer.MultiBufferSource;
|
||||||
|
import net.minecraft.client.renderer.RenderType;
|
||||||
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
|
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
|
||||||
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
|
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
|
||||||
|
import net.minecraft.world.entity.Interaction;
|
||||||
import net.minecraft.world.item.ItemStack;
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import net.minecraft.world.phys.AABB;
|
||||||
|
|
||||||
public class DuelTableBlockEntityRenderer implements BlockEntityRenderer<DuelTableBlockEntity> {
|
public class DuelTableBlockEntityRenderer implements BlockEntityRenderer<DuelTableBlockEntity> {
|
||||||
|
private static final float TABLE_CARD_Y = 1.066F;
|
||||||
|
private static final float REWARD_CARD_Y = 1.75F;
|
||||||
|
private static final float IDLE_BOB_HEIGHT = 0.012F;
|
||||||
|
private static final float IDLE_BOB_SPEED = 0.08F;
|
||||||
|
|
||||||
public DuelTableBlockEntityRenderer(BlockEntityRendererProvider.Context context) {
|
public DuelTableBlockEntityRenderer(BlockEntityRendererProvider.Context context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,15 +36,155 @@ public class DuelTableBlockEntityRenderer implements BlockEntityRenderer<DuelTab
|
|||||||
|
|
||||||
BoardCell cell = new BoardCell(slot / BoardCell.SIZE, slot % BoardCell.SIZE);
|
BoardCell cell = new BoardCell(slot / BoardCell.SIZE, slot % BoardCell.SIZE);
|
||||||
BoardLocalSpace.SlotCenter center = BoardLocalSpace.slotCenter(cell, blockEntity.getBlockState().getValue(DuelTableBlock.FACING));
|
BoardLocalSpace.SlotCenter center = BoardLocalSpace.slotCenter(cell, blockEntity.getBlockState().getValue(DuelTableBlock.FACING));
|
||||||
|
float y = TABLE_CARD_Y;
|
||||||
|
float extraFlip = 0.0F;
|
||||||
|
boolean hideCard = false;
|
||||||
|
float idleOffset = idleBob(blockEntity, slot, partialTick);
|
||||||
|
if (blockEntity.isAnimatingSlot(slot)) {
|
||||||
|
float progress = blockEntity.animationProgress(partialTick);
|
||||||
|
if (blockEntity.isPlacementAnimation()) {
|
||||||
|
y += (1.0F - progress) * 0.18F;
|
||||||
|
} else if (blockEntity.isCaptureAnimation()) {
|
||||||
|
y += idleOffset;
|
||||||
|
y += (float) Math.sin(progress * Math.PI) * 0.08F;
|
||||||
|
extraFlip = 360.0F * progress;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
y += idleOffset;
|
||||||
|
}
|
||||||
|
if (blockEntity.hasOverviewSweepAnimation()) {
|
||||||
|
float progress = blockEntity.overviewSweepSlotProgress(slot, partialTick);
|
||||||
|
y += (float) Math.sin(progress * Math.PI) * 0.05F;
|
||||||
|
}
|
||||||
|
if (blockEntity.hasClearWaveAnimation()) {
|
||||||
|
float progress = blockEntity.clearWaveSlotProgress(slot, partialTick);
|
||||||
|
y += progress * 0.22F;
|
||||||
|
hideCard = progress >= 1.0F;
|
||||||
|
}
|
||||||
|
if (hideCard) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
poseStack.pushPose();
|
poseStack.pushPose();
|
||||||
poseStack.translate(center.x(), 1.066F, center.z());
|
poseStack.translate(center.x(), y, center.z());
|
||||||
poseStack.mulPose(Axis.YP.rotationDegrees(BoardLocalSpace.cardYawDegrees(blockEntity.getBlockState().getValue(DuelTableBlock.FACING))));
|
poseStack.mulPose(Axis.YP.rotationDegrees(BoardLocalSpace.cardYawDegrees(blockEntity.getBlockState().getValue(DuelTableBlock.FACING))));
|
||||||
|
if (extraFlip != 0.0F) {
|
||||||
|
poseStack.mulPose(Axis.ZP.rotationDegrees(extraFlip));
|
||||||
|
}
|
||||||
poseStack.mulPose(Axis.XN.rotationDegrees(90.0F));
|
poseStack.mulPose(Axis.XN.rotationDegrees(90.0F));
|
||||||
poseStack.scale(0.24F, 0.24F, 0.24F);
|
poseStack.scale(0.24F, 0.24F, 0.24F);
|
||||||
TriadCardItemRenderer.renderCard(stack, poseStack, buffer, perspectivePalette(blockEntity, blockEntity.ownerAt(slot)));
|
TriadCardItemRenderer.renderCard(stack, poseStack, buffer, perspectivePalette(blockEntity, blockEntity.ownerAt(slot)));
|
||||||
poseStack.popPose();
|
poseStack.popPose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderRewardCards(blockEntity, partialTick, poseStack, buffer);
|
||||||
|
renderRewardTakeAnimations(blockEntity, partialTick, poseStack, buffer);
|
||||||
|
renderRewardProxyDebug(blockEntity, poseStack, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void renderRewardCards(DuelTableBlockEntity blockEntity, float partialTick, PoseStack poseStack, MultiBufferSource buffer) {
|
||||||
|
int rewardCount = blockEntity.rewardCardCount();
|
||||||
|
if (rewardCount <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rewardIndex = 0;
|
||||||
|
for (int slot = 0; slot < DuelTableBlockEntity.REWARD_SLOT_COUNT; slot++) {
|
||||||
|
ItemStack rewardStack = blockEntity.getRewardCard(slot);
|
||||||
|
if (rewardStack.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
BoardLocalSpace.SlotCenter center = BoardLocalSpace.rewardCenter(
|
||||||
|
rewardIndex,
|
||||||
|
blockEntity.getBlockState().getValue(DuelTableBlock.FACING),
|
||||||
|
rewardCount);
|
||||||
|
float manifestProgress = blockEntity.hasOverviewSweepAnimation()
|
||||||
|
? Math.max(0.2F, blockEntity.overviewSweepSlotProgress(Math.min(slot, DuelTableBlockEntity.SLOT_COUNT - 1), partialTick))
|
||||||
|
: 1.0F;
|
||||||
|
float y = 1.0F + (REWARD_CARD_Y - 1.0F) * manifestProgress + (float) Math.sin((Minecraft.getInstance().level == null ? 0.0F : (Minecraft.getInstance().level.getGameTime() + partialTick) * 0.08F) + rewardIndex * 0.7F) * 0.01F;
|
||||||
|
|
||||||
|
poseStack.pushPose();
|
||||||
|
poseStack.translate(center.x(), y, center.z());
|
||||||
|
poseStack.mulPose(Axis.YP.rotationDegrees(blockEntity.getBlockState().getValue(DuelTableBlock.FACING).toYRot()));
|
||||||
|
poseStack.scale(0.3F, 0.3F, 0.3F);
|
||||||
|
TriadCardItemRenderer.renderCard(rewardStack, poseStack, buffer, perspectivePalette(blockEntity, DuelTableBlockEntity.OWNER_SECOND));
|
||||||
|
poseStack.popPose();
|
||||||
|
rewardIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float idleBob(DuelTableBlockEntity blockEntity, int slot, float partialTick) {
|
||||||
|
int age = blockEntity.slotAge(slot);
|
||||||
|
int delay = 6 + deterministicOffset(blockEntity, slot) % 18;
|
||||||
|
if (age <= delay) {
|
||||||
|
return 0.0F;
|
||||||
|
}
|
||||||
|
|
||||||
|
float bobTime = (age - delay) + partialTick;
|
||||||
|
return (float) Math.sin(bobTime * IDLE_BOB_SPEED) * IDLE_BOB_HEIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int deterministicOffset(DuelTableBlockEntity blockEntity, int slot) {
|
||||||
|
return Math.floorMod(blockEntity.getBlockPos().hashCode() * 31 + slot * 17, 97);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void renderRewardTakeAnimations(DuelTableBlockEntity blockEntity, float partialTick, PoseStack poseStack, MultiBufferSource buffer) {
|
||||||
|
var facing = blockEntity.getBlockState().getValue(DuelTableBlock.FACING);
|
||||||
|
float forwardX = facing.getStepX() * 0.33F;
|
||||||
|
float forwardZ = facing.getStepZ() * 0.33F;
|
||||||
|
for (DuelTableBlockEntity.RewardTakeAnimation animation : blockEntity.rewardTakeAnimations()) {
|
||||||
|
float progress = Math.clamp((animation.age() + partialTick) / animation.duration(), 0.0F, 1.0F);
|
||||||
|
float riseProgress = Math.min(1.0F, progress / 0.45F);
|
||||||
|
float arcProgress = progress <= 0.45F ? 0.0F : (progress - 0.45F) / 0.55F;
|
||||||
|
|
||||||
|
float x = animation.localX() + forwardX * arcProgress;
|
||||||
|
float y = REWARD_CARD_Y
|
||||||
|
+ riseProgress * 0.26F
|
||||||
|
+ (float) Math.sin(arcProgress * Math.PI) * 0.08F
|
||||||
|
- arcProgress * 0.26F;
|
||||||
|
float z = animation.localZ() + forwardZ * arcProgress;
|
||||||
|
float spin = progress * 720.0F;
|
||||||
|
|
||||||
|
poseStack.pushPose();
|
||||||
|
poseStack.translate(x, y, z);
|
||||||
|
poseStack.mulPose(Axis.YP.rotationDegrees(facing.toYRot()));
|
||||||
|
poseStack.mulPose(Axis.YP.rotationDegrees(spin));
|
||||||
|
poseStack.scale(0.3F, 0.3F, 0.3F);
|
||||||
|
TriadCardItemRenderer.renderCard(animation.stack(), poseStack, buffer, perspectivePalette(blockEntity, DuelTableBlockEntity.OWNER_SECOND));
|
||||||
|
poseStack.popPose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void renderRewardProxyDebug(DuelTableBlockEntity blockEntity, PoseStack poseStack, MultiBufferSource buffer) {
|
||||||
|
if (Minecraft.getInstance().level == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AABB searchBox = new AABB(blockEntity.getBlockPos()).inflate(2.0D, 2.0D, 2.0D);
|
||||||
|
for (Interaction interaction : Minecraft.getInstance().level.getEntitiesOfClass(
|
||||||
|
Interaction.class,
|
||||||
|
searchBox,
|
||||||
|
entity -> DuelTableBlockEntity.isRewardProxy(entity)
|
||||||
|
&& blockEntity.getBlockPos().equals(DuelTableBlockEntity.rewardProxyPos(entity)))) {
|
||||||
|
AABB box = interaction.getBoundingBox().move(
|
||||||
|
-blockEntity.getBlockPos().getX(),
|
||||||
|
-blockEntity.getBlockPos().getY(),
|
||||||
|
-blockEntity.getBlockPos().getZ());
|
||||||
|
LevelRenderer.addChainedFilledBoxVertices(
|
||||||
|
poseStack,
|
||||||
|
buffer.getBuffer(RenderType.debugFilledBox()),
|
||||||
|
(float) box.minX,
|
||||||
|
(float) box.minY,
|
||||||
|
(float) box.minZ,
|
||||||
|
(float) box.maxX,
|
||||||
|
(float) box.maxY,
|
||||||
|
(float) box.maxZ,
|
||||||
|
0.2F,
|
||||||
|
0.95F,
|
||||||
|
0.35F,
|
||||||
|
0.28F);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static TriadCardItemRenderer.CardPalette perspectivePalette(DuelTableBlockEntity blockEntity, int owner) {
|
private static TriadCardItemRenderer.CardPalette perspectivePalette(DuelTableBlockEntity blockEntity, int owner) {
|
||||||
|
|||||||
@@ -27,12 +27,15 @@ import org.joml.Matrix4f;
|
|||||||
public final class TriadCardItemRenderer extends BlockEntityWithoutLevelRenderer {
|
public final class TriadCardItemRenderer extends BlockEntityWithoutLevelRenderer {
|
||||||
private static final float CARD_MIN = -0.42F;
|
private static final float CARD_MIN = -0.42F;
|
||||||
private static final float CARD_MAX = 0.42F;
|
private static final float CARD_MAX = 0.42F;
|
||||||
private static final float FRONT_Z = 0.024F;
|
private static final float CARD_DEPTH = 0.024F;
|
||||||
private static final float BACK_Z = -0.024F;
|
private static final float FRONT_Z = CARD_DEPTH;
|
||||||
private static final float BORDER_Z = 0.026F;
|
private static final float BACK_Z = -CARD_DEPTH;
|
||||||
private static final float VALUE_Z = 0.030F;
|
private static final float ART_Z = FRONT_Z + 0.004F;
|
||||||
|
private static final float BORDER_Z = FRONT_Z + 0.008F;
|
||||||
|
private static final float VALUE_Z = FRONT_Z + 0.012F;
|
||||||
private static final float INNER_MIN = -0.37F;
|
private static final float INNER_MIN = -0.37F;
|
||||||
private static final float INNER_MAX = 0.37F;
|
private static final float INNER_MAX = 0.37F;
|
||||||
|
private static final float[] EDGE_COLOR = new float[] {0.18F, 0.14F, 0.10F};
|
||||||
private static TriadCardItemRenderer INSTANCE;
|
private static TriadCardItemRenderer INSTANCE;
|
||||||
private static final CardPalette NEUTRAL_PALETTE = new CardPalette(
|
private static final CardPalette NEUTRAL_PALETTE = new CardPalette(
|
||||||
new float[] {0.97F, 0.93F, 0.80F},
|
new float[] {0.97F, 0.93F, 0.80F},
|
||||||
@@ -77,6 +80,7 @@ public final class TriadCardItemRenderer extends BlockEntityWithoutLevelRenderer
|
|||||||
CardDefinition card = cardData == null || cardData.equals(CardStackData.EMPTY) ? null : CardRegistry.get(cardData.cardId());
|
CardDefinition card = cardData == null || cardData.equals(CardStackData.EMPTY) ? null : CardRegistry.get(cardData.cardId());
|
||||||
|
|
||||||
poseStack.pushPose();
|
poseStack.pushPose();
|
||||||
|
drawCardBody(poseStack, buffer);
|
||||||
drawBack(poseStack, buffer);
|
drawBack(poseStack, buffer);
|
||||||
if (card != null) {
|
if (card != null) {
|
||||||
drawFrontArt(poseStack, buffer, artTexture(card));
|
drawFrontArt(poseStack, buffer, artTexture(card));
|
||||||
@@ -94,6 +98,25 @@ public final class TriadCardItemRenderer extends BlockEntityWithoutLevelRenderer
|
|||||||
poseStack.popPose();
|
poseStack.popPose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void drawCardBody(PoseStack poseStack, MultiBufferSource buffer) {
|
||||||
|
float r = EDGE_COLOR[0];
|
||||||
|
float g = EDGE_COLOR[1];
|
||||||
|
float b = EDGE_COLOR[2];
|
||||||
|
LevelRenderer.addChainedFilledBoxVertices(
|
||||||
|
poseStack,
|
||||||
|
buffer.getBuffer(RenderType.debugFilledBox()),
|
||||||
|
CARD_MIN,
|
||||||
|
CARD_MIN,
|
||||||
|
BACK_Z,
|
||||||
|
CARD_MAX,
|
||||||
|
CARD_MAX,
|
||||||
|
FRONT_Z,
|
||||||
|
r,
|
||||||
|
g,
|
||||||
|
b,
|
||||||
|
1.0F);
|
||||||
|
}
|
||||||
|
|
||||||
private static String displayRank(int rank) {
|
private static String displayRank(int rank) {
|
||||||
return rank == 10 ? "A" : Integer.toString(rank);
|
return rank == 10 ? "A" : Integer.toString(rank);
|
||||||
}
|
}
|
||||||
@@ -161,7 +184,7 @@ public final class TriadCardItemRenderer extends BlockEntityWithoutLevelRenderer
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void drawFrontArt(PoseStack poseStack, MultiBufferSource buffer, ResourceLocation textureLocation) {
|
private static void drawFrontArt(PoseStack poseStack, MultiBufferSource buffer, ResourceLocation textureLocation) {
|
||||||
drawTexturedQuad(poseStack, buffer, textureLocation, FRONT_Z, false);
|
drawTexturedQuad(poseStack, buffer, textureLocation, ART_Z, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void drawTexturedQuad(PoseStack poseStack, MultiBufferSource buffer, ResourceLocation textureLocation, float z, boolean reverseWinding) {
|
private static void drawTexturedQuad(PoseStack poseStack, MultiBufferSource buffer, ResourceLocation textureLocation, float z, boolean reverseWinding) {
|
||||||
|
|||||||
@@ -36,6 +36,44 @@ public final class BoardLocalSpace {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static SlotCenter rewardCenter(int index, Direction boardFacing, int count) {
|
||||||
|
if (count <= 0) {
|
||||||
|
return new SlotCenter(0.5F, 0.22F);
|
||||||
|
}
|
||||||
|
float columnProgress = count == 1 ? 0.5F : -0.04F + (1.08F * index / (count - 1.0F));
|
||||||
|
float rowProgress = 0.15F;
|
||||||
|
HorizontalVector bottomVector = vectorFor(boardFacing);
|
||||||
|
HorizontalVector rightVector = vectorFor(rightDirection(boardFacing));
|
||||||
|
float x = 0.5F + bottomVector.x() * (rowProgress - 0.5F) + rightVector.x() * (columnProgress - 0.5F);
|
||||||
|
float z = 0.5F + bottomVector.z() * (rowProgress - 0.5F) + rightVector.z() * (columnProgress - 0.5F);
|
||||||
|
return new SlotCenter(x, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int rewardIndexForHit(BlockHitResult hitResult, Direction boardFacing, int count) {
|
||||||
|
double localX = hitResult.getLocation().x - hitResult.getBlockPos().getX();
|
||||||
|
double localY = hitResult.getLocation().y - hitResult.getBlockPos().getY();
|
||||||
|
double localZ = hitResult.getLocation().z - hitResult.getBlockPos().getZ();
|
||||||
|
if (localY < 1.20D || localY > 2.02D) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
int nearestIndex = -1;
|
||||||
|
double nearestDistance = Double.MAX_VALUE;
|
||||||
|
for (int index = 0; index < count; index++) {
|
||||||
|
SlotCenter center = rewardCenter(index, boardFacing, count);
|
||||||
|
double dx = localX - center.x();
|
||||||
|
double dz = localZ - center.z();
|
||||||
|
if (Math.abs(dx) <= 0.12D && Math.abs(dz) <= 0.18D) {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
double distanceSq = dx * dx + dz * dz;
|
||||||
|
if (distanceSq < nearestDistance) {
|
||||||
|
nearestDistance = distanceSq;
|
||||||
|
nearestIndex = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nearestDistance <= 0.05D * 0.05D ? nearestIndex : -1;
|
||||||
|
}
|
||||||
|
|
||||||
private static double projectProgress(double localX, double localZ, Direction direction) {
|
private static double projectProgress(double localX, double localZ, Direction direction) {
|
||||||
HorizontalVector vector = vectorFor(direction);
|
HorizontalVector vector = vectorFor(direction);
|
||||||
double offsetX = localX - 0.5D;
|
double offsetX = localX - 0.5D;
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ import net.minecraft.world.phys.BlockHitResult;
|
|||||||
public final class DuelSession {
|
public final class DuelSession {
|
||||||
private enum Phase {
|
private enum Phase {
|
||||||
PLAYING,
|
PLAYING,
|
||||||
|
PLAYER_ANIMATION,
|
||||||
|
OPPONENT_ANIMATION,
|
||||||
|
CLOSING_ANIMATION,
|
||||||
OVERVIEW
|
OVERVIEW
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,7 +28,9 @@ public final class DuelSession {
|
|||||||
private final MatchParticipant opponentParticipant;
|
private final MatchParticipant opponentParticipant;
|
||||||
private final TriadMatch match;
|
private final TriadMatch match;
|
||||||
private final List<ResourceLocation> refundablePlayerCards;
|
private final List<ResourceLocation> refundablePlayerCards;
|
||||||
|
private final List<ResourceLocation> opponentStartingCards;
|
||||||
private final boolean refundCardsOnEnd;
|
private final boolean refundCardsOnEnd;
|
||||||
|
private final boolean aiVsAi;
|
||||||
private final BlockPos tablePos;
|
private final BlockPos tablePos;
|
||||||
private Phase phase;
|
private Phase phase;
|
||||||
|
|
||||||
@@ -36,12 +41,17 @@ public final class DuelSession {
|
|||||||
List<GameCard> opponentHand,
|
List<GameCard> opponentHand,
|
||||||
List<ResourceLocation> refundablePlayerCards,
|
List<ResourceLocation> refundablePlayerCards,
|
||||||
boolean refundCardsOnEnd,
|
boolean refundCardsOnEnd,
|
||||||
|
boolean aiVsAi,
|
||||||
BlockPos tablePos) {
|
BlockPos tablePos) {
|
||||||
this.playerParticipant = new MatchParticipant(playerId, playerName);
|
this.playerParticipant = new MatchParticipant(playerId, playerName);
|
||||||
this.opponentParticipant = new MatchParticipant(UUID.nameUUIDFromBytes(("opponent:" + playerId).getBytes()), "Training Duelist");
|
this.opponentParticipant = new MatchParticipant(UUID.nameUUIDFromBytes(("opponent:" + playerId).getBytes()), "Training Duelist");
|
||||||
this.match = new TriadMatch(playerParticipant, opponentParticipant, playerHand, opponentHand, TriadRuleSet.CLASSIC_OPEN);
|
this.match = new TriadMatch(playerParticipant, opponentParticipant, playerHand, opponentHand, TriadRuleSet.CLASSIC_OPEN);
|
||||||
this.refundablePlayerCards = List.copyOf(refundablePlayerCards);
|
this.refundablePlayerCards = List.copyOf(refundablePlayerCards);
|
||||||
|
this.opponentStartingCards = opponentHand.stream()
|
||||||
|
.map(card -> card.definition().id())
|
||||||
|
.toList();
|
||||||
this.refundCardsOnEnd = refundCardsOnEnd;
|
this.refundCardsOnEnd = refundCardsOnEnd;
|
||||||
|
this.aiVsAi = aiVsAi;
|
||||||
this.tablePos = tablePos.immutable();
|
this.tablePos = tablePos.immutable();
|
||||||
this.phase = Phase.PLAYING;
|
this.phase = Phase.PLAYING;
|
||||||
}
|
}
|
||||||
@@ -62,6 +72,114 @@ public final class DuelSession {
|
|||||||
return phase == Phase.OVERVIEW;
|
return phase == Phase.OVERVIEW;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isAnimating() {
|
||||||
|
return phase == Phase.PLAYER_ANIMATION || phase == Phase.OPPONENT_ANIMATION;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAwaitingPlayerAnimation() {
|
||||||
|
return phase == Phase.PLAYER_ANIMATION;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAwaitingOpponentAnimation() {
|
||||||
|
return phase == Phase.OPPONENT_ANIMATION;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void beginPlayerAnimation() {
|
||||||
|
phase = Phase.PLAYER_ANIMATION;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void beginOpponentAnimation() {
|
||||||
|
phase = Phase.OPPONENT_ANIMATION;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void returnToPlaying() {
|
||||||
|
phase = Phase.PLAYING;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void beginClosingAnimation() {
|
||||||
|
phase = Phase.CLOSING_ANIMATION;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isClosingAnimation() {
|
||||||
|
return phase == Phase.CLOSING_ANIMATION;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAiVsAi() {
|
||||||
|
return aiVsAi;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int playerHandCount() {
|
||||||
|
return match.handFor(playerParticipant).size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int opponentHandCount() {
|
||||||
|
return match.handFor(opponentParticipant).size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int playerScore() {
|
||||||
|
return match.scoreFor(playerParticipant);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int opponentScore() {
|
||||||
|
return match.scoreFor(opponentParticipant);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String rulesSummary() {
|
||||||
|
List<String> rules = new ArrayList<>();
|
||||||
|
if (match.ruleSet().sameRule()) {
|
||||||
|
rules.add("Same");
|
||||||
|
}
|
||||||
|
if (match.ruleSet().sameWallRule()) {
|
||||||
|
rules.add("Same Wall");
|
||||||
|
}
|
||||||
|
if (match.ruleSet().plusRule()) {
|
||||||
|
rules.add("Plus");
|
||||||
|
}
|
||||||
|
if (match.ruleSet().openHands()) {
|
||||||
|
rules.add("Open");
|
||||||
|
}
|
||||||
|
if (rules.isEmpty()) {
|
||||||
|
return "None";
|
||||||
|
}
|
||||||
|
return String.join(", ", rules);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String turnIndicator() {
|
||||||
|
return isPlayerTurn() ? "P1" : "P2";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String gameStateLabel() {
|
||||||
|
return switch (phase) {
|
||||||
|
case PLAYING -> "Playing";
|
||||||
|
case PLAYER_ANIMATION, OPPONENT_ANIMATION -> "Animating";
|
||||||
|
case CLOSING_ANIMATION -> "Closing";
|
||||||
|
case OVERVIEW -> "Overview";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public String nextStepInstruction() {
|
||||||
|
if (isComplete()) {
|
||||||
|
int playerScore = playerScore();
|
||||||
|
int opponentScore = opponentScore();
|
||||||
|
String winner = playerScore == opponentScore ? "Draw" : playerScore > opponentScore ? "P1" : "P2";
|
||||||
|
if (isInOverview()) {
|
||||||
|
return opponentRewardCards().isEmpty()
|
||||||
|
? "Ready for next game"
|
||||||
|
: winner + " choosing 1 cards to keep";
|
||||||
|
}
|
||||||
|
return "Game over - " + winner + (winner.equals("Draw") ? "" : " wins");
|
||||||
|
}
|
||||||
|
|
||||||
|
return switch (phase) {
|
||||||
|
case PLAYER_ANIMATION, OPPONENT_ANIMATION -> "Resolving animations";
|
||||||
|
case CLOSING_ANIMATION -> "Clearing board";
|
||||||
|
case OVERVIEW -> "Choose a reward card or finish";
|
||||||
|
case PLAYING -> isPlayerTurn()
|
||||||
|
? "Waiting for you to place a card"
|
||||||
|
: "Waiting for P2 to place a card";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public void enterOverview() {
|
public void enterOverview() {
|
||||||
phase = Phase.OVERVIEW;
|
phase = Phase.OVERVIEW;
|
||||||
}
|
}
|
||||||
@@ -96,15 +214,23 @@ public final class DuelSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public MoveResult playOpponentTurn() {
|
public MoveResult playOpponentTurn() {
|
||||||
|
return playAiTurnFor(opponentParticipant);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MoveResult playAiTurn() {
|
||||||
|
return playAiTurnFor(match.activeParticipant());
|
||||||
|
}
|
||||||
|
|
||||||
|
private MoveResult playAiTurnFor(MatchParticipant participant) {
|
||||||
int bestHandIndex = -1;
|
int bestHandIndex = -1;
|
||||||
BoardCell bestCell = null;
|
BoardCell bestCell = null;
|
||||||
int bestCaptures = -1;
|
int bestCaptures = -1;
|
||||||
|
|
||||||
List<GameCard> opponentHand = match.handFor(opponentParticipant);
|
List<GameCard> hand = match.handFor(participant);
|
||||||
for (int handIndex = 0; handIndex < opponentHand.size(); handIndex++) {
|
for (int handIndex = 0; handIndex < hand.size(); handIndex++) {
|
||||||
for (BoardCell cell : openCells()) {
|
for (BoardCell cell : openCells()) {
|
||||||
TriadMatch probe = duplicateMatch();
|
TriadMatch probe = duplicateMatch();
|
||||||
MoveResult result = probe.play(new TriadMove(opponentParticipant, handIndex, cell));
|
MoveResult result = probe.play(new TriadMove(participant, handIndex, cell));
|
||||||
if (!result.valid()) {
|
if (!result.valid()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -121,7 +247,7 @@ public final class DuelSession {
|
|||||||
return MoveResult.failure("Opponent could not find a legal move");
|
return MoveResult.failure("Opponent could not find a legal move");
|
||||||
}
|
}
|
||||||
|
|
||||||
return match.play(new TriadMove(opponentParticipant, bestHandIndex, bestCell));
|
return match.play(new TriadMove(participant, bestHandIndex, bestCell));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Component boardSummary() {
|
public Component boardSummary() {
|
||||||
@@ -160,7 +286,7 @@ public final class DuelSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Component overviewMessage() {
|
public Component overviewMessage() {
|
||||||
return Component.literal("Duel overview. Right-click the table to finish and return cards.");
|
return Component.literal("Duel overview. Right-click a floating red card to take it, or right-click the table to finish.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<ResourceLocation> refundablePlayerCards() {
|
public List<ResourceLocation> refundablePlayerCards() {
|
||||||
@@ -195,6 +321,10 @@ public final class DuelSession {
|
|||||||
return refundCardsOnEnd;
|
return refundCardsOnEnd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<ResourceLocation> opponentRewardCards() {
|
||||||
|
return List.copyOf(opponentStartingCards);
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isAtTable(BlockPos blockPos) {
|
public boolean isAtTable(BlockPos blockPos) {
|
||||||
return tablePos.equals(blockPos);
|
return tablePos.equals(blockPos);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
import com.trunksbomb.minetriad.card.CardRegistry;
|
import com.trunksbomb.minetriad.card.CardRegistry;
|
||||||
import com.trunksbomb.minetriad.card.CardDefinition;
|
import com.trunksbomb.minetriad.card.CardDefinition;
|
||||||
@@ -29,12 +30,27 @@ public final class DuelSessionManager {
|
|||||||
return ACTIVE_DUELS.get(player.getUUID());
|
return ACTIVE_DUELS.get(player.getUUID());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static DuelSession getAt(BlockPos tablePos) {
|
||||||
|
return ACTIVE_DUELS.values().stream()
|
||||||
|
.filter(session -> session.isAtTable(tablePos))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
public static DuelSession start(Player player, BlockPos tablePos) {
|
public static DuelSession start(Player player, BlockPos tablePos) {
|
||||||
|
return start(player, tablePos, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DuelSession start(Player player, BlockPos tablePos, boolean aiVsAi) {
|
||||||
List<GameCard> playerHand = buildPlayerHand(player.getInventory());
|
List<GameCard> playerHand = buildPlayerHand(player.getInventory());
|
||||||
if (playerHand.size() < 5) {
|
if (!aiVsAi && playerHand.size() < 5) {
|
||||||
throw new IllegalStateException("A duel requires 5 Triad Cards in your inventory");
|
throw new IllegalStateException("A duel requires 5 Triad Cards in your inventory");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (aiVsAi) {
|
||||||
|
playerHand = buildRandomHand();
|
||||||
|
}
|
||||||
|
|
||||||
List<ResourceLocation> refundableCards = playerHand.stream()
|
List<ResourceLocation> refundableCards = playerHand.stream()
|
||||||
.map(card -> card.definition().id())
|
.map(card -> card.definition().id())
|
||||||
.toList();
|
.toList();
|
||||||
@@ -42,9 +58,10 @@ public final class DuelSessionManager {
|
|||||||
player.getUUID(),
|
player.getUUID(),
|
||||||
player.getName().getString(),
|
player.getName().getString(),
|
||||||
playerHand,
|
playerHand,
|
||||||
buildOpponentHand(),
|
aiVsAi ? buildRandomHand() : buildOpponentHand(),
|
||||||
refundableCards,
|
refundableCards,
|
||||||
!player.getAbilities().instabuild,
|
!aiVsAi && !player.getAbilities().instabuild,
|
||||||
|
aiVsAi,
|
||||||
tablePos);
|
tablePos);
|
||||||
ACTIVE_DUELS.put(player.getUUID(), session);
|
ACTIVE_DUELS.put(player.getUUID(), session);
|
||||||
return session;
|
return session;
|
||||||
@@ -109,4 +126,13 @@ public final class DuelSessionManager {
|
|||||||
.map(GameCard::new)
|
.map(GameCard::new)
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static List<GameCard> buildRandomHand() {
|
||||||
|
List<CardDefinition> cards = new ArrayList<>(CardRegistry.all());
|
||||||
|
Collections.shuffle(cards);
|
||||||
|
return cards.stream()
|
||||||
|
.limit(5)
|
||||||
|
.map(GameCard::new)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,21 +7,29 @@ import net.minecraft.resources.ResourceLocation;
|
|||||||
public record MoveResult(
|
public record MoveResult(
|
||||||
boolean valid,
|
boolean valid,
|
||||||
String errorMessage,
|
String errorMessage,
|
||||||
|
List<List<BoardCell>> captureWaves,
|
||||||
List<BoardCell> capturedCells,
|
List<BoardCell> capturedCells,
|
||||||
List<String> battleLog,
|
List<String> battleLog,
|
||||||
String playedCardName,
|
String playedCardName,
|
||||||
ResourceLocation playedCardId,
|
ResourceLocation playedCardId,
|
||||||
BoardCell playedCell) {
|
BoardCell playedCell) {
|
||||||
public static MoveResult success(
|
public static MoveResult success(
|
||||||
List<BoardCell> capturedCells,
|
List<List<BoardCell>> captureWaves,
|
||||||
List<String> battleLog,
|
List<String> battleLog,
|
||||||
String playedCardName,
|
String playedCardName,
|
||||||
ResourceLocation playedCardId,
|
ResourceLocation playedCardId,
|
||||||
BoardCell playedCell) {
|
BoardCell playedCell) {
|
||||||
return new MoveResult(true, "", List.copyOf(capturedCells), List.copyOf(battleLog), playedCardName, playedCardId, playedCell);
|
List<List<BoardCell>> immutableWaves = captureWaves.stream()
|
||||||
|
.map(List::copyOf)
|
||||||
|
.toList();
|
||||||
|
List<BoardCell> flattenedCaptures = immutableWaves.stream()
|
||||||
|
.flatMap(List::stream)
|
||||||
|
.distinct()
|
||||||
|
.toList();
|
||||||
|
return new MoveResult(true, "", immutableWaves, flattenedCaptures, List.copyOf(battleLog), playedCardName, playedCardId, playedCell);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static MoveResult failure(String errorMessage) {
|
public static MoveResult failure(String errorMessage) {
|
||||||
return new MoveResult(false, errorMessage, List.of(), List.of(), "", null, null);
|
return new MoveResult(false, errorMessage, List.of(), List.of(), List.of(), "", null, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,11 @@ package com.trunksbomb.minetriad.game;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
public final class TriadMatch {
|
public final class TriadMatch {
|
||||||
private final MatchParticipant firstParticipant;
|
private final MatchParticipant firstParticipant;
|
||||||
@@ -75,14 +79,111 @@ public final class TriadMatch {
|
|||||||
board[move.targetCell().index()] = new PlacedCard(move.participant(), playedCard);
|
board[move.targetCell().index()] = new PlacedCard(move.participant(), playedCard);
|
||||||
|
|
||||||
List<String> battleLog = new ArrayList<>();
|
List<String> battleLog = new ArrayList<>();
|
||||||
List<BoardCell> capturedCells = resolveCaptures(move.targetCell(), move.participant(), playedCard, battleLog);
|
CaptureResolution captureResolution = resolveCaptures(move.targetCell(), move.participant(), playedCard, battleLog);
|
||||||
activeParticipant = otherParticipant(move.participant());
|
activeParticipant = otherParticipant(move.participant());
|
||||||
return MoveResult.success(capturedCells, battleLog, playedCard.definition().name().getString(), playedCard.definition().id(), move.targetCell());
|
return MoveResult.success(captureResolution.captureWaves(), battleLog, playedCard.definition().name().getString(), playedCard.definition().id(), move.targetCell());
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<BoardCell> resolveCaptures(BoardCell origin, MatchParticipant owner, GameCard playedCard, List<String> battleLog) {
|
private CaptureResolution resolveCaptures(BoardCell origin, MatchParticipant owner, GameCard playedCard, List<String> battleLog) {
|
||||||
List<BoardCell> captured = new ArrayList<>();
|
List<SideComparison> comparisons = buildComparisons(origin, owner, playedCard, battleLog);
|
||||||
|
LinkedHashSet<BoardCell> firstWave = new LinkedHashSet<>();
|
||||||
|
LinkedHashSet<BoardCell> comboSeeds = new LinkedHashSet<>();
|
||||||
|
|
||||||
|
for (SideComparison comparison : comparisons) {
|
||||||
|
if (!comparison.neighbor().owner().equals(owner) && comparison.attackValue() > comparison.defendValue()) {
|
||||||
|
firstWave.add(comparison.cell());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ruleSet.sameRule()) {
|
||||||
|
List<SameCandidate> sameCandidates = buildSameCandidates(origin, playedCard, battleLog);
|
||||||
|
long sameMatches = sameCandidates.stream().filter(SameCandidate::matches).count();
|
||||||
|
if (sameMatches >= 2) {
|
||||||
|
List<BoardCell> sameCaptures = sameCandidates.stream()
|
||||||
|
.filter(SameCandidate::matches)
|
||||||
|
.map(SameCandidate::cell)
|
||||||
|
.filter(cell -> cell != null)
|
||||||
|
.distinct()
|
||||||
|
.filter(cell -> {
|
||||||
|
PlacedCard placedCard = cardAt(cell);
|
||||||
|
return placedCard != null && !placedCard.owner().equals(owner);
|
||||||
|
})
|
||||||
|
.toList();
|
||||||
|
if (!sameCaptures.isEmpty()) {
|
||||||
|
battleLog.add("Same triggers.");
|
||||||
|
firstWave.addAll(sameCaptures);
|
||||||
|
comboSeeds.addAll(sameCaptures);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ruleSet.plusRule()) {
|
||||||
|
Map<Integer, List<SideComparison>> plusGroups = new LinkedHashMap<>();
|
||||||
|
for (SideComparison comparison : comparisons) {
|
||||||
|
int sum = comparison.attackValue() + comparison.defendValue();
|
||||||
|
plusGroups.computeIfAbsent(sum, ignored -> new ArrayList<>()).add(comparison);
|
||||||
|
}
|
||||||
|
for (Map.Entry<Integer, List<SideComparison>> entry : plusGroups.entrySet()) {
|
||||||
|
if (entry.getValue().size() < 2) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
List<BoardCell> plusCaptures = entry.getValue().stream()
|
||||||
|
.map(SideComparison::cell)
|
||||||
|
.distinct()
|
||||||
|
.filter(cell -> {
|
||||||
|
PlacedCard placedCard = cardAt(cell);
|
||||||
|
return placedCard != null && !placedCard.owner().equals(owner);
|
||||||
|
})
|
||||||
|
.toList();
|
||||||
|
if (!plusCaptures.isEmpty()) {
|
||||||
|
battleLog.add("Plus triggers on sum " + entry.getKey() + ".");
|
||||||
|
firstWave.addAll(plusCaptures);
|
||||||
|
comboSeeds.addAll(plusCaptures);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<List<BoardCell>> waves = new ArrayList<>();
|
||||||
|
if (!firstWave.isEmpty()) {
|
||||||
|
applyOwnership(owner, firstWave);
|
||||||
|
waves.add(List.copyOf(firstWave));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!comboSeeds.isEmpty()) {
|
||||||
|
LinkedHashSet<BoardCell> comboWave = new LinkedHashSet<>();
|
||||||
|
for (BoardCell comboCell : comboSeeds) {
|
||||||
|
PlacedCard comboCard = cardAt(comboCell);
|
||||||
|
if (comboCard == null || !comboCard.owner().equals(owner)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (CardSide side : CardSide.values()) {
|
||||||
|
BoardCell neighborCell = neighbor(comboCell, side);
|
||||||
|
if (neighborCell == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
PlacedCard neighbor = cardAt(neighborCell);
|
||||||
|
if (neighbor == null || neighbor.owner().equals(owner)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int attackValue = comboCard.card().value(side);
|
||||||
|
int defendValue = neighbor.card().value(side.opposite());
|
||||||
|
if (attackValue > defendValue) {
|
||||||
|
comboWave.add(neighborCell);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!comboWave.isEmpty()) {
|
||||||
|
battleLog.add("Combo triggers.");
|
||||||
|
applyOwnership(owner, comboWave);
|
||||||
|
waves.add(List.copyOf(comboWave));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new CaptureResolution(waves);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<SideComparison> buildComparisons(BoardCell origin, MatchParticipant owner, GameCard playedCard, List<String> battleLog) {
|
||||||
|
List<SideComparison> comparisons = new ArrayList<>();
|
||||||
for (CardSide side : CardSide.values()) {
|
for (CardSide side : CardSide.values()) {
|
||||||
BoardCell neighborCell = neighbor(origin, side);
|
BoardCell neighborCell = neighbor(origin, side);
|
||||||
if (neighborCell == null) {
|
if (neighborCell == null) {
|
||||||
@@ -90,13 +191,12 @@ public final class TriadMatch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
PlacedCard neighbor = cardAt(neighborCell);
|
PlacedCard neighbor = cardAt(neighborCell);
|
||||||
if (neighbor == null || neighbor.owner().equals(owner)) {
|
if (neighbor == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int attackValue = playedCard.value(side);
|
int attackValue = playedCard.value(side);
|
||||||
int defendValue = neighbor.card().value(side.opposite());
|
int defendValue = neighbor.card().value(side.opposite());
|
||||||
boolean capturedNeighbor = attackValue > defendValue;
|
|
||||||
battleLog.add(String.format(
|
battleLog.add(String.format(
|
||||||
"%s %s=%d vs %s %s=%d at [%d,%d] -> %s",
|
"%s %s=%d vs %s %s=%d at [%d,%d] -> %s",
|
||||||
playedCard.definition().name().getString(),
|
playedCard.definition().name().getString(),
|
||||||
@@ -107,14 +207,47 @@ public final class TriadMatch {
|
|||||||
defendValue,
|
defendValue,
|
||||||
neighborCell.row(),
|
neighborCell.row(),
|
||||||
neighborCell.column(),
|
neighborCell.column(),
|
||||||
capturedNeighbor ? "flip" : "hold"));
|
!neighbor.owner().equals(owner) && attackValue > defendValue ? "flip" : "hold"));
|
||||||
if (capturedNeighbor) {
|
comparisons.add(new SideComparison(side, neighborCell, neighbor, attackValue, defendValue));
|
||||||
board[neighborCell.index()] = new PlacedCard(owner, neighbor.card());
|
|
||||||
captured.add(neighborCell);
|
|
||||||
}
|
}
|
||||||
|
return comparisons;
|
||||||
}
|
}
|
||||||
|
|
||||||
return captured;
|
private List<SameCandidate> buildSameCandidates(BoardCell origin, GameCard playedCard, List<String> battleLog) {
|
||||||
|
List<SameCandidate> candidates = new ArrayList<>();
|
||||||
|
for (CardSide side : CardSide.values()) {
|
||||||
|
BoardCell neighborCell = neighbor(origin, side);
|
||||||
|
if (neighborCell == null) {
|
||||||
|
boolean wallMatch = ruleSet.sameWallRule() && playedCard.value(side) == 10;
|
||||||
|
battleLog.add(String.format(
|
||||||
|
"%s %s=%d vs WALL=%d -> %s",
|
||||||
|
playedCard.definition().name().getString(),
|
||||||
|
side.name(),
|
||||||
|
playedCard.value(side),
|
||||||
|
10,
|
||||||
|
wallMatch ? "same" : "ignore"));
|
||||||
|
candidates.add(new SameCandidate(side, null, wallMatch));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlacedCard neighbor = cardAt(neighborCell);
|
||||||
|
if (neighbor == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean sameMatch = playedCard.value(side) == neighbor.card().value(side.opposite());
|
||||||
|
candidates.add(new SameCandidate(side, neighborCell, sameMatch));
|
||||||
|
}
|
||||||
|
return candidates;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyOwnership(MatchParticipant owner, Set<BoardCell> capturedCells) {
|
||||||
|
for (BoardCell cell : capturedCells) {
|
||||||
|
PlacedCard placedCard = cardAt(cell);
|
||||||
|
if (placedCard != null) {
|
||||||
|
board[cell.index()] = new PlacedCard(owner, placedCard.card());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private BoardCell neighbor(BoardCell cell, CardSide side) {
|
private BoardCell neighbor(BoardCell cell, CardSide side) {
|
||||||
@@ -167,4 +300,13 @@ public final class TriadMatch {
|
|||||||
void forceActiveParticipant(MatchParticipant participant) {
|
void forceActiveParticipant(MatchParticipant participant) {
|
||||||
this.activeParticipant = participant;
|
this.activeParticipant = participant;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private record SideComparison(CardSide side, BoardCell cell, PlacedCard neighbor, int attackValue, int defendValue) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private record SameCandidate(CardSide side, BoardCell cell, boolean matches) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private record CaptureResolution(List<List<BoardCell>> captureWaves) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ package com.trunksbomb.minetriad.game;
|
|||||||
public record TriadRuleSet(
|
public record TriadRuleSet(
|
||||||
boolean openHands,
|
boolean openHands,
|
||||||
boolean sameRule,
|
boolean sameRule,
|
||||||
|
boolean sameWallRule,
|
||||||
boolean plusRule,
|
boolean plusRule,
|
||||||
boolean elementalRule) {
|
boolean elementalRule) {
|
||||||
public static final TriadRuleSet CLASSIC_OPEN = new TriadRuleSet(true, false, false, false);
|
public static final TriadRuleSet CLASSIC_OPEN = new TriadRuleSet(true, true, true, true, false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ package com.trunksbomb.minetriad.world;
|
|||||||
|
|
||||||
import com.mojang.serialization.MapCodec;
|
import com.mojang.serialization.MapCodec;
|
||||||
import com.trunksbomb.minetriad.MineTriad;
|
import com.trunksbomb.minetriad.MineTriad;
|
||||||
|
import com.trunksbomb.minetriad.blockentity.DuelTableAnimationResolver;
|
||||||
import com.trunksbomb.minetriad.blockentity.DuelTableBlockEntity;
|
import com.trunksbomb.minetriad.blockentity.DuelTableBlockEntity;
|
||||||
import com.trunksbomb.minetriad.card.CardStackData;
|
import com.trunksbomb.minetriad.card.CardStackData;
|
||||||
|
import com.trunksbomb.minetriad.game.BoardLocalSpace;
|
||||||
import com.trunksbomb.minetriad.game.DuelSession;
|
import com.trunksbomb.minetriad.game.DuelSession;
|
||||||
import com.trunksbomb.minetriad.game.DuelSessionManager;
|
import com.trunksbomb.minetriad.game.DuelSessionManager;
|
||||||
import com.trunksbomb.minetriad.game.MoveResult;
|
import com.trunksbomb.minetriad.game.MoveResult;
|
||||||
@@ -19,18 +21,28 @@ import net.minecraft.world.InteractionHand;
|
|||||||
import net.minecraft.world.InteractionResult;
|
import net.minecraft.world.InteractionResult;
|
||||||
import net.minecraft.world.ItemInteractionResult;
|
import net.minecraft.world.ItemInteractionResult;
|
||||||
import net.minecraft.world.entity.player.Player;
|
import net.minecraft.world.entity.player.Player;
|
||||||
|
import net.minecraft.world.entity.Interaction;
|
||||||
import net.minecraft.world.item.ItemStack;
|
import net.minecraft.world.item.ItemStack;
|
||||||
import net.minecraft.world.level.Level;
|
import net.minecraft.world.level.Level;
|
||||||
|
import net.minecraft.world.level.BlockGetter;
|
||||||
import net.minecraft.world.level.block.BaseEntityBlock;
|
import net.minecraft.world.level.block.BaseEntityBlock;
|
||||||
import net.minecraft.world.level.block.HorizontalDirectionalBlock;
|
import net.minecraft.world.level.block.HorizontalDirectionalBlock;
|
||||||
import net.minecraft.world.level.block.RenderShape;
|
import net.minecraft.world.level.block.RenderShape;
|
||||||
import net.minecraft.world.level.block.Block;
|
import net.minecraft.world.level.block.Block;
|
||||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||||
|
import net.minecraft.world.level.block.entity.BlockEntityTicker;
|
||||||
|
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||||
import net.minecraft.world.level.block.state.StateDefinition;
|
import net.minecraft.world.level.block.state.StateDefinition;
|
||||||
import net.minecraft.world.level.block.state.BlockState;
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
|
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
|
||||||
import net.minecraft.world.level.block.state.properties.DirectionProperty;
|
import net.minecraft.world.level.block.state.properties.DirectionProperty;
|
||||||
import net.minecraft.world.phys.BlockHitResult;
|
import net.minecraft.world.phys.BlockHitResult;
|
||||||
|
import net.minecraft.world.phys.shapes.CollisionContext;
|
||||||
|
import net.minecraft.world.phys.shapes.Shapes;
|
||||||
|
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||||
|
import net.neoforged.bus.api.SubscribeEvent;
|
||||||
|
import net.neoforged.fml.common.EventBusSubscriber;
|
||||||
|
import net.neoforged.neoforge.event.entity.player.PlayerInteractEvent;
|
||||||
|
|
||||||
public class DuelTableBlock extends BaseEntityBlock {
|
public class DuelTableBlock extends BaseEntityBlock {
|
||||||
public static final MapCodec<DuelTableBlock> CODEC = simpleCodec(DuelTableBlock::new);
|
public static final MapCodec<DuelTableBlock> CODEC = simpleCodec(DuelTableBlock::new);
|
||||||
@@ -53,57 +65,60 @@ public class DuelTableBlock extends BaseEntityBlock {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (hitResult.getDirection() != Direction.UP) {
|
|
||||||
player.displayClientMessage(Component.literal("Click the top face of the Duel Table to target a board space.").withStyle(ChatFormatting.YELLOW), false);
|
|
||||||
return ItemInteractionResult.CONSUME;
|
|
||||||
}
|
|
||||||
Direction tableFacing = state.getValue(FACING);
|
|
||||||
|
|
||||||
DuelTableBlockEntity table = getTableEntity(level, pos);
|
DuelTableBlockEntity table = getTableEntity(level, pos);
|
||||||
if (table == null) {
|
if (table == null) {
|
||||||
player.displayClientMessage(Component.literal("Duel table storage is unavailable.").withStyle(ChatFormatting.RED), false);
|
player.displayClientMessage(Component.literal("Duel table storage is unavailable.").withStyle(ChatFormatting.RED), false);
|
||||||
return ItemInteractionResult.CONSUME;
|
return ItemInteractionResult.CONSUME;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Direction tableFacing = state.getValue(FACING);
|
||||||
|
|
||||||
DuelSession session = DuelSessionManager.get(player);
|
DuelSession session = DuelSessionManager.get(player);
|
||||||
|
if (session != null && session.isInOverview()) {
|
||||||
|
if (tryTakeRewardCard(level, pos, player, hitResult, tableFacing)) {
|
||||||
|
return ItemInteractionResult.CONSUME;
|
||||||
|
}
|
||||||
|
if (hitResult.getDirection() != Direction.UP) {
|
||||||
|
player.displayClientMessage(Component.literal("Reward debug: no reward hit; clicked face " + hitResult.getDirection().getName()).withStyle(ChatFormatting.DARK_GRAY), false);
|
||||||
|
return ItemInteractionResult.CONSUME;
|
||||||
|
}
|
||||||
|
player.displayClientMessage(Component.literal("Reward debug: no reward hit; falling back to board close.").withStyle(ChatFormatting.DARK_GRAY), false);
|
||||||
|
completeOverview(level, pos, player, session);
|
||||||
|
return ItemInteractionResult.CONSUME;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hitResult.getDirection() != Direction.UP) {
|
||||||
|
player.displayClientMessage(Component.literal("Click the top face of the Duel Table to target a board space.").withStyle(ChatFormatting.YELLOW), false);
|
||||||
|
return ItemInteractionResult.CONSUME;
|
||||||
|
}
|
||||||
|
|
||||||
if (session == null) {
|
if (session == null) {
|
||||||
|
boolean aiVsAi = stack.isEmpty();
|
||||||
try {
|
try {
|
||||||
session = DuelSessionManager.start(player, pos);
|
session = DuelSessionManager.start(player, pos, aiVsAi);
|
||||||
} catch (IllegalStateException exception) {
|
} catch (IllegalStateException exception) {
|
||||||
player.displayClientMessage(Component.literal("You need 5 Triad Cards in your inventory to start a duel.").withStyle(ChatFormatting.YELLOW), false);
|
player.displayClientMessage(Component.literal("You need 5 Triad Cards in your inventory to start a duel.").withStyle(ChatFormatting.YELLOW), false);
|
||||||
return ItemInteractionResult.CONSUME;
|
return ItemInteractionResult.CONSUME;
|
||||||
}
|
}
|
||||||
table.clearBoard();
|
table.clearBoard();
|
||||||
table.setParticipants(session.playerParticipantId(), session.opponentParticipantId());
|
table.setParticipants(session.playerParticipantId(), session.opponentParticipantId());
|
||||||
|
table.updateHudState(session);
|
||||||
|
if (aiVsAi) {
|
||||||
|
player.displayClientMessage(Component.literal("AI vs AI duel started. Empty-hand click on a Duel Table launches an autoplay match.").withStyle(ChatFormatting.GOLD), false);
|
||||||
|
if (player instanceof net.minecraft.server.level.ServerPlayer serverPlayer) {
|
||||||
|
DuelTableAnimationResolver.startAiTurn(table, session, serverPlayer);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
player.displayClientMessage(session.startMessage().copy().withStyle(ChatFormatting.GOLD), false);
|
player.displayClientMessage(session.startMessage().copy().withStyle(ChatFormatting.GOLD), false);
|
||||||
player.displayClientMessage(session.handSummary(), false);
|
}
|
||||||
return ItemInteractionResult.CONSUME;
|
return ItemInteractionResult.CONSUME;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (session.isInOverview()) {
|
if (table.hasActiveAnimation() || session.isAnimating()) {
|
||||||
completeOverview(level, pos, player, session);
|
|
||||||
return ItemInteractionResult.CONSUME;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!session.isPlayerTurn()) {
|
|
||||||
MoveResult opponentMove = session.playOpponentTurn();
|
|
||||||
if (!opponentMove.valid()) {
|
|
||||||
player.displayClientMessage(Component.literal(opponentMove.errorMessage()).withStyle(ChatFormatting.RED), false);
|
|
||||||
clearAndRefund(level, pos, player, true);
|
|
||||||
return ItemInteractionResult.CONSUME;
|
|
||||||
}
|
|
||||||
placeBoardCard(table, opponentMove, DuelTableBlockEntity.OWNER_SECOND);
|
|
||||||
applyCapturedOwnership(table, opponentMove, DuelTableBlockEntity.OWNER_SECOND);
|
|
||||||
player.displayClientMessage(Component.literal("Training Duelist takes a turn."), false);
|
|
||||||
sendBattleLog(player, opponentMove, ChatFormatting.GRAY);
|
|
||||||
player.displayClientMessage(session.boardSummary(), false);
|
|
||||||
finishIfComplete(level, pos, player, session);
|
|
||||||
return ItemInteractionResult.CONSUME;
|
return ItemInteractionResult.CONSUME;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!stack.is(TriadItems.TRIAD_CARD.get())) {
|
if (!stack.is(TriadItems.TRIAD_CARD.get())) {
|
||||||
player.displayClientMessage(Component.literal("Hold a Triad Card from your duel hand to play your turn.").withStyle(ChatFormatting.YELLOW), false);
|
|
||||||
player.displayClientMessage(session.handSummary(), false);
|
|
||||||
return ItemInteractionResult.CONSUME;
|
return ItemInteractionResult.CONSUME;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,8 +131,6 @@ public class DuelTableBlock extends BaseEntityBlock {
|
|||||||
MoveResult playerMove = session.playPlayerCard(cardData.cardId(), hitResult, player.getAbilities().instabuild, tableFacing);
|
MoveResult playerMove = session.playPlayerCard(cardData.cardId(), hitResult, player.getAbilities().instabuild, tableFacing);
|
||||||
if (!playerMove.valid()) {
|
if (!playerMove.valid()) {
|
||||||
player.displayClientMessage(Component.literal("Move rejected: " + playerMove.errorMessage()).withStyle(ChatFormatting.RED), false);
|
player.displayClientMessage(Component.literal("Move rejected: " + playerMove.errorMessage()).withStyle(ChatFormatting.RED), false);
|
||||||
player.displayClientMessage(session.boardSummary().copy().withStyle(ChatFormatting.DARK_GRAY), false);
|
|
||||||
player.displayClientMessage(session.handSummary(), false);
|
|
||||||
return ItemInteractionResult.CONSUME;
|
return ItemInteractionResult.CONSUME;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,36 +139,14 @@ public class DuelTableBlock extends BaseEntityBlock {
|
|||||||
clearAndRefund(level, pos, player, true);
|
clearAndRefund(level, pos, player, true);
|
||||||
return ItemInteractionResult.CONSUME;
|
return ItemInteractionResult.CONSUME;
|
||||||
}
|
}
|
||||||
applyCapturedOwnership(table, playerMove, DuelTableBlockEntity.OWNER_FIRST);
|
|
||||||
|
|
||||||
if (!player.getAbilities().instabuild) {
|
if (!player.getAbilities().instabuild) {
|
||||||
stack.consume(1, player);
|
stack.consume(1, player);
|
||||||
}
|
}
|
||||||
|
|
||||||
player.displayClientMessage(Component.literal("You play " + playerMove.playedCardName() + "."), false);
|
session.beginPlayerAnimation();
|
||||||
sendBattleLog(player, playerMove, ChatFormatting.DARK_AQUA);
|
table.updateHudState(session);
|
||||||
player.displayClientMessage(session.boardSummary(), false);
|
table.startMoveAnimation(playerMove, DuelTableBlockEntity.OWNER_FIRST);
|
||||||
finishIfComplete(level, pos, player, session);
|
|
||||||
if (session.isComplete()) {
|
|
||||||
return ItemInteractionResult.CONSUME;
|
|
||||||
}
|
|
||||||
|
|
||||||
MoveResult opponentMove = session.playOpponentTurn();
|
|
||||||
if (!opponentMove.valid()) {
|
|
||||||
player.displayClientMessage(Component.literal(opponentMove.errorMessage()).withStyle(ChatFormatting.RED), false);
|
|
||||||
clearAndRefund(level, pos, player, true);
|
|
||||||
return ItemInteractionResult.CONSUME;
|
|
||||||
}
|
|
||||||
placeBoardCard(table, opponentMove, DuelTableBlockEntity.OWNER_SECOND);
|
|
||||||
applyCapturedOwnership(table, opponentMove, DuelTableBlockEntity.OWNER_SECOND);
|
|
||||||
player.displayClientMessage(Component.literal("Training Duelist answers with " + opponentMove.playedCardName() + "."), false);
|
|
||||||
sendBattleLog(player, opponentMove, ChatFormatting.GRAY);
|
|
||||||
player.displayClientMessage(session.boardSummary(), false);
|
|
||||||
finishIfComplete(level, pos, player, session);
|
|
||||||
if (!session.isComplete()) {
|
|
||||||
player.displayClientMessage(Component.literal("Your turn. Hold one of your remaining duel cards and click an open space.").withStyle(ChatFormatting.YELLOW), false);
|
|
||||||
player.displayClientMessage(session.handSummary(), false);
|
|
||||||
}
|
|
||||||
return ItemInteractionResult.CONSUME;
|
return ItemInteractionResult.CONSUME;
|
||||||
} catch (Exception exception) {
|
} catch (Exception exception) {
|
||||||
MineTriad.LOGGER.error("Duel table interaction failed", exception);
|
MineTriad.LOGGER.error("Duel table interaction failed", exception);
|
||||||
@@ -203,11 +194,64 @@ public class DuelTableBlock extends BaseEntityBlock {
|
|||||||
return new DuelTableBlockEntity(pos, state);
|
return new DuelTableBlockEntity(pos, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level, BlockState state, BlockEntityType<T> blockEntityType) {
|
||||||
|
return createTickerHelper(blockEntityType, com.trunksbomb.minetriad.registry.TriadBlockEntities.DUEL_TABLE.get(), DuelTableBlockEntity::tick);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected RenderShape getRenderShape(BlockState state) {
|
protected RenderShape getRenderShape(BlockState state) {
|
||||||
return RenderShape.MODEL;
|
return RenderShape.MODEL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
|
||||||
|
return overviewRewardShape(state, level, pos, super.getShape(state, level, pos, context));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected VoxelShape getInteractionShape(BlockState state, BlockGetter level, BlockPos pos) {
|
||||||
|
return overviewRewardShape(state, level, pos, super.getInteractionShape(state, level, pos));
|
||||||
|
}
|
||||||
|
|
||||||
|
private VoxelShape overviewRewardShape(BlockState state, BlockGetter level, BlockPos pos, VoxelShape baseShape) {
|
||||||
|
VoxelShape shape = baseShape;
|
||||||
|
BlockEntity blockEntity = level.getBlockEntity(pos);
|
||||||
|
if (!(blockEntity instanceof DuelTableBlockEntity table) || table.rewardCardCount() <= 0) {
|
||||||
|
return shape;
|
||||||
|
}
|
||||||
|
|
||||||
|
Direction tableFacing = state.getValue(FACING);
|
||||||
|
int rewardCount = table.rewardCardCount();
|
||||||
|
VoxelShape rewardShape = Shapes.empty();
|
||||||
|
for (int index = 0; index < rewardCount; index++) {
|
||||||
|
BoardLocalSpace.SlotCenter center = BoardLocalSpace.rewardCenter(index, tableFacing, rewardCount);
|
||||||
|
double minX;
|
||||||
|
double maxX;
|
||||||
|
double minZ;
|
||||||
|
double maxZ;
|
||||||
|
if (tableFacing.getAxis() == Direction.Axis.Z) {
|
||||||
|
minX = center.x() - 0.11D;
|
||||||
|
maxX = center.x() + 0.11D;
|
||||||
|
minZ = center.z() - 0.05D;
|
||||||
|
maxZ = center.z() + 0.05D;
|
||||||
|
} else {
|
||||||
|
minX = center.x() - 0.05D;
|
||||||
|
maxX = center.x() + 0.05D;
|
||||||
|
minZ = center.z() - 0.11D;
|
||||||
|
maxZ = center.z() + 0.11D;
|
||||||
|
}
|
||||||
|
rewardShape = Shapes.or(rewardShape, Block.box(
|
||||||
|
minX * 16.0D,
|
||||||
|
20.0D,
|
||||||
|
minZ * 16.0D,
|
||||||
|
maxX * 16.0D,
|
||||||
|
31.5D,
|
||||||
|
maxZ * 16.0D));
|
||||||
|
}
|
||||||
|
return Shapes.or(shape, rewardShape);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
|
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
|
||||||
builder.add(FACING);
|
builder.add(FACING);
|
||||||
@@ -225,33 +269,11 @@ public class DuelTableBlock extends BaseEntityBlock {
|
|||||||
return table.setCard(moveResult.playedCell().index(), CardItem.createCardStack(moveResult.playedCardId(), TriadItems.TRIAD_CARD.get()), owner);
|
return table.setCard(moveResult.playedCell().index(), CardItem.createCardStack(moveResult.playedCardId(), TriadItems.TRIAD_CARD.get()), owner);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void applyCapturedOwnership(DuelTableBlockEntity table, MoveResult moveResult, int owner) {
|
|
||||||
for (var cell : moveResult.capturedCells()) {
|
|
||||||
table.setOwner(cell.index(), owner);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void sendBattleLog(Player player, MoveResult moveResult, ChatFormatting color) {
|
|
||||||
for (String line : moveResult.battleLog()) {
|
|
||||||
player.displayClientMessage(Component.literal(line).withStyle(color), false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static DuelTableBlockEntity getTableEntity(Level level, BlockPos pos) {
|
private static DuelTableBlockEntity getTableEntity(Level level, BlockPos pos) {
|
||||||
BlockEntity blockEntity = level.getBlockEntity(pos);
|
BlockEntity blockEntity = level.getBlockEntity(pos);
|
||||||
return blockEntity instanceof DuelTableBlockEntity duelTableBlockEntity ? duelTableBlockEntity : null;
|
return blockEntity instanceof DuelTableBlockEntity duelTableBlockEntity ? duelTableBlockEntity : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void finishIfComplete(Level level, BlockPos pos, Player player, DuelSession session) {
|
|
||||||
if (!session.isComplete()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
session.enterOverview();
|
|
||||||
player.displayClientMessage(session.resultSummary().copy().withStyle(ChatFormatting.AQUA), false);
|
|
||||||
player.displayClientMessage(session.overviewMessage().copy().withStyle(ChatFormatting.YELLOW), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void clearAndRefund(Level level, BlockPos pos, Player player, boolean refundFullHand) {
|
private static void clearAndRefund(Level level, BlockPos pos, Player player, boolean refundFullHand) {
|
||||||
DuelTableBlockEntity table = getTableEntity(level, pos);
|
DuelTableBlockEntity table = getTableEntity(level, pos);
|
||||||
if (table != null) {
|
if (table != null) {
|
||||||
@@ -263,9 +285,98 @@ public class DuelTableBlock extends BaseEntityBlock {
|
|||||||
private static void completeOverview(Level level, BlockPos pos, Player player, DuelSession session) {
|
private static void completeOverview(Level level, BlockPos pos, Player player, DuelSession session) {
|
||||||
DuelTableBlockEntity table = getTableEntity(level, pos);
|
DuelTableBlockEntity table = getTableEntity(level, pos);
|
||||||
if (table != null) {
|
if (table != null) {
|
||||||
table.clearBoard();
|
session.beginClosingAnimation();
|
||||||
|
table.updateHudState(session);
|
||||||
|
table.startClearWaveAnimation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean tryTakeRewardCard(Level level, BlockPos pos, Player player, BlockHitResult hitResult, Direction tableFacing) {
|
||||||
|
DuelTableBlockEntity table = getTableEntity(level, pos);
|
||||||
|
if (table == null || table.rewardCardCount() <= 0) {
|
||||||
|
player.displayClientMessage(Component.literal("Reward debug: no reward cards available.").withStyle(ChatFormatting.DARK_GRAY), false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
double localX = hitResult.getLocation().x - hitResult.getBlockPos().getX();
|
||||||
|
double localY = hitResult.getLocation().y - hitResult.getBlockPos().getY();
|
||||||
|
double localZ = hitResult.getLocation().z - hitResult.getBlockPos().getZ();
|
||||||
|
int rewardIndex = com.trunksbomb.minetriad.game.BoardLocalSpace.rewardIndexForHit(hitResult, tableFacing, table.rewardCardCount());
|
||||||
|
if (rewardIndex < 0) {
|
||||||
|
player.displayClientMessage(Component.literal(String.format(
|
||||||
|
"Reward debug: miss at local %.3f, %.3f, %.3f with %d cards",
|
||||||
|
localX,
|
||||||
|
localY,
|
||||||
|
localZ,
|
||||||
|
table.rewardCardCount())).withStyle(ChatFormatting.DARK_GRAY), false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int actualSlot = actualRewardSlot(table, rewardIndex);
|
||||||
|
if (actualSlot < 0) {
|
||||||
|
player.displayClientMessage(Component.literal("Reward debug: resolved visual index " + rewardIndex + " but found no matching slot.").withStyle(ChatFormatting.DARK_GRAY), false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemStack reward = table.takeRewardCard(actualSlot);
|
||||||
|
if (reward.isEmpty()) {
|
||||||
|
player.displayClientMessage(Component.literal("Reward debug: slot " + actualSlot + " was empty when taking.").withStyle(ChatFormatting.DARK_GRAY), false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
player.displayClientMessage(Component.literal("Reward debug: took visual index " + rewardIndex + " from slot " + actualSlot + ".").withStyle(ChatFormatting.DARK_GRAY), false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int actualRewardSlot(DuelTableBlockEntity table, int rewardIndex) {
|
||||||
|
int visibleIndex = 0;
|
||||||
|
for (int slot = 0; slot < DuelTableBlockEntity.REWARD_SLOT_COUNT; slot++) {
|
||||||
|
if (table.getRewardCard(slot).isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (visibleIndex == rewardIndex) {
|
||||||
|
return slot;
|
||||||
|
}
|
||||||
|
visibleIndex++;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventBusSubscriber(modid = MineTriad.MOD_ID)
|
||||||
|
public static final class RewardInteractionEvents {
|
||||||
|
@SubscribeEvent
|
||||||
|
public static void onEntityInteract(PlayerInteractEvent.EntityInteractSpecific event) {
|
||||||
|
if (!(event.getTarget() instanceof Interaction interaction) || !DuelTableBlockEntity.isRewardProxy(interaction)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BlockPos tablePos = DuelTableBlockEntity.rewardProxyPos(interaction);
|
||||||
|
if (tablePos == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Level level = event.getLevel();
|
||||||
|
DuelTableBlockEntity table = getTableEntity(level, tablePos);
|
||||||
|
DuelSession session = DuelSessionManager.getAt(tablePos);
|
||||||
|
if (table == null || session == null || !session.isInOverview()) {
|
||||||
|
interaction.discard();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int slot = DuelTableBlockEntity.rewardProxySlot(interaction);
|
||||||
|
if (slot < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemStack reward = table.takeRewardCard(slot);
|
||||||
|
if (reward.isEmpty()) {
|
||||||
|
interaction.discard();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.getEntity().displayClientMessage(Component.literal("Reward debug: entity take from slot " + slot + ".").withStyle(ChatFormatting.DARK_GRAY), false);
|
||||||
|
event.setCanceled(true);
|
||||||
|
event.setCancellationResult(InteractionResult.SUCCESS);
|
||||||
}
|
}
|
||||||
DuelSessionManager.end(player, true);
|
|
||||||
player.displayClientMessage(Component.literal("Duel finished. All played cards have been returned.").withStyle(ChatFormatting.GREEN), false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user