full card structure in place, card binder working.
Some checks failed
Build / build (push) Has been cancelled

This commit is contained in:
trunksbomb
2026-03-23 05:18:20 -04:00
parent 44ebb792ea
commit 4cd60e4fac
167 changed files with 3345 additions and 77 deletions

View File

@@ -8,6 +8,7 @@ import com.trunksbomb.minetriad.registry.TriadBlockEntities;
import com.trunksbomb.minetriad.registry.TriadCreativeTabs;
import com.trunksbomb.minetriad.registry.TriadDataComponents;
import com.trunksbomb.minetriad.registry.TriadItems;
import com.trunksbomb.minetriad.registry.TriadMenus;
import net.neoforged.bus.api.IEventBus;
import net.neoforged.fml.ModContainer;
@@ -23,6 +24,7 @@ public final class MineTriad {
TriadBlocks.register(modEventBus);
TriadBlockEntities.register(modEventBus);
TriadItems.register(modEventBus);
TriadMenus.register(modEventBus);
TriadCreativeTabs.register(modEventBus);
}
}

View File

@@ -1,15 +1,22 @@
package com.trunksbomb.minetriad;
import com.trunksbomb.minetriad.client.render.FirstPersonCardHandRenderer;
import com.trunksbomb.minetriad.client.render.DuelTableBlockEntityRenderer;
import com.trunksbomb.minetriad.client.screen.CardBinderScreen;
import com.trunksbomb.minetriad.registry.TriadItems;
import com.trunksbomb.minetriad.registry.TriadBlockEntities;
import com.trunksbomb.minetriad.registry.TriadMenus;
import net.minecraft.client.renderer.blockentity.BlockEntityRenderers;
import net.minecraft.client.gui.screens.MenuScreens;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.Mod;
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.RenderHandEvent;
@Mod(value = MineTriad.MOD_ID, dist = Dist.CLIENT)
public final class MineTriadClient {
@@ -22,5 +29,31 @@ public final class MineTriadClient {
public static void registerRenderers(EntityRenderersEvent.RegisterRenderers event) {
event.registerBlockEntityRenderer(TriadBlockEntities.DUEL_TABLE.get(), DuelTableBlockEntityRenderer::new);
}
@SubscribeEvent
public static void registerScreens(RegisterMenuScreensEvent event) {
event.register(TriadMenus.CARD_BINDER.get(), CardBinderScreen::new);
}
}
@EventBusSubscriber(modid = MineTriad.MOD_ID, value = Dist.CLIENT)
public static final class ClientGameEvents {
@SubscribeEvent
public static void onRenderHand(RenderHandEvent event) {
if (!event.getItemStack().is(TriadItems.TRIAD_CARD.get())) {
return;
}
event.setCanceled(true);
FirstPersonCardHandRenderer.render(
event.getHand(),
event.getPoseStack(),
event.getMultiBufferSource(),
event.getPackedLight(),
event.getInterpolatedPitch(),
event.getSwingProgress(),
event.getEquipProgress(),
event.getItemStack());
}
}
}

View File

