add basic "proximity UI" that appears when you approach a game in progress
Some checks failed
Build / build (push) Has been cancelled
Some checks failed
Build / build (push) Has been cancelled
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
package com.trunksbomb.minetriad;
|
||||
|
||||
import com.trunksbomb.minetriad.client.hud.LocalDuelHudOverlay;
|
||||
import com.trunksbomb.minetriad.client.render.FirstPersonCardHandRenderer;
|
||||
import com.trunksbomb.minetriad.client.render.DuelTableBlockEntityRenderer;
|
||||
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.neoforge.client.event.EntityRenderersEvent;
|
||||
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.RenderGuiLayerEvent;
|
||||
|
||||
@Mod(value = MineTriad.MOD_ID, dist = Dist.CLIENT)
|
||||
public final class MineTriadClient {
|
||||
@@ -55,5 +58,15 @@ public final class MineTriadClient {
|
||||
event.getEquipProgress(),
|
||||
event.getItemStack());
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onClientTick(ClientTickEvent.Post event) {
|
||||
LocalDuelHudOverlay.onClientTick(event);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onRenderGui(RenderGuiLayerEvent.Post event) {
|
||||
LocalDuelHudOverlay.onRenderGui(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ public final class DuelTableAnimationResolver {
|
||||
}
|
||||
|
||||
session.beginOpponentAnimation();
|
||||
table.updateHudState(session);
|
||||
table.startMoveAnimation(opponentMove, DuelTableBlockEntity.OWNER_SECOND);
|
||||
}
|
||||
|
||||
@@ -68,6 +69,7 @@ public final class DuelTableAnimationResolver {
|
||||
}
|
||||
|
||||
session.returnToPlaying();
|
||||
table.updateHudState(session);
|
||||
}
|
||||
|
||||
public static void startAiTurn(DuelTableBlockEntity table, DuelSession session, ServerPlayer player) {
|
||||
@@ -92,6 +94,7 @@ public final class DuelTableAnimationResolver {
|
||||
} else {
|
||||
session.beginOpponentAnimation();
|
||||
}
|
||||
table.updateHudState(session);
|
||||
table.startMoveAnimation(aiMove, owner);
|
||||
}
|
||||
|
||||
@@ -101,6 +104,7 @@ public final class DuelTableAnimationResolver {
|
||||
.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);
|
||||
}
|
||||
|
||||
@@ -67,6 +67,15 @@ public class DuelTableBlockEntity extends BlockEntity {
|
||||
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) {
|
||||
super(TriadBlockEntities.DUEL_TABLE.get(), pos, blockState);
|
||||
@@ -110,6 +119,42 @@ public class DuelTableBlockEntity extends BlockEntity {
|
||||
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;
|
||||
}
|
||||
@@ -171,6 +216,34 @@ public class DuelTableBlockEntity extends BlockEntity {
|
||||
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) {
|
||||
if (slot < 0 || slot >= boardCards.size() || !boardCards.get(slot).isEmpty() || !stack.is(TriadItems.TRIAD_CARD.get())) {
|
||||
return false;
|
||||
@@ -202,6 +275,7 @@ public class DuelTableBlockEntity extends BlockEntity {
|
||||
clearRewardCards();
|
||||
firstParticipantId = null;
|
||||
secondParticipantId = null;
|
||||
clearHudState();
|
||||
sync();
|
||||
}
|
||||
|
||||
@@ -236,6 +310,7 @@ public class DuelTableBlockEntity extends BlockEntity {
|
||||
dropRewardCards(level, pos);
|
||||
firstParticipantId = null;
|
||||
secondParticipantId = null;
|
||||
clearHudState();
|
||||
sync();
|
||||
}
|
||||
|
||||
@@ -248,6 +323,10 @@ public class DuelTableBlockEntity extends BlockEntity {
|
||||
}
|
||||
}
|
||||
refreshRewardInteractions();
|
||||
DuelSession session = level == null ? null : DuelSessionManager.getAt(worldPosition);
|
||||
if (session != null) {
|
||||
updateHudState(session);
|
||||
}
|
||||
sync();
|
||||
}
|
||||
|
||||
@@ -270,6 +349,10 @@ public class DuelTableBlockEntity extends BlockEntity {
|
||||
}
|
||||
rewardCards.set(slot, ItemStack.EMPTY);
|
||||
refreshRewardInteractions();
|
||||
DuelSession session = level == null ? null : DuelSessionManager.getAt(worldPosition);
|
||||
if (session != null) {
|
||||
updateHudState(session);
|
||||
}
|
||||
sync();
|
||||
return taken;
|
||||
}
|
||||
@@ -475,6 +558,7 @@ public class DuelTableBlockEntity extends BlockEntity {
|
||||
ContainerHelper.saveAllItems(rewardTag, rewardCards, registries);
|
||||
tag.put("RewardCards", rewardTag);
|
||||
saveRewardTakeAnimations(tag, registries);
|
||||
saveHudState(tag);
|
||||
tag.putIntArray("OwnerSlots", ownerSlots);
|
||||
tag.putIntArray("SlotAges", slotAges);
|
||||
saveAnimation(tag);
|
||||
@@ -496,6 +580,7 @@ public class DuelTableBlockEntity extends BlockEntity {
|
||||
ContainerHelper.loadAllItems(tag.getCompound("RewardCards"), rewardCards, registries);
|
||||
}
|
||||
loadRewardTakeAnimations(tag, registries);
|
||||
loadHudState(tag);
|
||||
loadOwnerData(tag);
|
||||
loadAnimation(tag);
|
||||
loadTableAnimation(tag);
|
||||
@@ -509,6 +594,7 @@ public class DuelTableBlockEntity extends BlockEntity {
|
||||
ContainerHelper.saveAllItems(rewardTag, rewardCards, registries);
|
||||
tag.put("RewardCards", rewardTag);
|
||||
saveRewardTakeAnimations(tag, registries);
|
||||
saveHudState(tag);
|
||||
tag.putIntArray("OwnerSlots", ownerSlots);
|
||||
tag.putIntArray("SlotAges", slotAges);
|
||||
saveAnimation(tag);
|
||||
@@ -537,6 +623,7 @@ public class DuelTableBlockEntity extends BlockEntity {
|
||||
ContainerHelper.loadAllItems(tag.getCompound("RewardCards"), rewardCards, registries);
|
||||
}
|
||||
loadRewardTakeAnimations(tag, registries);
|
||||
loadHudState(tag);
|
||||
loadOwnerData(tag);
|
||||
loadAnimation(tag);
|
||||
loadTableAnimation(tag);
|
||||
@@ -554,6 +641,15 @@ public class DuelTableBlockEntity extends BlockEntity {
|
||||
clearRewardCards();
|
||||
firstParticipantId = null;
|
||||
secondParticipantId = null;
|
||||
hudActive = false;
|
||||
hudP1HandCount = 0;
|
||||
hudP2HandCount = 0;
|
||||
hudP1Score = 0;
|
||||
hudP2Score = 0;
|
||||
hudRules = "";
|
||||
hudGameState = "";
|
||||
hudTurn = "";
|
||||
hudInstruction = "";
|
||||
}
|
||||
|
||||
private void loadOwnerData(CompoundTag tag) {
|
||||
@@ -585,6 +681,30 @@ public class DuelTableBlockEntity extends BlockEntity {
|
||||
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)) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -108,6 +108,78 @@ public final class DuelSession {
|
||||
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() {
|
||||
phase = Phase.OVERVIEW;
|
||||
}
|
||||
|
||||
@@ -102,6 +102,7 @@ public class DuelTableBlock extends BaseEntityBlock {
|
||||
}
|
||||
table.clearBoard();
|
||||
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) {
|
||||
@@ -144,6 +145,7 @@ public class DuelTableBlock extends BaseEntityBlock {
|
||||
}
|
||||
|
||||
session.beginPlayerAnimation();
|
||||
table.updateHudState(session);
|
||||
table.startMoveAnimation(playerMove, DuelTableBlockEntity.OWNER_FIRST);
|
||||
return ItemInteractionResult.CONSUME;
|
||||
} catch (Exception exception) {
|
||||
@@ -284,6 +286,7 @@ public class DuelTableBlock extends BaseEntityBlock {
|
||||
DuelTableBlockEntity table = getTableEntity(level, pos);
|
||||
if (table != null) {
|
||||
session.beginClosingAnimation();
|
||||
table.updateHudState(session);
|
||||
table.startClearWaveAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user