cards now have some simple animations: idle bob on the board, placement, card capture flip, game over wave, and end duel pop up
This commit is contained in:
@@ -0,0 +1,122 @@
|
|||||||
|
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.startMoveAnimation(opponentMove, DuelTableBlockEntity.OWNER_SECOND);
|
||||||
|
player.displayClientMessage(Component.literal("Training Duelist answers with " + opponentMove.playedCardName() + ".").withStyle(ChatFormatting.GRAY), false);
|
||||||
|
sendBattleLog(player, opponentMove, ChatFormatting.GRAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
player.displayClientMessage(session.boardSummary(), false);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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.startMoveAnimation(aiMove, owner);
|
||||||
|
player.displayClientMessage(Component.literal((owner == DuelTableBlockEntity.OWNER_FIRST ? "Blue AI" : "Red AI") + " plays " + aiMove.playedCardName() + ".")
|
||||||
|
.withStyle(ChatFormatting.GRAY), false);
|
||||||
|
sendBattleLog(player, aiMove, ChatFormatting.GRAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void enterOverview(DuelTableBlockEntity table, DuelSession session, ServerPlayer player) {
|
||||||
|
session.enterOverview();
|
||||||
|
table.startOverviewSweepAnimation();
|
||||||
|
player.displayClientMessage(session.resultSummary().copy().withStyle(ChatFormatting.AQUA), false);
|
||||||
|
player.displayClientMessage(session.overviewMessage().copy().withStyle(ChatFormatting.YELLOW), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void sendBattleLog(ServerPlayer player, MoveResult moveResult, ChatFormatting color) {
|
||||||
|
for (String line : moveResult.battleLog()) {
|
||||||
|
player.displayClientMessage(Component.literal(line).withStyle(color), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ServerPlayer getPlayer(Level level, DuelSession session) {
|
||||||
|
return level.getServer() == null ? null : level.getServer().getPlayerList().getPlayer(session.playerParticipantId());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,17 +1,26 @@
|
|||||||
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.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.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;
|
||||||
@@ -24,11 +33,26 @@ public class DuelTableBlockEntity extends BlockEntity {
|
|||||||
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 final NonNullList<ItemStack> boardCards = NonNullList.withSize(SLOT_COUNT, ItemStack.EMPTY);
|
private final NonNullList<ItemStack> boardCards = NonNullList.withSize(SLOT_COUNT, ItemStack.EMPTY);
|
||||||
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 animationSlot = -1;
|
||||||
|
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;
|
||||||
|
|
||||||
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);
|
||||||
@@ -42,6 +66,10 @@ public class DuelTableBlockEntity extends BlockEntity {
|
|||||||
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,6 +78,52 @@ public class DuelTableBlockEntity extends BlockEntity {
|
|||||||
return Optional.ofNullable(secondParticipantId);
|
return Optional.ofNullable(secondParticipantId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 animationSlot;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
@@ -63,6 +137,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,7 +154,10 @@ 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();
|
||||||
firstParticipantId = null;
|
firstParticipantId = null;
|
||||||
secondParticipantId = null;
|
secondParticipantId = null;
|
||||||
sync();
|
sync();
|
||||||
@@ -92,18 +170,106 @@ 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();
|
||||||
firstParticipantId = null;
|
firstParticipantId = null;
|
||||||
secondParticipantId = null;
|
secondParticipantId = null;
|
||||||
sync();
|
sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void startMoveAnimation(MoveResult moveResult, int owner) {
|
||||||
|
pendingAnimations.clear();
|
||||||
|
clearAnimationState();
|
||||||
|
clearTableAnimationState();
|
||||||
|
if (moveResult.playedCell() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pendingAnimations.addLast(new AnimationStep(AnimationType.PLACEMENT, moveResult.playedCell().index(), owner));
|
||||||
|
for (BoardCell capturedCell : moveResult.capturedCells()) {
|
||||||
|
pendingAnimations.addLast(new AnimationStep(AnimationType.CAPTURE, capturedCell.index(), 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)) {
|
||||||
|
blockEntity.setOwner(blockEntity.animationSlot, 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@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);
|
||||||
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);
|
||||||
}
|
}
|
||||||
@@ -118,6 +284,8 @@ public class DuelTableBlockEntity extends BlockEntity {
|
|||||||
clearBoardContents();
|
clearBoardContents();
|
||||||
ContainerHelper.loadAllItems(tag, boardCards, registries);
|
ContainerHelper.loadAllItems(tag, boardCards, registries);
|
||||||
loadOwnerData(tag);
|
loadOwnerData(tag);
|
||||||
|
loadAnimation(tag);
|
||||||
|
loadTableAnimation(tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -125,6 +293,9 @@ public class DuelTableBlockEntity extends BlockEntity {
|
|||||||
CompoundTag tag = super.getUpdateTag(registries);
|
CompoundTag tag = super.getUpdateTag(registries);
|
||||||
ContainerHelper.saveAllItems(tag, boardCards, registries);
|
ContainerHelper.saveAllItems(tag, boardCards, registries);
|
||||||
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);
|
||||||
}
|
}
|
||||||
@@ -146,6 +317,8 @@ public class DuelTableBlockEntity extends BlockEntity {
|
|||||||
clearBoardContents();
|
clearBoardContents();
|
||||||
ContainerHelper.loadAllItems(tag, boardCards, registries);
|
ContainerHelper.loadAllItems(tag, boardCards, registries);
|
||||||
loadOwnerData(tag);
|
loadOwnerData(tag);
|
||||||
|
loadAnimation(tag);
|
||||||
|
loadTableAnimation(tag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,7 +326,10 @@ 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();
|
||||||
firstParticipantId = null;
|
firstParticipantId = null;
|
||||||
secondParticipantId = null;
|
secondParticipantId = null;
|
||||||
}
|
}
|
||||||
@@ -163,14 +339,153 @@ 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 saveAnimation(CompoundTag tag) {
|
||||||
|
tag.putString("AnimationType", animationType.name());
|
||||||
|
tag.putInt("AnimationSlot", animationSlot);
|
||||||
|
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"));
|
||||||
|
animationSlot = tag.getInt("AnimationSlot");
|
||||||
|
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();
|
||||||
|
animationSlot = nextStep.slot();
|
||||||
|
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;
|
||||||
|
animationSlot = -1;
|
||||||
|
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 slot, int targetOwner) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
|
|||||||
import net.minecraft.world.item.ItemStack;
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
|
||||||
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 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,10 +31,41 @@ 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.animationSlot() == 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 if (!blockEntity.hasActiveAnimation()) {
|
||||||
|
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)));
|
||||||
@@ -38,6 +73,21 @@ public class DuelTableBlockEntityRenderer implements BlockEntityRenderer<DuelTab
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 TriadCardItemRenderer.CardPalette perspectivePalette(DuelTableBlockEntity blockEntity, int owner) {
|
private static TriadCardItemRenderer.CardPalette perspectivePalette(DuelTableBlockEntity blockEntity, int owner) {
|
||||||
if (owner == DuelTableBlockEntity.OWNER_NONE || Minecraft.getInstance().player == null) {
|
if (owner == DuelTableBlockEntity.OWNER_NONE || Minecraft.getInstance().player == null) {
|
||||||
return new TriadCardItemRenderer.CardPalette(
|
return new TriadCardItemRenderer.CardPalette(
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,6 +29,7 @@ public final class DuelSession {
|
|||||||
private final TriadMatch match;
|
private final TriadMatch match;
|
||||||
private final List<ResourceLocation> refundablePlayerCards;
|
private final List<ResourceLocation> refundablePlayerCards;
|
||||||
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 +40,14 @@ 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.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 +68,42 @@ 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 void enterOverview() {
|
public void enterOverview() {
|
||||||
phase = Phase.OVERVIEW;
|
phase = Phase.OVERVIEW;
|
||||||
}
|
}
|
||||||
@@ -96,15 +138,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 +171,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() {
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ 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.DuelSession;
|
import com.trunksbomb.minetriad.game.DuelSession;
|
||||||
@@ -26,6 +27,8 @@ 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;
|
||||||
@@ -67,16 +70,24 @@ public class DuelTableBlock extends BaseEntityBlock {
|
|||||||
|
|
||||||
DuelSession session = DuelSessionManager.get(player);
|
DuelSession session = DuelSessionManager.get(player);
|
||||||
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());
|
||||||
|
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);
|
player.displayClientMessage(session.handSummary(), false);
|
||||||
|
}
|
||||||
return ItemInteractionResult.CONSUME;
|
return ItemInteractionResult.CONSUME;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,19 +96,8 @@ public class DuelTableBlock extends BaseEntityBlock {
|
|||||||
return ItemInteractionResult.CONSUME;
|
return ItemInteractionResult.CONSUME;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!session.isPlayerTurn()) {
|
if (table.hasActiveAnimation() || session.isAnimating()) {
|
||||||
MoveResult opponentMove = session.playOpponentTurn();
|
player.displayClientMessage(Component.literal("Wait for the current card animation to finish.").withStyle(ChatFormatting.YELLOW), false);
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,36 +126,15 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
session.beginPlayerAnimation();
|
||||||
|
table.startMoveAnimation(playerMove, DuelTableBlockEntity.OWNER_FIRST);
|
||||||
player.displayClientMessage(Component.literal("You play " + playerMove.playedCardName() + "."), false);
|
player.displayClientMessage(Component.literal("You play " + playerMove.playedCardName() + "."), false);
|
||||||
sendBattleLog(player, playerMove, ChatFormatting.DARK_AQUA);
|
sendBattleLog(player, playerMove, ChatFormatting.DARK_AQUA);
|
||||||
player.displayClientMessage(session.boardSummary(), false);
|
|
||||||
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,6 +182,11 @@ 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;
|
||||||
@@ -225,12 +209,6 @@ 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) {
|
private static void sendBattleLog(Player player, MoveResult moveResult, ChatFormatting color) {
|
||||||
for (String line : moveResult.battleLog()) {
|
for (String line : moveResult.battleLog()) {
|
||||||
player.displayClientMessage(Component.literal(line).withStyle(color), false);
|
player.displayClientMessage(Component.literal(line).withStyle(color), false);
|
||||||
@@ -242,16 +220,6 @@ public class DuelTableBlock extends BaseEntityBlock {
|
|||||||
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 +231,8 @@ 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.startClearWaveAnimation();
|
||||||
}
|
}
|
||||||
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