@@ -6,6 +6,7 @@ import net.minecraft.resources.ResourceLocation;
public record CardDefinition(
ResourceLocation id,
Component name,
int level,
int top,
int right,
int bottom,
@@ -13,6 +14,9 @@ public record CardDefinition(
Rarity rarity) {
public CardDefinition {
if (level < 1 || level > 10) {
throw new IllegalArgumentException("level must be between 1 and 10");
}
validateRank("top", top);
validateRank("right", right);
validateRank("bottom", bottom);

View File

@@ -1,5 +1,6 @@
package com.trunksbomb.minetriad.card;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
@@ -11,14 +12,139 @@ import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
public final class CardRegistry {
private static final List<CardDefinition> CARD_DEFINITIONS = List.of(
define("slime", "Slime", 3, 5, 2, 4, Rarity.COMMON),
define("zombie", "Zombie", 5, 4, 6, 3, Rarity.COMMON),
define("skeleton", "Skeleton", 6, 5, 3, 2, Rarity.COMMON),
define("creeper", "Creeper", 2, 7, 5, 6, Rarity.UNCOMMON),
define("enderman", "Enderman", 8, 4, 7, 5, Rarity.RARE),
define("warden", "Warden", 9, 8, 7, 6, Rarity.LEGENDARY));
private static final int[][][] TT_LEVEL_STATS = {
{
{ 1, 4, 1, 5 }, // Geezard
{ 1, 1, 1, 3 }, // Funguar
{ 3, 3, 3, 5 }, // Bite Bug
{ 1, 1, 1, 2 }, // Red Bat
{ 1, 3, 1, 5 }, // Blobra
{ 4, 1, 4, 4 }, // Gayla
{ 4, 5, 4, 1 }, // Gesper
{ 2, 5, 2, 1 }, // Fastitocalon-F
{ 6, 1, 6, 1 }, // Blood Soul
{ 4, 2, 4, 3 }, // Caterchipillar
{ 2, 1, 2, 6 } // Cockatrice
},
{
{ 3, 1, 3, 1 }, // Grat
{ 2, 2, 2, 3 }, // Buel
{ 3, 3, 3, 4 }, // Mesmerize
{ 4, 1, 4, 3 }, // Glacial Eye
{ 5, 4, 5, 3 }, // Belhelmel
{ 2, 3, 2, 5 }, // Thrustaevis
{ 3, 1, 3, 5 }, // Anacondaur
{ 5, 2, 5, 2 }, // Creeps
{ 5, 4, 5, 2 }, // Grendal
{ 1, 2, 1, 7 }, // Jelleye
{ 5, 2, 5, 3 } // Grand Mantis
},
{
{ 3, 6, 3, 2 }, // Forbidden
{ 1, 3, 1, 6 }, // Armadodo
{ 5, 5, 5, 5 }, // Tri-Face
{ 1, 5, 1, 3 }, // Fastitocalon
{ 5, 1, 5, 3 }, // Snow Lion
{ 3, 6, 3, 3 }, // Ochu
{ 2, 6, 2, 4 }, // SAM08G
{ 7, 4, 7, 2 }, // Death Claw
{ 6, 2, 6, 3 }, // Cactuar
{ 4, 6, 4, 4 }, // Tonberry
{ 3, 2, 3, 5 } // Abyss Worm
},
{
{ 3, 6, 3, 7 }, // Turtapod
{ 4, 5, 4, 5 }, // Vysage
{ 2, 6, 2, 7 }, // T-Rexaur
{ 6, 7, 6, 3 }, // Bomb
{ 4, 6, 4, 7 }, // Blitz
{ 1, 3, 1, 6 }, // Wendigo
{ 4, 4, 4, 4 }, // Torama
{ 3, 7, 3, 6 }, // Imp
{ 7, 2, 7, 3 }, // Blue Dragon
{ 5, 5, 5, 6 }, // Adamantoise
{ 4, 5, 4, 3 } // HexDragon
},
{
{ 6, 5, 6, 5 }, // Iron Giant
{ 5, 6, 5, 7 }, // Behemoth
{ 5, 6, 5, 3 }, // Chimera
{ 2, 10, 2, 1 }, // Pupu
{ 6, 2, 6, 7 }, // Elastoid
{ 7, 5, 7, 4 }, // GIM47N
{ 4, 7, 4, 2 }, // Malboro
{ 7, 2, 7, 4 }, // Ruby Dragon
{ 7, 3, 7, 6 }, // Elnoyle
{ 6, 7, 6, 4 }, // Tonberry King
{ 2, 6, 2, 7 } // Wedge, Biggs
},
{
{ 8, 8, 8, 4 }, // Fujin, Rajin
{ 3, 8, 3, 4 }, // Elvoret
{ 7, 8, 7, 3 }, // X-ATM092
{ 8, 2, 8, 5 }, // Granaldo
{ 8, 8, 8, 3 }, // Gerogero
{ 8, 2, 8, 2 }, // Iguion
{ 4, 8, 4, 5 }, // Abadon
{ 5, 8, 5, 6 }, // Trauma
{ 4, 8, 4, 8 }, // Oilboyle
{ 8, 5, 8, 4 }, // Shumi Tribe
{ 8, 5, 8, 1 } // Krysta
},
{
{ 4, 4, 4, 8 }, // Propagator
{ 4, 8, 4, 4 }, // Jumbo Cactuar
{ 6, 6, 6, 8 }, // Tri-Point
{ 6, 6, 6, 8 }, // Gargantua
{ 7, 6, 7, 3 }, // Mobile Type 8
{ 5, 3, 5, 8 }, // Sphinxara
{ 5, 8, 5, 4 }, // Tiamat
{ 8, 7, 8, 5 }, // BGH251F2
{ 4, 8, 4, 7 }, // Red Giant
{ 7, 8, 7, 7 }, // Catoblepas
{ 1, 7, 1, 8 } // Ultima Weapon
},
{
{ 8, 4, 8, 9 }, // Chubby Chocobo
{ 7, 6, 7, 3 }, // Angelo
{ 9, 7, 9, 6 }, // Gilgamesh
{ 9, 3, 9, 2 }, // Mini Mog
{ 8, 4, 8, 4 }, // Chicobo
{ 9, 9, 9, 4 }, // Quezacotl
{ 4, 7, 4, 9 }, // Shiva
{ 2, 6, 2, 8 }, // Ifrit
{ 6, 9, 6, 2 }, // Siren
{ 9, 1, 9, 9 }, // Sacred
{ 2, 5, 2, 9 } // Minotaur
},
{
{ 8, 4, 8, 4 }, // Carbuncle
{ 8, 8, 8, 3 }, // Diablos
{ 1, 10, 1, 7 }, // Leviathan
{ 3, 10, 3, 5 }, // Odin
{ 7, 1, 7, 7 }, // Pandemona
{ 6, 4, 6, 10 }, // Cerberus
{ 4, 10, 4, 2 }, // Alexander
{ 7, 2, 7, 10 }, // Phoenix
{ 2, 8, 2, 6 }, // Bahamut
{ 10, 1, 10, 10 }, // Doomtrain
{ 9, 4, 9, 10 } // Eden
},
{
{ 2, 7, 2, 8 }, // Ward
{ 6, 7, 6, 10 }, // Kiros
{ 3, 10, 3, 9 }, // Laguna
{ 6, 8, 6, 4 }, // Selphie
{ 10, 6, 10, 2 }, // Quistis
{ 9, 6, 9, 10 }, // Irvine
{ 10, 5, 10, 6 }, // Zell
{ 2, 10, 2, 10 }, // Rinoa
{ 3, 10, 3, 3 }, // Edea
{ 10, 9, 10, 4 }, // Seifer
{ 6, 4, 6, 9 } // Squall
} };
private static final List<CardDefinition> CARD_DEFINITIONS = createDefinitions();
private static final Map<ResourceLocation, CardDefinition> CARD_LOOKUP = CARD_DEFINITIONS.stream()
.collect(Collectors.toUnmodifiableMap(CardDefinition::id, Function.identity()));
@@ -37,14 +163,170 @@ public final class CardRegistry {
return definition;
}
private static CardDefinition define(String path, String displayName, int top, int right, int bottom, int left, Rarity rarity) {
private static List<CardDefinition> createDefinitions() {
List<CardDefinition> cards = new ArrayList<>();
registerLevel(cards, 1, Rarity.COMMON,
seed("chicken", "Chicken"),
seed("rabbit", "Rabbit"),
seed("cod", "Cod"),
seed("salmon", "Salmon"),
seed("tropical_fish", "Tropical Fish"),
seed("pufferfish", "Pufferfish"),
seed("tadpole", "Tadpole"),
seed("bat", "Bat"),
seed("bee", "Bee"),
seed("pig", "Pig"),
seed("sheep", "Sheep"));
registerLevel(cards, 2, Rarity.COMMON,
seed("cow", "Cow"),
seed("mooshroom", "Mooshroom"),
seed("goat", "Goat"),
seed("fox", "Fox"),
seed("cat", "Cat"),
seed("wolf", "Wolf"),
seed("frog", "Frog"),
seed("turtle", "Turtle"),
seed("parrot", "Parrot"),
seed("axolotl", "Axolotl"),
seed("ocelot", "Ocelot"));
registerLevel(cards, 3, Rarity.COMMON,
seed("donkey", "Donkey"),
seed("mule", "Mule"),
seed("horse", "Horse"),
seed("llama", "Llama"),
seed("camel", "Camel"),
seed("panda", "Panda"),
seed("polar_bear", "Polar Bear"),
seed("dolphin", "Dolphin"),
seed("squid", "Squid"),
seed("glow_squid", "Glow Squid"),
seed("sniffer", "Sniffer"));
registerLevel(cards, 4, Rarity.UNCOMMON,
seed("spider", "Spider"),
seed("cave_spider", "Cave Spider"),
seed("zombie", "Zombie"),
seed("husk", "Husk"),
seed("drowned", "Drowned"),
seed("skeleton", "Skeleton"),
seed("stray", "Stray"),
seed("bogged", "Bogged"),
seed("slime", "Slime"),
seed("magma_cube", "Magma Cube"),
seed("creeper", "Creeper"));
registerLevel(cards, 5, Rarity.UNCOMMON,
seed("enderman", "Enderman"),
seed("witch", "Witch"),
seed("phantom", "Phantom"),
seed("silverfish", "Silverfish"),
seed("endermite", "Endermite"),
seed("blaze", "Blaze"),
seed("ghast", "Ghast"),
seed("piglin", "Piglin"),
seed("hoglin", "Hoglin"),
seed("zoglin", "Zoglin"),
seed("wither_skeleton", "Wither Skeleton"));
registerLevel(cards, 6, Rarity.RARE,
seed("guardian", "Guardian"),
seed("shulker", "Shulker"),
seed("breeze", "Breeze"),
seed("pillager", "Pillager"),
seed("vindicator", "Vindicator"),
seed("evoker", "Evoker"),
seed("ravager", "Ravager"),
seed("zombified_piglin", "Zombified Piglin"),
seed("piglin_brute", "Piglin Brute"),
seed("zombie_villager", "Zombie Villager"),
seed("villager", "Villager"));
registerLevel(cards, 7, Rarity.RARE,
seed("wandering_trader", "Wandering Trader"),
seed("trader_llama", "Trader Llama"),
seed("iron_golem", "Iron Golem"),
seed("snow_golem", "Snow Golem"),
seed("vex", "Vex"),
seed("allay", "Allay"),
seed("armadillo", "Armadillo"),
seed("skeleton_horse", "Skeleton Horse"),
seed("zombie_horse", "Zombie Horse"),
seed("elder_guardian", "Elder Guardian"),
seed("strider", "Strider"));
registerLevel(cards, 8, Rarity.LEGENDARY,
seed("wither", "Wither"),
seed("ender_dragon", "Ender Dragon"),
seed("beacon", "Beacon"),
seed("conduit", "Conduit"),
seed("nether_star", "Nether Star"),
seed("dragon_egg", "Dragon Egg"),
seed("elytra", "Elytra"),
seed("totem_of_undying", "Totem of Undying"),
seed("ancient_city", "Ancient City"),
seed("trial_spawner", "Trial Spawner"),
seed("vault", "Vault"));
registerLevel(cards, 9, Rarity.LEGENDARY,
seed("stronghold", "Stronghold"),
seed("ocean_monument", "Ocean Monument"),
seed("woodland_mansion", "Woodland Mansion"),
seed("nether_fortress", "Nether Fortress"),
seed("bastion_remnant", "Bastion Remnant"),
seed("end_city", "End City"),
seed("netherite", "Netherite"),
seed("poppy", "Poppy"),
seed("enchanted_golden_apple", "Enchanted Golden Apple"),
seed("mace", "Mace"),
seed("recovery_compass", "Recovery Compass"));
registerLevel(cards, 10, Rarity.LEGENDARY,
seed("jeb", "Jeb"),
seed("dinnerbone", "Dinnerbone"),
seed("cpw", "cpw"),
seed("direwolf20", "DireWolf20"),
seed("xisumavoid", "XisumaVoid"),
seed("docm77", "Docm77"),
seed("ethoslab", "Etho"),
seed("mumbojumbo", "Mumbo Jumbo"),
seed("vintagebeef", "VintageBeef"),
seed("sphax", "Sphax"),
seed("c418", "C418"));
return List.copyOf(cards);
}
private static void registerLevel(List<CardDefinition> cards, int level, Rarity rarity, CardSeed... seeds) {
int[][] levelStats = TT_LEVEL_STATS[level - 1];
if (seeds.length != levelStats.length) {
throw new IllegalStateException("Expected " + levelStats.length + " cards for level " + level + " but got " + seeds.length);
}
for (int i = 0; i < seeds.length; i++) {
CardSeed seed = seeds[i];
cards.add(define(level, seed.path(), seed.displayName(), levelStats[i], rarity));
}
}
private static CardDefinition define(int level, String path, String displayName, int[] pattern, Rarity rarity) {
return new CardDefinition(
ResourceLocation.fromNamespaceAndPath(MineTriad.MOD_ID, path),
Component.literal(displayName),
top,
right,
bottom,
left,
level,
pattern[0],
pattern[1],
pattern[2],
pattern[3],
rarity);
}
private static CardSeed seed(String path, String displayName) {
return new CardSeed(path, displayName);
}
private record CardSeed(String path, String displayName) {
}
}

View File

@@ -0,0 +1,184 @@
package com.trunksbomb.minetriad.client.render;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.math.Axis;
import net.minecraft.client.Minecraft;
import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.entity.EntityRenderDispatcher;
import net.minecraft.client.renderer.entity.player.PlayerRenderer;
import net.minecraft.util.Mth;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.HumanoidArm;
import net.minecraft.world.item.ItemDisplayContext;
import net.minecraft.world.item.ItemStack;
public final class FirstPersonCardHandRenderer {
private FirstPersonCardHandRenderer() {
}
public static void render(
InteractionHand hand,
PoseStack poseStack,
MultiBufferSource buffer,
int packedLight,
float pitch,
float swingProgress,
float equipProgress,
ItemStack stack) {
Minecraft minecraft = Minecraft.getInstance();
AbstractClientPlayer player = minecraft.player;
if (player == null) {
return;
}
HumanoidArm arm = hand == InteractionHand.MAIN_HAND ? player.getMainArm() : player.getMainArm().getOpposite();
boolean useTwoHanded = hand == InteractionHand.MAIN_HAND && player.getOffhandItem().isEmpty();
poseStack.pushPose();
if (useTwoHanded) {
renderTwoHandedCard(minecraft, player, poseStack, buffer, packedLight, pitch, equipProgress, swingProgress, stack);
} else {
renderOneHandedCard(minecraft, player, poseStack, buffer, packedLight, equipProgress, arm, swingProgress, stack);
}
poseStack.popPose();
}
private static void renderTwoHandedCard(
Minecraft minecraft,
AbstractClientPlayer player,
PoseStack poseStack,
MultiBufferSource buffer,
int packedLight,
float pitch,
float equipProgress,
float swingProgress,
ItemStack stack) {
float rootSwing = Mth.sqrt(swingProgress);
float swingY = -0.2F * Mth.sin(swingProgress * (float) Math.PI);
float swingZ = -0.4F * Mth.sin(rootSwing * (float) Math.PI);
poseStack.translate(0.0F, -swingY / 2.0F, swingZ);
float tilt = calculateCardTilt(pitch);
poseStack.translate(0.0F, 0.04F + equipProgress * -1.2F + tilt * -0.5F, -0.72F);
poseStack.mulPose(Axis.XP.rotationDegrees(tilt * -85.0F));
if (!player.isInvisible()) {
poseStack.pushPose();
poseStack.mulPose(Axis.YP.rotationDegrees(90.0F));
renderCardHand(minecraft, player, poseStack, buffer, packedLight, HumanoidArm.RIGHT);
renderCardHand(minecraft, player, poseStack, buffer, packedLight, HumanoidArm.LEFT);
poseStack.popPose();
}
float swingTilt = Mth.sin(rootSwing * (float) Math.PI);
poseStack.mulPose(Axis.XP.rotationDegrees(swingTilt * 20.0F));
poseStack.scale(2.0F, 2.0F, 2.0F);
renderCenteredCard(poseStack, buffer, stack);
}
private static void renderOneHandedCard(
Minecraft minecraft,
AbstractClientPlayer player,
PoseStack poseStack,
MultiBufferSource buffer,
int packedLight,
float equipProgress,
HumanoidArm arm,
float swingProgress,
ItemStack stack) {
float armSign = arm == HumanoidArm.RIGHT ? 1.0F : -1.0F;
poseStack.translate(armSign * 0.51F, -0.08F + equipProgress * -1.2F, -0.75F);
float rootSwing = Mth.sqrt(swingProgress);
float swingSin = Mth.sin(rootSwing * (float) Math.PI);
float swingX = -0.5F * swingSin;
float swingY = 0.4F * Mth.sin(rootSwing * (float) (Math.PI * 2));
float swingZ = -0.3F * Mth.sin(swingProgress * (float) Math.PI);
poseStack.translate(armSign * swingX, swingY - 0.3F * swingSin, swingZ);
poseStack.mulPose(Axis.XP.rotationDegrees(swingSin * -45.0F));
poseStack.mulPose(Axis.YP.rotationDegrees(armSign * swingSin * -30.0F));
if (!player.isInvisible()) {
poseStack.pushPose();
poseStack.mulPose(Axis.ZP.rotationDegrees(armSign * 10.0F));
renderPlayerArm(minecraft, player, poseStack, buffer, packedLight, equipProgress, swingProgress, arm);
poseStack.popPose();
}
renderCenteredCard(poseStack, buffer, stack);
}
private static void renderCenteredCard(PoseStack poseStack, MultiBufferSource buffer, ItemStack stack) {
poseStack.translate(0.0F, 0.0F, -0.18F);
poseStack.scale(0.513F, 0.513F, 0.513F);
TriadCardItemRenderer.renderCard(stack, poseStack, buffer, TriadCardItemRenderer.neutralPalette());
}
private static void renderCardHand(
Minecraft minecraft,
AbstractClientPlayer player,
PoseStack poseStack,
MultiBufferSource buffer,
int packedLight,
HumanoidArm arm) {
PlayerRenderer renderer = getPlayerRenderer(minecraft, player);
poseStack.pushPose();
float armSign = arm == HumanoidArm.RIGHT ? 1.0F : -1.0F;
poseStack.mulPose(Axis.YP.rotationDegrees(92.0F));
poseStack.mulPose(Axis.XP.rotationDegrees(45.0F));
poseStack.mulPose(Axis.ZP.rotationDegrees(armSign * -41.0F));
poseStack.translate(armSign * 0.3F, -1.1F, 0.45F);
if (arm == HumanoidArm.RIGHT) {
renderer.renderRightHand(poseStack, buffer, packedLight, player);
} else {
renderer.renderLeftHand(poseStack, buffer, packedLight, player);
}
poseStack.popPose();
}
private static void renderPlayerArm(
Minecraft minecraft,
AbstractClientPlayer player,
PoseStack poseStack,
MultiBufferSource buffer,
int packedLight,
float equipProgress,
float swingProgress,
HumanoidArm arm) {
PlayerRenderer renderer = getPlayerRenderer(minecraft, player);
float armSign = arm == HumanoidArm.RIGHT ? 1.0F : -1.0F;
float swingRoot = Mth.sqrt(swingProgress);
float swingY = -0.3F * Mth.sin(swingRoot * (float) Math.PI);
float swingZ = 0.4F * Mth.sin(swingRoot * (float) (Math.PI * 2));
float swingX = -0.4F * Mth.sin(swingProgress * (float) Math.PI);
poseStack.translate(armSign * (swingX + 0.64F), swingY + -0.6F + equipProgress * -0.6F, swingZ + -0.72F);
poseStack.mulPose(Axis.YP.rotationDegrees(armSign * 45.0F));
float armSwing = Mth.sin(swingProgress * swingProgress * (float) Math.PI);
float armSwingRoot = Mth.sin(swingRoot * (float) Math.PI);
poseStack.mulPose(Axis.YP.rotationDegrees(armSign * armSwingRoot * 70.0F));
poseStack.mulPose(Axis.ZP.rotationDegrees(armSign * armSwing * -20.0F));
poseStack.translate(armSign * -1.0F, 3.6F, 3.5F);
poseStack.mulPose(Axis.ZP.rotationDegrees(armSign * 120.0F));
poseStack.mulPose(Axis.XP.rotationDegrees(200.0F));
poseStack.mulPose(Axis.YP.rotationDegrees(armSign * -135.0F));
poseStack.translate(armSign * 5.6F, 0.0F, 0.0F);
if (arm == HumanoidArm.RIGHT) {
renderer.renderRightHand(poseStack, buffer, packedLight, player);
} else {
renderer.renderLeftHand(poseStack, buffer, packedLight, player);
}
}
private static PlayerRenderer getPlayerRenderer(Minecraft minecraft, AbstractClientPlayer player) {
EntityRenderDispatcher dispatcher = minecraft.getEntityRenderDispatcher();
return (PlayerRenderer) dispatcher.getRenderer(player);
}
private static float calculateCardTilt(float pitch) {
float tilt = 1.0F - pitch / 67.5F + 0.1F;
tilt = Mth.clamp(tilt, 0.0F, 1.0F);
return -Mth.cos(tilt * (float) Math.PI) * 0.5F + 0.5F;
}
}

View File

@@ -1,6 +1,8 @@
package com.trunksbomb.minetriad.client.render;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import com.mojang.math.Axis;
import com.trunksbomb.minetriad.card.CardDefinition;
import com.trunksbomb.minetriad.card.CardRegistry;
import com.trunksbomb.minetriad.card.CardStackData;
@@ -15,18 +17,25 @@ import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.blockentity.BlockEntityRenderDispatcher;
import net.minecraft.client.renderer.LevelRenderer;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemDisplayContext;
import net.minecraft.world.item.ItemStack;
import net.minecraft.client.renderer.texture.OverlayTexture;
import net.minecraft.util.FormattedCharSequence;
import org.joml.Matrix4f;
public final class TriadCardItemRenderer extends BlockEntityWithoutLevelRenderer {
private static final float TOP_VALUE_Y = 0.28F;
private static final float BOTTOM_VALUE_Y = -0.22F;
private static final float SIDE_VALUE_X = 0.27F;
private static final float SIDE_VALUE_Y = 0.03F;
private static final float CARD_MIN = -0.42F;
private static final float CARD_MAX = 0.42F;
private static final float FRONT_Z = 0.024F;
private static final float BACK_Z = -0.024F;
private static final float BORDER_Z = 0.026F;
private static final float VALUE_Z = 0.030F;
private static final float INNER_MIN = -0.37F;
private static final float INNER_MAX = 0.37F;
private static TriadCardItemRenderer INSTANCE;
private static final CardPalette NEUTRAL_PALETTE = new CardPalette(
new float[] {0.88F, 0.84F, 0.72F},
new float[] {0.97F, 0.93F, 0.80F},
new float[] {0.20F, 0.22F, 0.28F});
private TriadCardItemRenderer(BlockEntityRenderDispatcher blockEntityRenderDispatcher, EntityModelSet entityModelSet) {
@@ -43,55 +52,100 @@ public final class TriadCardItemRenderer extends BlockEntityWithoutLevelRenderer
@Override
public void renderByItem(ItemStack stack, ItemDisplayContext displayContext, PoseStack poseStack, MultiBufferSource buffer, int packedLight, int packedOverlay) {
renderCard(stack, poseStack, buffer, NEUTRAL_PALETTE);
renderCard(stack, poseStack, buffer, NEUTRAL_PALETTE, CardLayout.forDisplayContext(displayContext));
}
public static void renderCard(ItemStack stack, PoseStack poseStack, MultiBufferSource buffer, CardPalette palette) {
renderCard(stack, poseStack, buffer, palette, CardLayout.BOARD);
}
public static CardPalette neutralPalette() {
return NEUTRAL_PALETTE;
}
public static void renderCardForDisplayContext(
ItemStack stack,
PoseStack poseStack,
MultiBufferSource buffer,
CardPalette palette,
ItemDisplayContext displayContext) {
renderCard(stack, poseStack, buffer, palette, CardLayout.forDisplayContext(displayContext));
}
private static void renderCard(ItemStack stack, PoseStack poseStack, MultiBufferSource buffer, CardPalette palette, CardLayout layout) {
CardStackData cardData = stack.get(TriadDataComponents.CARD_DATA);
CardDefinition card = cardData == null || cardData.equals(CardStackData.EMPTY) ? null : CardRegistry.get(cardData.cardId());
poseStack.pushPose();
LevelRenderer.addChainedFilledBoxVertices(
poseStack,
buffer.getBuffer(RenderType.debugFilledBox()),
-0.42F,
-0.42F,
-0.015F,
0.42F,
0.42F,
-0.009F,
palette.border()[0],
palette.border()[1],
palette.border()[2],
1.0F);
LevelRenderer.addChainedFilledBoxVertices(
poseStack,
buffer.getBuffer(RenderType.debugFilledBox()),
-0.32F,
-0.32F,
-0.026F,
0.32F,
0.32F,
-0.016F,
palette.face()[0],
palette.face()[1],
palette.face()[2],
1.0F);
drawBack(poseStack, buffer);
if (card != null) {
drawFrontArt(poseStack, buffer, artTexture(card));
} else {
drawFallbackFace(poseStack, buffer, palette);
}
drawBorderOverlay(poseStack, buffer, palette);
if (card != null) {
Font font = Minecraft.getInstance().font;
drawValue(poseStack, buffer, font, Integer.toString(card.top()), 0.0F, TOP_VALUE_Y);
drawValue(poseStack, buffer, font, Integer.toString(card.bottom()), 0.0F, BOTTOM_VALUE_Y);
drawValue(poseStack, buffer, font, Integer.toString(card.left()), -SIDE_VALUE_X, SIDE_VALUE_Y);
drawValue(poseStack, buffer, font, Integer.toString(card.right()), SIDE_VALUE_X, SIDE_VALUE_Y);
drawValue(poseStack, buffer, font, displayRank(card.top()), 0.0F, layout.topY(), layout.valueScale());
drawValue(poseStack, buffer, font, displayRank(card.bottom()), 0.0F, layout.bottomY(), layout.valueScale());
drawValue(poseStack, buffer, font, displayRank(card.left()), -layout.sideX(), layout.sideY(), layout.valueScale());
drawValue(poseStack, buffer, font, displayRank(card.right()), layout.sideX(), layout.sideY(), layout.valueScale());
}
poseStack.popPose();
}
private static void drawValue(PoseStack poseStack, MultiBufferSource buffer, Font font, String value, float x, float y) {
private static String displayRank(int rank) {
return rank == 10 ? "A" : Integer.toString(rank);
}
private static void drawFallbackFace(PoseStack poseStack, MultiBufferSource buffer, CardPalette palette) {
LevelRenderer.addChainedFilledBoxVertices(
poseStack,
buffer.getBuffer(RenderType.debugFilledBox()),
CARD_MIN,
CARD_MIN,
FRONT_Z,
CARD_MAX,
CARD_MAX,
FRONT_Z + 0.001F,
palette.face()[0],
palette.face()[1],
palette.face()[2],
1.0F);
}
private static void drawBack(PoseStack poseStack, MultiBufferSource buffer) {
drawTexturedQuad(poseStack, buffer, backTexture(), BACK_Z, true);
}
private static void drawBorderOverlay(PoseStack poseStack, MultiBufferSource buffer, CardPalette palette) {
float[] border = palette.border();
drawBorderStrip(poseStack, buffer, CARD_MIN, INNER_MAX, CARD_MAX, CARD_MAX, border);
drawBorderStrip(poseStack, buffer, CARD_MIN, CARD_MIN, CARD_MAX, INNER_MIN, border);
drawBorderStrip(poseStack, buffer, CARD_MIN, INNER_MIN, INNER_MIN, INNER_MAX, border);
drawBorderStrip(poseStack, buffer, INNER_MAX, INNER_MIN, CARD_MAX, INNER_MAX, border);
}
private static void drawBorderStrip(PoseStack poseStack, MultiBufferSource buffer, float minX, float minY, float maxX, float maxY, float[] color) {
LevelRenderer.addChainedFilledBoxVertices(
poseStack,
buffer.getBuffer(RenderType.debugFilledBox()),
minX,
minY,
BORDER_Z,
maxX,
maxY,
BORDER_Z + 0.001F,
color[0],
color[1],
color[2],
1.0F);
}
private static void drawValue(PoseStack poseStack, MultiBufferSource buffer, Font font, String value, float x, float y, float scale) {
poseStack.pushPose();
poseStack.translate(x, y, 0.03F);
poseStack.scale(0.03F, -0.03F, 0.03F);
poseStack.translate(x, y, VALUE_Z);
poseStack.scale(scale, -scale, scale);
float width = font.width(value);
FormattedCharSequence sequence = FormattedCharSequence.forward(value, net.minecraft.network.chat.Style.EMPTY);
font.drawInBatch8xOutline(
@@ -106,6 +160,57 @@ public final class TriadCardItemRenderer extends BlockEntityWithoutLevelRenderer
poseStack.popPose();
}
private static void drawFrontArt(PoseStack poseStack, MultiBufferSource buffer, ResourceLocation textureLocation) {
drawTexturedQuad(poseStack, buffer, textureLocation, FRONT_Z, false);
}
private static void drawTexturedQuad(PoseStack poseStack, MultiBufferSource buffer, ResourceLocation textureLocation, float z, boolean reverseWinding) {
VertexConsumer vertexConsumer = buffer.getBuffer(RenderType.entityCutoutNoCull(textureLocation));
Matrix4f matrix = poseStack.last().pose();
if (reverseWinding) {
addArtVertex(vertexConsumer, matrix, CARD_MIN, CARD_MIN, z, 0.0F, 1.0F, -1.0F);
addArtVertex(vertexConsumer, matrix, CARD_MAX, CARD_MIN, z, 1.0F, 1.0F, -1.0F);
addArtVertex(vertexConsumer, matrix, CARD_MAX, CARD_MAX, z, 1.0F, 0.0F, -1.0F);
addArtVertex(vertexConsumer, matrix, CARD_MIN, CARD_MAX, z, 0.0F, 0.0F, -1.0F);
} else {
addArtVertex(vertexConsumer, matrix, CARD_MIN, CARD_MAX, z, 0.0F, 0.0F, 1.0F);
addArtVertex(vertexConsumer, matrix, CARD_MAX, CARD_MAX, z, 1.0F, 0.0F, 1.0F);
addArtVertex(vertexConsumer, matrix, CARD_MAX, CARD_MIN, z, 1.0F, 1.0F, 1.0F);
addArtVertex(vertexConsumer, matrix, CARD_MIN, CARD_MIN, z, 0.0F, 1.0F, 1.0F);
}
}
private static void addArtVertex(VertexConsumer vertexConsumer, Matrix4f matrix, float x, float y, float z, float u, float v, float normalZ) {
vertexConsumer.addVertex(matrix, x, y, z)
.setColor(255, 255, 255, 255)
.setUv(u, v)
.setOverlay(OverlayTexture.NO_OVERLAY)
.setLight(LightTexture.FULL_BRIGHT)
.setNormal(0.0F, 0.0F, normalZ);
}
private static ResourceLocation artTexture(CardDefinition card) {
ResourceLocation id = card.id();
return ResourceLocation.fromNamespaceAndPath(id.getNamespace(), "textures/item/cards/" + id.getPath() + ".png");
}
private static ResourceLocation backTexture() {
return ResourceLocation.fromNamespaceAndPath("minetriad", "textures/item/card_back.png");
}
public record CardPalette(float[] border, float[] face) {
}
private record CardLayout(float topY, float bottomY, float sideX, float sideY, float valueScale) {
private static final CardLayout BOARD = new CardLayout(0.41F, -0.29F, 0.35F, 0.06F, 0.03F);
private static final CardLayout ITEM = new CardLayout(0.26F, -0.17F, 0.21F, 0.03F, 0.03F);
private static CardLayout forDisplayContext(ItemDisplayContext displayContext) {
return switch (displayContext) {
case GUI, GROUND, FIXED, FIRST_PERSON_LEFT_HAND, FIRST_PERSON_RIGHT_HAND,
THIRD_PERSON_LEFT_HAND, THIRD_PERSON_RIGHT_HAND -> ITEM;
default -> BOARD;
};
}
}
}

View File

@@ -0,0 +1,122 @@
package com.trunksbomb.minetriad.client.screen;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.math.Axis;
import com.trunksbomb.minetriad.MineTriad;
import com.trunksbomb.minetriad.client.render.TriadCardItemRenderer;
import com.trunksbomb.minetriad.menu.CardBinderMenu;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.components.Button;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
public class CardBinderScreen extends AbstractContainerScreen<CardBinderMenu> {
private static final ResourceLocation TEXTURE = ResourceLocation.fromNamespaceAndPath(MineTriad.MOD_ID, "textures/gui/card_binder.png");
private static final ResourceLocation SELECTOR_TEXTURE = ResourceLocation.fromNamespaceAndPath(MineTriad.MOD_ID, "textures/gui/card_binder_selector.png");
private Button previousPageButton;
private Button nextPageButton;
private Button handsButton;
public CardBinderScreen(CardBinderMenu menu, Inventory playerInventory, Component title) {
super(menu, playerInventory, title);
imageWidth = 248;
imageHeight = 222;
inventoryLabelY = imageHeight - 92;
}
@Override
protected void init() {
super.init();
previousPageButton = addRenderableWidget(Button.builder(Component.literal("<"), button -> pressMenuButton(CardBinderMenu.BUTTON_PREVIOUS_PAGE))
.bounds(leftPos + 48, topPos + 108, 20, 20)
.build());
nextPageButton = addRenderableWidget(Button.builder(Component.literal(">"), button -> pressMenuButton(CardBinderMenu.BUTTON_NEXT_PAGE))
.bounds(leftPos + 72, topPos + 108, 20, 20)
.build());
handsButton = addRenderableWidget(Button.builder(Component.translatable("gui.minetriad.card_binder.hands"), button -> pressMenuButton(CardBinderMenu.BUTTON_HANDS))
.bounds(leftPos + 48, topPos + 86, 44, 20)
.build());
handsButton.active = false;
updateButtons();
}
@Override
public void containerTick() {
super.containerTick();
updateButtons();
}
@Override
protected void renderBg(GuiGraphics guiGraphics, float partialTick, int mouseX, int mouseY) {
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
guiGraphics.blit(TEXTURE, leftPos, topPos, 0, 0, imageWidth, imageHeight, imageWidth, imageHeight);
}
@Override
protected void renderLabels(GuiGraphics guiGraphics, int mouseX, int mouseY) {
guiGraphics.drawString(font, title, 8, 6, 0x2E2418, false);
guiGraphics.drawString(font, Component.translatable("gui.minetriad.card_binder.level", menu.currentLevel()), 48, 76, 0x2E2418, false);
guiGraphics.drawString(font, Component.translatable("gui.minetriad.card_binder.preview"), 138, 12, 0x2E2418, false);
guiGraphics.drawString(font, playerInventoryTitle, inventoryLabelX, inventoryLabelY, 0x2E2418, false);
}
@Override
public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) {
renderBackground(guiGraphics, mouseX, mouseY, partialTick);
super.render(guiGraphics, mouseX, mouseY, partialTick);
renderSelection(guiGraphics);
renderPreview(guiGraphics, partialTick);
renderTooltip(guiGraphics, mouseX, mouseY);
}
private void pressMenuButton(int buttonId) {
if (minecraft != null && minecraft.gameMode != null) {
minecraft.gameMode.handleInventoryButtonClick(menu.containerId, buttonId);
updateButtons();
}
}
private void updateButtons() {
previousPageButton.active = menu.currentPage() > 0;
nextPageButton.active = menu.currentPage() < CardBinderMenu.PAGE_COUNT - 1;
}
private void renderSelection(GuiGraphics guiGraphics) {
int selectedSlotIndex = menu.selectedBinderSlot();
if (selectedSlotIndex < 0 || selectedSlotIndex >= menu.slots.size()) {
return;
}
Slot slot = menu.slots.get(selectedSlotIndex);
if (!slot.isActive() || !slot.hasItem()) {
return;
}
guiGraphics.blit(SELECTOR_TEXTURE, leftPos + slot.x, topPos + slot.y, 0, 0, 16, 16, 16, 16);
}
private void renderPreview(GuiGraphics guiGraphics, float partialTick) {
ItemStack previewStack = menu.previewStack();
if (previewStack.isEmpty() || minecraft == null || minecraft.level == null) {
return;
}
float rotation = ((minecraft.level.getGameTime() + partialTick) * 2.4F) % 360.0F;
guiGraphics.pose().pushPose();
guiGraphics.pose().translate(leftPos + 184.0F, topPos + 71.0F, 200.0F);
guiGraphics.pose().mulPose(Axis.XP.rotationDegrees(18.0F));
guiGraphics.pose().mulPose(Axis.YP.rotationDegrees(rotation));
guiGraphics.pose().scale(84.0F, -84.0F, 84.0F);
TriadCardItemRenderer.renderCard(previewStack, guiGraphics.pose(), minecraft.renderBuffers().bufferSource(), TriadCardItemRenderer.neutralPalette());
minecraft.renderBuffers().bufferSource().endBatch();
guiGraphics.pose().popPose();
}
}

View File

@@ -0,0 +1,54 @@
package com.trunksbomb.minetriad.compat.jei;
import java.util.List;
import com.trunksbomb.minetriad.MineTriad;
import com.trunksbomb.minetriad.card.CardRegistry;
import com.trunksbomb.minetriad.card.CardStackData;
import com.trunksbomb.minetriad.item.CardItem;
import com.trunksbomb.minetriad.registry.TriadDataComponents;
import com.trunksbomb.minetriad.registry.TriadItems;
import mezz.jei.api.IModPlugin;
import mezz.jei.api.JeiPlugin;
import mezz.jei.api.ingredients.subtypes.ISubtypeInterpreter;
import mezz.jei.api.registration.IExtraIngredientRegistration;
import mezz.jei.api.registration.ISubtypeRegistration;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
@JeiPlugin
public final class MineTriadJeiPlugin implements IModPlugin {
private static final ResourceLocation PLUGIN_UID = ResourceLocation.fromNamespaceAndPath(MineTriad.MOD_ID, "jei_plugin");
private static final ISubtypeInterpreter<ItemStack> CARD_SUBTYPE = new ISubtypeInterpreter<>() {
@Override
public Object getSubtypeData(ItemStack stack, mezz.jei.api.ingredients.subtypes.UidContext context) {
CardStackData data = stack.get(TriadDataComponents.CARD_DATA);
return data == null || CardStackData.EMPTY.equals(data) ? null : data.cardId().toString();
}
@Override
public String getLegacyStringSubtypeInfo(ItemStack stack, mezz.jei.api.ingredients.subtypes.UidContext context) {
CardStackData data = stack.get(TriadDataComponents.CARD_DATA);
return data == null || CardStackData.EMPTY.equals(data) ? "" : data.cardId().toString();
}
};
@Override
public ResourceLocation getPluginUid() {
return PLUGIN_UID;
}
@Override
public void registerItemSubtypes(ISubtypeRegistration registration) {
registration.registerSubtypeInterpreter(TriadItems.TRIAD_CARD.get(), CARD_SUBTYPE);
}
@Override
public void registerExtraIngredients(IExtraIngredientRegistration registration) {
List<ItemStack> cardStacks = CardRegistry.all().stream()
.map(card -> CardItem.createCardStack(card.id(), TriadItems.TRIAD_CARD.get()))
.toList();
registration.addExtraItemStacks(cardStacks);
}
}

View File

@@ -0,0 +1,31 @@
package com.trunksbomb.minetriad.item;
import com.trunksbomb.minetriad.menu.CardBinderMenu;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResultHolder;
import net.minecraft.world.SimpleMenuProvider;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
public class CardBinderItem extends Item {
public CardBinderItem(Properties properties) {
super(properties);
}
@Override
public InteractionResultHolder<ItemStack> use(Level level, Player player, InteractionHand usedHand) {
ItemStack stack = player.getItemInHand(usedHand);
if (player instanceof ServerPlayer serverPlayer) {
serverPlayer.openMenu(
new SimpleMenuProvider(
(containerId, inventory, menuPlayer) -> new CardBinderMenu(containerId, inventory, usedHand),
stack.getHoverName()),
buffer -> buffer.writeEnum(usedHand));
}
return InteractionResultHolder.sidedSuccess(stack, level.isClientSide());
}
}

View File

@@ -30,7 +30,9 @@ public class CardItem extends Item {
}
CardDefinition card = CardRegistry.get(cardData.cardId());
return card.name().copy().withStyle(card.rarity().formatting());
return Component.literal("[MT:" + card.level() + "] ")
.append(card.name().copy())
.withStyle(card.rarity().formatting());
}
@Override
@@ -42,6 +44,7 @@ public class CardItem extends Item {
}
CardDefinition card = CardRegistry.get(cardData.cardId());
tooltipComponents.add(Component.literal("Level " + card.level()).withStyle(ChatFormatting.GRAY));
tooltipComponents.add(Component.literal("Top " + card.top() + " Right " + card.right()).withStyle(ChatFormatting.GRAY));
tooltipComponents.add(Component.literal("Bottom " + card.bottom() + " Left " + card.left()).withStyle(ChatFormatting.GRAY));
tooltipComponents.add(Component.literal(card.rarity().name()).withStyle(card.rarity().formatting()));

View File

@@ -0,0 +1,312 @@
package com.trunksbomb.minetriad.menu;
import java.util.List;
import com.trunksbomb.minetriad.card.CardDefinition;
import com.trunksbomb.minetriad.card.CardRegistry;
import com.trunksbomb.minetriad.card.CardStackData;
import com.trunksbomb.minetriad.registry.TriadDataComponents;
import com.trunksbomb.minetriad.registry.TriadItems;
import com.trunksbomb.minetriad.registry.TriadMenus;
import net.minecraft.core.NonNullList;
import net.minecraft.core.component.DataComponents;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.SimpleContainer;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.ClickType;
import net.minecraft.world.inventory.DataSlot;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.component.ItemContainerContents;
public class CardBinderMenu extends AbstractContainerMenu {
public static final int CARDS_PER_LEVEL = 11;
public static final int PAGE_COUNT = 10;
public static final int BINDER_SLOT_COUNT = PAGE_COUNT * CARDS_PER_LEVEL;
public static final int BUTTON_PREVIOUS_PAGE = 0;
public static final int BUTTON_NEXT_PAGE = 1;
public static final int BUTTON_HANDS = 2;
public static final int NO_SELECTION = -1;
private static final int SLOT_X_START = 8;
private static final int SLOT_Y_START = 22;
private static final int SLOT_SPACING = 18;
private final InteractionHand hand;
private final SimpleContainer binderContents;
private final int lockedHotbarSlot;
private int currentPage;
private int selectedBinderSlot = NO_SELECTION;
public CardBinderMenu(int containerId, Inventory inventory, FriendlyByteBuf extraData) {
this(containerId, inventory, extraData.readEnum(InteractionHand.class));
}
public CardBinderMenu(int containerId, Inventory inventory, InteractionHand hand) {
super(TriadMenus.CARD_BINDER.get(), containerId);
this.hand = hand;
this.lockedHotbarSlot = hand == InteractionHand.MAIN_HAND ? inventory.selected : -1;
this.binderContents = new SimpleContainer(BINDER_SLOT_COUNT) {
@Override
public void setChanged() {
super.setChanged();
saveBinderContents(inventory.player);
}
};
loadBinderContents(inventory.player);
addDataSlot(new DataSlot() {
@Override
public int get() {
return currentPage;
}
@Override
public void set(int value) {
currentPage = Math.clamp(value, 0, PAGE_COUNT - 1);
}
});
addDataSlot(new DataSlot() {
@Override
public int get() {
return selectedBinderSlot;
}
@Override
public void set(int value) {
selectedBinderSlot = value >= 0 && value < BINDER_SLOT_COUNT ? value : NO_SELECTION;
}
});
addBinderSlots();
addPlayerInventory(inventory);
}
public int currentPage() {
return currentPage;
}
public int currentLevel() {
return currentPage + 1;
}
public int selectedBinderSlot() {
return selectedBinderSlot;
}
public ItemStack previewStack() {
if (selectedBinderSlot < 0 || selectedBinderSlot >= BINDER_SLOT_COUNT) {
return ItemStack.EMPTY;
}
ItemStack stack = binderContents.getItem(selectedBinderSlot);
return stack.isEmpty() ? ItemStack.EMPTY : stack;
}
@Override
public boolean stillValid(Player player) {
return player.getItemInHand(hand).is(TriadItems.CARD_BINDER.get());
}
@Override
public ItemStack quickMoveStack(Player player, int index) {
Slot slot = slots.get(index);
if (!slot.hasItem()) {
return ItemStack.EMPTY;
}
ItemStack source = slot.getItem();
ItemStack copy = source.copy();
if (index < BINDER_SLOT_COUNT) {
if (!moveItemStackTo(source, BINDER_SLOT_COUNT, slots.size(), false)) {
return ItemStack.EMPTY;
}
} else {
int binderStart = binderRangeStartFor(source);
if (binderStart < 0 || !moveItemStackTo(source, binderStart, binderStart + CARDS_PER_LEVEL, false)) {
return ItemStack.EMPTY;
}
}
if (source.isEmpty()) {
slot.set(ItemStack.EMPTY);
} else {
slot.setChanged();
}
normalizeSelection();
return copy;
}
@Override
public boolean clickMenuButton(Player player, int id) {
if (id == BUTTON_PREVIOUS_PAGE && currentPage > 0) {
currentPage--;
broadcastChanges();
return true;
}
if (id == BUTTON_NEXT_PAGE && currentPage < PAGE_COUNT - 1) {
currentPage++;
broadcastChanges();
return true;
}
if (id == BUTTON_HANDS) {
return true;
}
return false;
}
@Override
public void removed(Player player) {
clearSelection();
saveBinderContents(player);
super.removed(player);
}
@Override
public void clicked(int slotId, int button, ClickType clickType, Player player) {
if (slotId >= 0 && slotId < BINDER_SLOT_COUNT && clickType == ClickType.PICKUP && getCarried().isEmpty()) {
Slot slot = slots.get(slotId);
if (slot.hasItem()) {
if (selectedBinderSlot == slotId) {
super.clicked(slotId, 1, ClickType.PICKUP, player);
normalizeSelection();
} else {
selectedBinderSlot = slotId;
broadcastChanges();
}
saveBinderContents(player);
return;
}
}
super.clicked(slotId, button, clickType, player);
normalizeSelection();
saveBinderContents(player);
}
private void addBinderSlots() {
for (int page = 0; page < PAGE_COUNT; page++) {
for (int slotOnPage = 0; slotOnPage < CARDS_PER_LEVEL; slotOnPage++) {
int slotIndex = page * CARDS_PER_LEVEL + slotOnPage;
int row = slotOnPage / 2;
int column = slotOnPage % 2;
int x = SLOT_X_START + column * SLOT_SPACING;
int y = SLOT_Y_START + row * SLOT_SPACING;
addSlot(new BinderSlot(binderContents, slotIndex, x, y, page, page + 1));
}
}
}
private void addPlayerInventory(Inventory inventory) {
for (int row = 0; row < 3; row++) {
for (int column = 0; column < 9; column++) {
addSlot(new Slot(inventory, column + row * 9 + 9, 8 + column * 18, 140 + row * 18));
}
}
for (int slot = 0; slot < 9; slot++) {
int inventoryIndex = slot;
int x = 8 + slot * 18;
int y = 198;
if (slot == lockedHotbarSlot) {
addSlot(new LockedSlot(inventory, inventoryIndex, x, y));
} else {
addSlot(new Slot(inventory, inventoryIndex, x, y));
}
}
}
private void loadBinderContents(Player player) {
ItemStack binderStack = player.getItemInHand(hand);
ItemContainerContents contents = binderStack.getOrDefault(DataComponents.CONTAINER, ItemContainerContents.EMPTY);
NonNullList<ItemStack> items = NonNullList.withSize(BINDER_SLOT_COUNT, ItemStack.EMPTY);
contents.copyInto(items);
for (int i = 0; i < BINDER_SLOT_COUNT; i++) {
binderContents.setItem(i, i < items.size() ? items.get(i) : ItemStack.EMPTY);
}
}
private void saveBinderContents(Player player) {
ItemStack binderStack = player.getItemInHand(hand);
if (!binderStack.is(TriadItems.CARD_BINDER.get())) {
return;
}
NonNullList<ItemStack> items = NonNullList.withSize(BINDER_SLOT_COUNT, ItemStack.EMPTY);
for (int i = 0; i < BINDER_SLOT_COUNT; i++) {
items.set(i, binderContents.getItem(i));
}
binderStack.set(DataComponents.CONTAINER, ItemContainerContents.fromItems(items));
}
private void normalizeSelection() {
if (selectedBinderSlot < 0 || selectedBinderSlot >= BINDER_SLOT_COUNT || binderContents.getItem(selectedBinderSlot).isEmpty()) {
clearSelection();
}
}
private void clearSelection() {
selectedBinderSlot = NO_SELECTION;
}
private static int binderRangeStartFor(ItemStack stack) {
CardStackData data = stack.get(TriadDataComponents.CARD_DATA);
if (!stack.is(TriadItems.TRIAD_CARD.get()) || data == null || data.equals(CardStackData.EMPTY)) {
return -1;
}
CardDefinition definition = CardRegistry.get(data.cardId());
return (definition.level() - 1) * CARDS_PER_LEVEL;
}
private final class BinderSlot extends Slot {
private final int pageIndex;
private final int expectedLevel;
private BinderSlot(SimpleContainer container, int slot, int x, int y, int pageIndex, int expectedLevel) {
super(container, slot, x, y);
this.pageIndex = pageIndex;
this.expectedLevel = expectedLevel;
}
@Override
public boolean mayPlace(ItemStack stack) {
CardStackData data = stack.get(TriadDataComponents.CARD_DATA);
if (!stack.is(TriadItems.TRIAD_CARD.get()) || data == null || data.equals(CardStackData.EMPTY)) {
return false;
}
return CardRegistry.get(data.cardId()).level() == expectedLevel;
}
@Override
public int getMaxStackSize() {
return 1;
}
@Override
public boolean isActive() {
return pageIndex == currentPage;
}
}
private static final class LockedSlot extends Slot {
private LockedSlot(Inventory inventory, int slot, int x, int y) {
super(inventory, slot, x, y);
}
@Override
public boolean mayPickup(Player player) {
return false;
}
@Override
public boolean mayPlace(ItemStack stack) {
return false;
}
}
}

View File

@@ -2,10 +2,13 @@ package com.trunksbomb.minetriad.registry;
import com.trunksbomb.minetriad.MineTriad;
import com.trunksbomb.minetriad.item.CardItem;
import com.trunksbomb.minetriad.item.CardBinderItem;
import com.trunksbomb.minetriad.item.DeckBoxItem;
import net.minecraft.core.component.DataComponents;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.component.ItemContainerContents;
import net.neoforged.bus.api.IEventBus;
import net.neoforged.neoforge.registries.DeferredItem;
import net.neoforged.neoforge.registries.DeferredRegister;
@@ -17,7 +20,9 @@ public final class TriadItems {
public static final DeferredItem<Item> TRIAD_CARD = ITEMS.register("triad_card", () -> new CardItem(DEFAULT_PROPERTIES.stacksTo(1)));
public static final DeferredItem<Item> DECK_BOX = ITEMS.register("deck_box", () -> new DeckBoxItem(DEFAULT_PROPERTIES.stacksTo(1)));
public static final DeferredItem<Item> CARD_BINDER = ITEMS.registerSimpleItem("card_binder", DEFAULT_PROPERTIES.stacksTo(1));
public static final DeferredItem<Item> CARD_BINDER = ITEMS.register(
"card_binder",
() -> new CardBinderItem(DEFAULT_PROPERTIES.stacksTo(1).component(DataComponents.CONTAINER, ItemContainerContents.EMPTY)));
public static final DeferredItem<BlockItem> DUEL_TABLE = ITEMS.registerSimpleBlockItem("duel_table", TriadBlocks.DUEL_TABLE);
private TriadItems() {

View File

@@ -0,0 +1,26 @@
package com.trunksbomb.minetriad.registry;
import com.trunksbomb.minetriad.MineTriad;
import com.trunksbomb.minetriad.menu.CardBinderMenu;
import net.minecraft.core.registries.Registries;
import net.minecraft.world.inventory.MenuType;
import net.neoforged.bus.api.IEventBus;
import net.neoforged.neoforge.common.extensions.IMenuTypeExtension;
import net.neoforged.neoforge.registries.DeferredHolder;
import net.neoforged.neoforge.registries.DeferredRegister;
public final class TriadMenus {
private static final DeferredRegister<MenuType<?>> MENUS = DeferredRegister.create(Registries.MENU, MineTriad.MOD_ID);
public static final DeferredHolder<MenuType<?>, MenuType<CardBinderMenu>> CARD_BINDER = MENUS.register(
"card_binder",
() -> IMenuTypeExtension.create(CardBinderMenu::new));
private TriadMenus() {
}
public static void register(IEventBus eventBus) {
MENUS.register(eventBus);
}
}

View File

@@ -4,5 +4,8 @@
"block.minetriad.duel_table": "Duel Table",
"item.minetriad.triad_card": "Triad Card",
"item.minetriad.deck_box": "Deck Box",
"item.minetriad.card_binder": "Card Binder"
"item.minetriad.card_binder": "Card Binder",
"gui.minetriad.card_binder.level": "Level %s",
"gui.minetriad.card_binder.preview": "Card Preview",
"gui.minetriad.card_binder.hands": "Hands"
}

View File

@@ -1,6 +1,40 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "minetriad:item/triad_card"
"parent": "minecraft:builtin/entity",
"display": {
"thirdperson_righthand": {
"rotation": [0, -90, 35],
"translation": [0, 2.5, 0],
"scale": [0.75, 0.75, 0.75]
},
"thirdperson_lefthand": {
"rotation": [0, 90, -35],
"translation": [0, 2.5, 0],
"scale": [0.75, 0.75, 0.75]
},
"firstperson_righthand": {
"rotation": [0, 0, 0],
"translation": [0, 0, 0],
"scale": [1, 1, 1]
},
"firstperson_lefthand": {
"rotation": [0, 0, 0],
"translation": [0, 0, 0],
"scale": [1, 1, 1]
},
"gui": {
"rotation": [0, 0, 0],
"translation": [8, 8, 0],
"scale": [1, 1, 1]
},
"ground": {
"rotation": [0, 0, 0],
"translation": [0, 2, 0],
"scale": [0.5, 0.5, 0.5]
},
"fixed": {
"rotation": [0, 180, 0],
"translation": [0, 0, 0],
"scale": [1, 1, 1]
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 496 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 431 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 505 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 532 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 416 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 434 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 425 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 441 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 429 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 436 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 503 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 519 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 441 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 420 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 478 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 513 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 452 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 448 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 496 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 522 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 449 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 620 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 461 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 511 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 461 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 408 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 455 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 451 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 433 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 493 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 422 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 517 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 524 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 531 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 497 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 461 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 533 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 455 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 445 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 510 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 455 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 449 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 472 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 453 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 548 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 443 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 476 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 530 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 510 B

Some files were not shown because too many files have changed in this diff Show More