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

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

View File

@@ -1,25 +1,61 @@
# Mine Triad
Installation information
=======
Mine Triad is a NeoForge mod for Minecraft 1.21.1 that brings a Minecraft-themed take on Triple Triad into the game. Collect custom cards, organize them in a binder, and challenge a built-in training opponent on a physical duel table placed in the world.
This template repository can be directly cloned to get you started with a new
mod. Simply create a new repository cloned from this one, by following the
instructions provided by [GitHub](https://docs.github.com/en/repositories/creating-and-managing-repositories/creating-a-repository-from-a-template).
This project is currently focused on delivering a polished core card game experience: high-quality card presentation, a full starter card pool, an in-world board, and a clean collection UI.
Once you have your clone, simply open the repository in the IDE of your choice. The usual recommendation for an IDE is either IntelliJ IDEA or Eclipse.
## Features
If at any point you are missing libraries in your IDE, or you've run into problems you can
run `gradlew --refresh-dependencies` to refresh the local cache. `gradlew clean` to reset everything
{this does not affect your code} and then start the process again.
- 110 unique Triad cards across 10 progression levels
- Minecraft-themed card roster with custom names, art, rarity tiers, and stat layouts
- In-world `Duel Table` block with a playable 3x3 Triple Triad-style board
- Solo duels against a built-in `Training Duelist`
- Custom first-person and item rendering for cards
- `Card Binder` UI for organizing and previewing your collection by level
- JEI integration so every card variant can be browsed directly in item lookup
- Separate creative tabs for cards and gameplay items
Mapping Names:
============
By default, the MDK is configured to use the official mapping names from Mojang for methods and fields
in the Minecraft codebase. These names are covered by a specific license. All modders should be aware of this
license. For the latest license text, refer to the mapping file itself, or the reference copy here:
https://github.com/NeoForged/NeoForm/blob/main/Mojang.md
## What Is Implemented Right Now
Additional Resources:
==========
Community Documentation: https://docs.neoforged.net/
NeoForged Discord: https://discord.neoforged.net/
Mine Triad already includes a complete playable duel loop:
1. Put at least 5 Triad Cards in your inventory.
2. Place a Duel Table.
3. Right-click the top face of the table to start a duel.
4. Hold one of your duel cards and click an open board space to play your turn.
5. Alternate turns with the Training Duelist until the board is full.
6. Right-click the table again after the result screen to finish the match and return cards.
The current AI opponent evaluates legal moves and prefers plays that capture the most cards.
## Card Set Overview
- `Levels 1-3`: common, lower-power wildlife and passive mob inspired cards
- `Levels 4-5`: uncommon hostile mob focused cards
- `Levels 6-7`: rare advanced and elite encounter cards
- `Levels 8-10`: legendary endgame, structure, item, and community-inspired cards
The stat layouts are intentionally modeled after the progression curve of classic Triple Triad, but the card identities are reimagined around Minecraft.
## Items And Blocks
### Triad Card
The core collectible item. Each stack stores its own card identity as item data, renders as an individual card, and shows its level, sides, and rarity in the tooltip.
### Duel Table
The main gameplay block used to play matches in-world. The board state is shown directly on the table during a duel, and the block prevents normal survival breaking while a match is active.
### Card Binder
A collection binder with 110 slots arranged by card level. It supports:
- per-level paging
- persistent storage inside the binder item
- previewing the selected card in the UI
- locked held-slot behavior so the binder cannot be moved out from under the menu
### Deck Box
The item is present in the current build as part of the gameplay set, but deck management behavior is not implemented yet.

817
TTCardReference.txt Normal file
View File

@@ -0,0 +1,817 @@
-Level 1 Monster Cards-
-----------------------
1
5 4
1
Geezard
Non Elemental
1 Card = 5 Screws
5
3 1
1
Funguar
Non Elemental
1 Card = 1 M-Stone Piece
1
5 3
3
Bite Bug
Non Elemental
1 Card = 1 M-Stone Piece
6
2 1
1
Red Bat
Non Elemental
1 Card = 1 Vampire Fang
2
5 3
1
Blobra
Non Elemental
4 Cards = 1 Rune Armlet
2
4 1
4
Gayla
Thunder Elemental
1 Card = 1 Mystery Fluid
1
1 5
4
Gesper
Non Elemental
1 Card = 1 Black Hole
3
1 5
2
Fastitocalon-F
Earth Elemental
5 Cards = 1 Water Crystal
2
1 1
6
Blood Soul
Non Elemental
1 Card = 1 Zombie Powder
4
3 2
4
Caterchipillar
Non Elemental
1 Card = 1 Spider Web
2
6 1
2
Cockatrice
Thunder Elemental
1 Card = 1 Cockatrice Pinion
----------------------------
-Level 2 Monster Cards-
-----------------------
7
1 1
3
Grat
Non Elemental
1 Card = 1 Magic Stone
6
3 2
2
Buel
Non Elemental
1 Card = 1 Magic Stone
5
4 3
3
Mesmerize
Non Elemental
1 Card = 1 Mesmerize Blade
6
3 1
4
Glacial Eye
Ice Elemental
1 Card = 1 Arctic Wind
3
3 4
5
Belhelmel
Non Elemental
1 Card = 1 Saw Blade
5
5 3
2
Thrustaevis
Wind Elemental
1 Card = 1 Shear Feather
5
5 1
3
Anacondaur
Poison Elemental
1 Card = 1 Venom Fang
5
2 2
5
Creeps
Thunder Elemental
1 Card = 1 Coral Fragment
4
2 4
5
Grendal
Thunder Elemental
1 Card = 1 Dragon Fin
3
7 2
1
Jelleye
Non Elemental
1 Card = 1 Magic Stone
5
3 2
5
Grand Mantis
Non Elemental
1 Card = 1 Sharp Spike
----------------------------
-Level 3 Monster Cards-
-----------------------
6
2 6
3
Forbidden
Non Elemental
1 Card = 1 Betrayal Sword
6
6 3
1
Armadodo
Earth Elemental
1 Card = 1 Dino Bone
3
5 5
5
Tri-Face
Poison Elemental
1 Card = 1 Curse Spike
7
3 5
1
Fastitocalon
Earth Elemental
1 Card = 1 Water Crystal
7
3 1
5
Snow Lion
Ice Elemental
1 Card = 1 North Wind
5
3 6
3
Ochu
Non Elemental
1 Card = 1 Ochu Tentacle
5
4 6
2
SAM08G
Fire Elemental
1 Card = 1 Running Fire
4
2 4
7
Death Claw
Fire Elemental
1 Card = 1 Sharp Spike
6
3 2
6
Cactuar
Non Elemental
1 Card = 1 Cactus Thorn
3
4 6
4
Tonberry
Non Elemental
1 Card = 1 Chef's Knife
7
5 2
3
Abyss Worm
Earth Elemental
1 Card = 1 Windmill
----------------------------
-Level 4 Monster Cards-
-----------------------
2
7 6
3
Turtapod
Non Elemental
5 Cards = 1 Healing Mail
6
5 5
4
Vysage
Non Elemental
1 Card = 1 Wizard Stone
4
7 6
2
T-Rexaur
Non Elemental
2 Cards = 1 Dino Bone
2
3 7
6
Bomb
Fire Elemental
1 Card = 1 Bomb Fragment
1
7 6
4
Blitz
Thunder Elemental
1 Card = 1 Dynamite Stone
7
6 3
1
Wendigo
Non Elemental
1 Card = 1 Steel Orb
7
4 4
4
Torama
Non Elemental
5 Cards = 1 Life Ring
3
6 7
3
Imp
Non Elemental
1 Card = 1 Wizard Stone
6
3 2
7
Blue Dragon
Poison Elemental
4 Cards = 1 Fury Fragment
4
6 5
5
Adamantoise
Earth Elemental
3 Cards = 1 Turtle Shell
7
3 5
4
HexDragon
Non Elemental
1 Card = 1 Sharp Spike
----------------------------
-Level 5 Monster Cards-
-----------------------
6
5 5
6
Iron Giant
Non Elemental
3 Cards = 1 Star Fragment
3
7 6
5
Behemoth
Earth Elemental
10 Cards = 1 Barrier
7
3 6
5
Chimera
Water Elemental
10 Cards = 1 Regen Ring
3
1 A
2
Pupu (Rare Card - Need to Complete the UFO Chase to Acquire)
Non Elemental
1 Card = 1 Hungry Cookpot
6
7 2
6
Elastoid
Non Elemental
1 Card = 1 Steel Pipe
5
4 5
7
GIM47N
Non Elemental
1 Card = 10 Fast Ammo
7
2 7
4
Malboro
Poison Elemental
4 Cards = 1 Malboro Tentacle
7
4 2
7
Ruby Dragon
Fire Elemental
10 Cards = 1 Inferno Ring
5
6 3
7
Elnoyle
Non Elemental
10 Cards = 1 Energy Crystal
4
4 7
6
Tonberry King
Non Elemental
1 Card = 1 Chef's Knife
6
7 6
2
Wedge, Biggs
Non Elemental
1 Card = 1 X Potion
----------------------------
-Level 6 Boss Cards-
--------------------
2
4 8
8
Fujin, Rajin
Non Elemental
1 Card = 1 X Potion
7
4 8
3
Elvoret
Wind Elemental
1 Card = 1o Death Stones
4
3 8
7
X-ATM092
Non Elemental
2 Cards = 1 Turtle Shell
7
5 2
8
Granaldo
Non Elemental
1 Card = 1 G-Returner
1
3 8
8
Gerogero
Poison Elemental
10 Cards = 1 Circlet
8
2 2
8
Iguion
Non Elemental
1 Card = 1 Cocktrice Pinion
6
5 8
4
Abadon
Non Elemental
1 Card = 30 Dark Ammo
4
6 8
5
Trauma
Non Elemental
1 Card = 30 Demolition Ammo
1
8 8
4
Oilboyle
Non Elemental
1 Card = 30 Fire Ammo
6
4 5
8
Shumi Tribe
Non Elemental
5 Cards = 1 Gambler's Spirit
7
1 5
8
Krysta
Non Elemental
1 Card = 10 Holy Stones
----------------------------
-Level 7 Boss Cards-
--------------------
8
8 4
4
Propagator
Non Elemental
1 Card = 1 G-Mega Potion
8
4 8
4
Jumbo Cactuar
Non Elemental
1 Card = 1 Cactus Thorn
8
8 6
6
Tri-Point
Thunder Elemental
40 Cards = 1 Jet Engine
5
8 6
6
Gargantua
Non Elemental
10 Cards = 1 Strength Love
8
3 6
7
Moblie Type B
Non Elemental
1 Card = 10 Shell Stones
8
8 3
5
Sphinxara
Non Elemental
1 Card = 1 G-Mega Potion
8
4 8
5
Tiamat
Non Elemental
1 Card = 10 Flare Stones
5
5 7
8
BGH251F2
Non Elemental
1 Card = 10 Protect Stones
6
7 8
4
Red Giant
Non Elemental
1 Card = 5 Meteor Stones
1
7 8
7
Catoblepas
Non Elemental
1 Card = 1 Rename Card
7
8 7
1
Ultima Weapon
Non Elemental
1 Card = 1 Ultima Stone
----------------------------
-Level 8 GF Cards- (All Rare Cards)
------------------
4
9 4
8
Chubby Chocobo
Non Elemental
1 Card = 100 LuvLuvG's
9
3 6
7
Angelo
Wind Elemental
1 Card = 100 Elixers
3
6 7
9
Gilgamesh
Non Elemental
1 Card = 10 Holy Wars
9
2 3
9
Mini Mog
Ice Elemental
1 Card = 100 Pet Houses
9
4 4
8
Chicobo
Poison Elemental
1 Card = 100 Gysahi Greens
2
4 9
9
Quezacoyl
Thunder Elemental
1 Card = 100 Dynamite Stones
6
9 7
4
Shiva
Ice Elemental
1 Card = 100 North Winds
9
8 6
2
Ifrit
Fire Elemental
1 Card = 3 Elemental Attacks
8
2 9
6
Siren
Non Elemental
1 Card = 3 Status Attacks
5
9 1
9
Sacred
Earth Elemental
1 Card = 100 Dino Bones
9
9 5
2
Minotaur
Earth Elemental
1 Card = 10 Adamantines
----------------------------
-Level 9 GF Cards- (All Rare Cards)
------------------
8
4 4
8
Carbuncle
Non Elemental
1 Card = 3 Glow Curtains
5
3 8
8
Diablos
Non Elemental
1 Card = 100 Black Holes
7
7 A
1
Leviathan
Water Elemental
1 Card = 3 Doc's Codes
8
5 A
3
Odin
Non Elemental
1 Card = 100 Dead Spirits
8
7 1
7
Pandemona
Wind Elemental
1 Card = 100 Windmills
7
A 4
6
Cerberus
Non Elemental
1 Card = 100 Lightweights
9
2 A
4
Alexander
Holy Elemental
1 Card = 3 Moon Curtains
7
A 2
7
Phoenix
Fire Elemental
1 Card = 3 Phoneix Spirits
8
6 8
2
Bahamut
Non Elemental
1 Card = 100 Mega Elixers
3
A 1
A
Doomtrain
Poison Elemental
1 Card = 3 Status Guards
4
A 4
9
Eden
Non Elemental
1 Card = 3 Monk's Codes
----------------------------
-Level 10 Player's Cards- (All Rare)
------------------------
A
8 7
2
Ward
Non Elemental
1 Card = 3 Gaea's Rings
6
A 7
6
Kiros
Non Elemental
1 Card = 3 Accelerators
5
9 A
3
Laguana
Non Elemental
1 Card = 100 Heros
A
4 8
6
Selphie
Non Elemental
1 Card = 3 Elemental Guards
9
2 6
A
Quistis
Non Elemental
1 Card = 3 Samantha Souls
2
A 6
9
Irvine
Non Elemental
1 Card = 3 Rocket Engines
8
6 5
A
Zell
Non Elemental
1 Card = 3 Hyper Wrists
4
A A
2
Rinoa
Non Elemental
1 Card = 3 Magic Armlets
A
3 A
3
Edea
Non Elemental
1 Card = 3 Royal Crowns
6
4 9
A
Seifer
Non Elemental
1 Card = 3 Diamond Armors
A
9 4
6
Squall
Non Elemental
1 Card = 3 Three Stars

View File

@@ -0,0 +1,7 @@
{
"parent": "item/generated",
"textures": {
"layer0": "item/filled_map",
"layer1": "item/filled_map_markings"
}
}

View File

@@ -124,6 +124,8 @@ configurations {
}
dependencies {
compileOnly "curse.maven:jei-238222:7225068"
// Dev-only utility mods for local runs.
localRuntime "curse.maven:jade-324717:6155158"
localRuntime "curse.maven:jei-238222:7225068"

View File

@@ -0,0 +1,626 @@
package net.minecraft.client.renderer;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.MoreObjects;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import com.mojang.math.Axis;
import net.minecraft.client.Minecraft;
import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.client.renderer.entity.EntityRenderDispatcher;
import net.minecraft.client.renderer.entity.ItemRenderer;
import net.minecraft.client.renderer.entity.player.PlayerRenderer;
import net.minecraft.client.renderer.texture.OverlayTexture;
import net.minecraft.core.component.DataComponents;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.Mth;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.HumanoidArm;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.CrossbowItem;
import net.minecraft.world.item.ItemDisplayContext;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.MapItem;
import net.minecraft.world.level.saveddata.maps.MapId;
import net.minecraft.world.level.saveddata.maps.MapItemSavedData;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.api.distmarker.OnlyIn;
import org.joml.Matrix4f;
@OnlyIn(Dist.CLIENT)
public class ItemInHandRenderer {
private static final RenderType MAP_BACKGROUND = RenderType.text(ResourceLocation.withDefaultNamespace("textures/map/map_background.png"));
private static final RenderType MAP_BACKGROUND_CHECKERBOARD = RenderType.text(
ResourceLocation.withDefaultNamespace("textures/map/map_background_checkerboard.png")
);
private static final float ITEM_SWING_X_POS_SCALE = -0.4F;
private static final float ITEM_SWING_Y_POS_SCALE = 0.2F;
private static final float ITEM_SWING_Z_POS_SCALE = -0.2F;
private static final float ITEM_HEIGHT_SCALE = -0.6F;
private static final float ITEM_POS_X = 0.56F;
private static final float ITEM_POS_Y = -0.52F;
private static final float ITEM_POS_Z = -0.72F;
private static final float ITEM_PRESWING_ROT_Y = 45.0F;
private static final float ITEM_SWING_X_ROT_AMOUNT = -80.0F;
private static final float ITEM_SWING_Y_ROT_AMOUNT = -20.0F;
private static final float ITEM_SWING_Z_ROT_AMOUNT = -20.0F;
private static final float EAT_JIGGLE_X_ROT_AMOUNT = 10.0F;
private static final float EAT_JIGGLE_Y_ROT_AMOUNT = 90.0F;
private static final float EAT_JIGGLE_Z_ROT_AMOUNT = 30.0F;
private static final float EAT_JIGGLE_X_POS_SCALE = 0.6F;
private static final float EAT_JIGGLE_Y_POS_SCALE = -0.5F;
private static final float EAT_JIGGLE_Z_POS_SCALE = 0.0F;
private static final double EAT_JIGGLE_EXPONENT = 27.0;
private static final float EAT_EXTRA_JIGGLE_CUTOFF = 0.8F;
private static final float EAT_EXTRA_JIGGLE_SCALE = 0.1F;
private static final float ARM_SWING_X_POS_SCALE = -0.3F;
private static final float ARM_SWING_Y_POS_SCALE = 0.4F;
private static final float ARM_SWING_Z_POS_SCALE = -0.4F;
private static final float ARM_SWING_Y_ROT_AMOUNT = 70.0F;
private static final float ARM_SWING_Z_ROT_AMOUNT = -20.0F;
private static final float ARM_HEIGHT_SCALE = -0.6F;
private static final float ARM_POS_SCALE = 0.8F;
private static final float ARM_POS_X = 0.8F;
private static final float ARM_POS_Y = -0.75F;
private static final float ARM_POS_Z = -0.9F;
private static final float ARM_PRESWING_ROT_Y = 45.0F;
private static final float ARM_PREROTATION_X_OFFSET = -1.0F;
private static final float ARM_PREROTATION_Y_OFFSET = 3.6F;
private static final float ARM_PREROTATION_Z_OFFSET = 3.5F;
private static final float ARM_POSTROTATION_X_OFFSET = 5.6F;
private static final int ARM_ROT_X = 200;
private static final int ARM_ROT_Y = -135;
private static final int ARM_ROT_Z = 120;
private static final float MAP_SWING_X_POS_SCALE = -0.4F;
private static final float MAP_SWING_Z_POS_SCALE = -0.2F;
private static final float MAP_HANDS_POS_X = 0.0F;
private static final float MAP_HANDS_POS_Y = 0.04F;
private static final float MAP_HANDS_POS_Z = -0.72F;
private static final float MAP_HANDS_HEIGHT_SCALE = -1.2F;
private static final float MAP_HANDS_TILT_SCALE = -0.5F;
private static final float MAP_PLAYER_PITCH_SCALE = 45.0F;
private static final float MAP_HANDS_Z_ROT_AMOUNT = -85.0F;
private static final float MAPHAND_X_ROT_AMOUNT = 45.0F;
private static final float MAPHAND_Y_ROT_AMOUNT = 92.0F;
private static final float MAPHAND_Z_ROT_AMOUNT = -41.0F;
private static final float MAP_HAND_X_POS = 0.3F;
private static final float MAP_HAND_Y_POS = -1.1F;
private static final float MAP_HAND_Z_POS = 0.45F;
private static final float MAP_SWING_X_ROT_AMOUNT = 20.0F;
private static final float MAP_PRE_ROT_SCALE = 0.38F;
private static final float MAP_GLOBAL_X_POS = -0.5F;
private static final float MAP_GLOBAL_Y_POS = -0.5F;
private static final float MAP_GLOBAL_Z_POS = 0.0F;
private static final float MAP_FINAL_SCALE = 0.0078125F;
private static final int MAP_BORDER = 7;
private static final int MAP_HEIGHT = 128;
private static final int MAP_WIDTH = 128;
private static final float BOW_CHARGE_X_POS_SCALE = 0.0F;
private static final float BOW_CHARGE_Y_POS_SCALE = 0.0F;
private static final float BOW_CHARGE_Z_POS_SCALE = 0.04F;
private static final float BOW_CHARGE_SHAKE_X_SCALE = 0.0F;
private static final float BOW_CHARGE_SHAKE_Y_SCALE = 0.004F;
private static final float BOW_CHARGE_SHAKE_Z_SCALE = 0.0F;
private static final float BOW_CHARGE_Z_SCALE = 0.2F;
private static final float BOW_MIN_SHAKE_CHARGE = 0.1F;
private final Minecraft minecraft;
private ItemStack mainHandItem = ItemStack.EMPTY;
private ItemStack offHandItem = ItemStack.EMPTY;
private float mainHandHeight;
private float oMainHandHeight;
private float offHandHeight;
private float oOffHandHeight;
private final EntityRenderDispatcher entityRenderDispatcher;
private final ItemRenderer itemRenderer;
public ItemInHandRenderer(Minecraft minecraft, EntityRenderDispatcher entityRenderDispatcher, ItemRenderer itemRenderer) {
this.minecraft = minecraft;
this.entityRenderDispatcher = entityRenderDispatcher;
this.itemRenderer = itemRenderer;
}
public void renderItem(
LivingEntity entity,
ItemStack itemStack,
ItemDisplayContext displayContext,
boolean leftHand,
PoseStack poseStack,
MultiBufferSource buffer,
int seed
) {
if (!itemStack.isEmpty()) {
this.itemRenderer
.renderStatic(
entity,
itemStack,
displayContext,
leftHand,
poseStack,
buffer,
entity.level(),
seed,
OverlayTexture.NO_OVERLAY,
entity.getId() + displayContext.ordinal()
);
}
}
/**
* Return the angle to render the Map
*/
private float calculateMapTilt(float pitch) {
float f = 1.0F - pitch / 45.0F + 0.1F;
f = Mth.clamp(f, 0.0F, 1.0F);
return -Mth.cos(f * (float) Math.PI) * 0.5F + 0.5F;
}
private void renderMapHand(PoseStack poseStack, MultiBufferSource buffer, int packedLight, HumanoidArm side) {
PlayerRenderer playerrenderer = (PlayerRenderer)this.entityRenderDispatcher.<AbstractClientPlayer>getRenderer(this.minecraft.player);
poseStack.pushPose();
float f = side == 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(f * -41.0F));
poseStack.translate(f * 0.3F, -1.1F, 0.45F);
if (side == HumanoidArm.RIGHT) {
playerrenderer.renderRightHand(poseStack, buffer, packedLight, this.minecraft.player);
} else {
playerrenderer.renderLeftHand(poseStack, buffer, packedLight, this.minecraft.player);
}
poseStack.popPose();
}
private void renderOneHandedMap(
PoseStack poseStack, MultiBufferSource buffer, int packedLight, float equippedProgress, HumanoidArm hand, float swingProgress, ItemStack stack
) {
float f = hand == HumanoidArm.RIGHT ? 1.0F : -1.0F;
poseStack.translate(f * 0.125F, -0.125F, 0.0F);
if (!this.minecraft.player.isInvisible()) {
poseStack.pushPose();
poseStack.mulPose(Axis.ZP.rotationDegrees(f * 10.0F));
this.renderPlayerArm(poseStack, buffer, packedLight, equippedProgress, swingProgress, hand);
poseStack.popPose();
}
poseStack.pushPose();
poseStack.translate(f * 0.51F, -0.08F + equippedProgress * -1.2F, -0.75F);
float f1 = Mth.sqrt(swingProgress);
float f2 = Mth.sin(f1 * (float) Math.PI);
float f3 = -0.5F * f2;
float f4 = 0.4F * Mth.sin(f1 * (float) (Math.PI * 2));
float f5 = -0.3F * Mth.sin(swingProgress * (float) Math.PI);
poseStack.translate(f * f3, f4 - 0.3F * f2, f5);
poseStack.mulPose(Axis.XP.rotationDegrees(f2 * -45.0F));
poseStack.mulPose(Axis.YP.rotationDegrees(f * f2 * -30.0F));
this.renderMap(poseStack, buffer, packedLight, stack);
poseStack.popPose();
}
private void renderTwoHandedMap(PoseStack poseStack, MultiBufferSource buffer, int packedLight, float pitch, float equippedProgress, float swingProgress) {
float f = Mth.sqrt(swingProgress);
float f1 = -0.2F * Mth.sin(swingProgress * (float) Math.PI);
float f2 = -0.4F * Mth.sin(f * (float) Math.PI);
poseStack.translate(0.0F, -f1 / 2.0F, f2);
float f3 = this.calculateMapTilt(pitch);
poseStack.translate(0.0F, 0.04F + equippedProgress * -1.2F + f3 * -0.5F, -0.72F);
poseStack.mulPose(Axis.XP.rotationDegrees(f3 * -85.0F));
if (!this.minecraft.player.isInvisible()) {
poseStack.pushPose();
poseStack.mulPose(Axis.YP.rotationDegrees(90.0F));
this.renderMapHand(poseStack, buffer, packedLight, HumanoidArm.RIGHT);
this.renderMapHand(poseStack, buffer, packedLight, HumanoidArm.LEFT);
poseStack.popPose();
}
float f4 = Mth.sin(f * (float) Math.PI);
poseStack.mulPose(Axis.XP.rotationDegrees(f4 * 20.0F));
poseStack.scale(2.0F, 2.0F, 2.0F);
this.renderMap(poseStack, buffer, packedLight, this.mainHandItem);
}
private void renderMap(PoseStack poseStack, MultiBufferSource buffer, int packedLight, ItemStack stack) {
poseStack.mulPose(Axis.YP.rotationDegrees(180.0F));
poseStack.mulPose(Axis.ZP.rotationDegrees(180.0F));
poseStack.scale(0.38F, 0.38F, 0.38F);
poseStack.translate(-0.5F, -0.5F, 0.0F);
poseStack.scale(0.0078125F, 0.0078125F, 0.0078125F);
MapId mapid = stack.get(DataComponents.MAP_ID);
MapItemSavedData mapitemsaveddata = MapItem.getSavedData(stack, this.minecraft.level);
VertexConsumer vertexconsumer = buffer.getBuffer(mapitemsaveddata == null ? MAP_BACKGROUND : MAP_BACKGROUND_CHECKERBOARD);
Matrix4f matrix4f = poseStack.last().pose();
vertexconsumer.addVertex(matrix4f, -7.0F, 135.0F, 0.0F).setColor(-1).setUv(0.0F, 1.0F).setLight(packedLight);
vertexconsumer.addVertex(matrix4f, 135.0F, 135.0F, 0.0F).setColor(-1).setUv(1.0F, 1.0F).setLight(packedLight);
vertexconsumer.addVertex(matrix4f, 135.0F, -7.0F, 0.0F).setColor(-1).setUv(1.0F, 0.0F).setLight(packedLight);
vertexconsumer.addVertex(matrix4f, -7.0F, -7.0F, 0.0F).setColor(-1).setUv(0.0F, 0.0F).setLight(packedLight);
if (mapitemsaveddata != null) {
this.minecraft.gameRenderer.getMapRenderer().render(poseStack, buffer, mapid, mapitemsaveddata, false, packedLight);
}
}
private void renderPlayerArm(PoseStack poseStack, MultiBufferSource buffer, int packedLight, float equippedProgress, float swingProgress, HumanoidArm side) {
boolean flag = side != HumanoidArm.LEFT;
float f = flag ? 1.0F : -1.0F;
float f1 = Mth.sqrt(swingProgress);
float f2 = -0.3F * Mth.sin(f1 * (float) Math.PI);
float f3 = 0.4F * Mth.sin(f1 * (float) (Math.PI * 2));
float f4 = -0.4F * Mth.sin(swingProgress * (float) Math.PI);
poseStack.translate(f * (f2 + 0.64000005F), f3 + -0.6F + equippedProgress * -0.6F, f4 + -0.71999997F);
poseStack.mulPose(Axis.YP.rotationDegrees(f * 45.0F));
float f5 = Mth.sin(swingProgress * swingProgress * (float) Math.PI);
float f6 = Mth.sin(f1 * (float) Math.PI);
poseStack.mulPose(Axis.YP.rotationDegrees(f * f6 * 70.0F));
poseStack.mulPose(Axis.ZP.rotationDegrees(f * f5 * -20.0F));
AbstractClientPlayer abstractclientplayer = this.minecraft.player;
poseStack.translate(f * -1.0F, 3.6F, 3.5F);
poseStack.mulPose(Axis.ZP.rotationDegrees(f * 120.0F));
poseStack.mulPose(Axis.XP.rotationDegrees(200.0F));
poseStack.mulPose(Axis.YP.rotationDegrees(f * -135.0F));
poseStack.translate(f * 5.6F, 0.0F, 0.0F);
PlayerRenderer playerrenderer = (PlayerRenderer)this.entityRenderDispatcher.<AbstractClientPlayer>getRenderer(abstractclientplayer);
if (flag) {
playerrenderer.renderRightHand(poseStack, buffer, packedLight, abstractclientplayer);
} else {
playerrenderer.renderLeftHand(poseStack, buffer, packedLight, abstractclientplayer);
}
}
private void applyEatTransform(PoseStack poseStack, float partialTick, HumanoidArm arm, ItemStack stack, Player player) {
float f = (float)player.getUseItemRemainingTicks() - partialTick + 1.0F;
float f1 = f / (float)stack.getUseDuration(player);
if (f1 < 0.8F) {
float f2 = Mth.abs(Mth.cos(f / 4.0F * (float) Math.PI) * 0.1F);
poseStack.translate(0.0F, f2, 0.0F);
}
float f3 = 1.0F - (float)Math.pow((double)f1, 27.0);
int i = arm == HumanoidArm.RIGHT ? 1 : -1;
poseStack.translate(f3 * 0.6F * (float)i, f3 * -0.5F, f3 * 0.0F);
poseStack.mulPose(Axis.YP.rotationDegrees((float)i * f3 * 90.0F));
poseStack.mulPose(Axis.XP.rotationDegrees(f3 * 10.0F));
poseStack.mulPose(Axis.ZP.rotationDegrees((float)i * f3 * 30.0F));
}
private void applyBrushTransform(PoseStack poseStack, float partialTick, HumanoidArm arm, ItemStack stack, Player player, float equippedProgress) {
this.applyItemArmTransform(poseStack, arm, equippedProgress);
float f = (float)(player.getUseItemRemainingTicks() % 10);
float f1 = f - partialTick + 1.0F;
float f2 = 1.0F - f1 / 10.0F;
float f3 = -90.0F;
float f4 = 60.0F;
float f5 = 150.0F;
float f6 = -15.0F;
int i = 2;
float f7 = -15.0F + 75.0F * Mth.cos(f2 * 2.0F * (float) Math.PI);
if (arm != HumanoidArm.RIGHT) {
poseStack.translate(0.1, 0.83, 0.35);
poseStack.mulPose(Axis.XP.rotationDegrees(-80.0F));
poseStack.mulPose(Axis.YP.rotationDegrees(-90.0F));
poseStack.mulPose(Axis.XP.rotationDegrees(f7));
poseStack.translate(-0.3, 0.22, 0.35);
} else {
poseStack.translate(-0.25, 0.22, 0.35);
poseStack.mulPose(Axis.XP.rotationDegrees(-80.0F));
poseStack.mulPose(Axis.YP.rotationDegrees(90.0F));
poseStack.mulPose(Axis.ZP.rotationDegrees(0.0F));
poseStack.mulPose(Axis.XP.rotationDegrees(f7));
}
}
private void applyItemArmAttackTransform(PoseStack poseStack, HumanoidArm hand, float swingProgress) {
int i = hand == HumanoidArm.RIGHT ? 1 : -1;
float f = Mth.sin(swingProgress * swingProgress * (float) Math.PI);
poseStack.mulPose(Axis.YP.rotationDegrees((float)i * (45.0F + f * -20.0F)));
float f1 = Mth.sin(Mth.sqrt(swingProgress) * (float) Math.PI);
poseStack.mulPose(Axis.ZP.rotationDegrees((float)i * f1 * -20.0F));
poseStack.mulPose(Axis.XP.rotationDegrees(f1 * -80.0F));
poseStack.mulPose(Axis.YP.rotationDegrees((float)i * -45.0F));
}
private void applyItemArmTransform(PoseStack poseStack, HumanoidArm hand, float equippedProg) {
int i = hand == HumanoidArm.RIGHT ? 1 : -1;
poseStack.translate((float)i * 0.56F, -0.52F + equippedProg * -0.6F, -0.72F);
}
public void renderHandsWithItems(float partialTicks, PoseStack poseStack, MultiBufferSource.BufferSource buffer, LocalPlayer playerEntity, int combinedLight) {
float f = playerEntity.getAttackAnim(partialTicks);
InteractionHand interactionhand = MoreObjects.firstNonNull(playerEntity.swingingArm, InteractionHand.MAIN_HAND);
float f1 = Mth.lerp(partialTicks, playerEntity.xRotO, playerEntity.getXRot());
ItemInHandRenderer.HandRenderSelection iteminhandrenderer$handrenderselection = evaluateWhichHandsToRender(playerEntity);
float f2 = Mth.lerp(partialTicks, playerEntity.xBobO, playerEntity.xBob);
float f3 = Mth.lerp(partialTicks, playerEntity.yBobO, playerEntity.yBob);
poseStack.mulPose(Axis.XP.rotationDegrees((playerEntity.getViewXRot(partialTicks) - f2) * 0.1F));
poseStack.mulPose(Axis.YP.rotationDegrees((playerEntity.getViewYRot(partialTicks) - f3) * 0.1F));
if (iteminhandrenderer$handrenderselection.renderMainHand) {
float f4 = interactionhand == InteractionHand.MAIN_HAND ? f : 0.0F;
float f5 = 1.0F - Mth.lerp(partialTicks, this.oMainHandHeight, this.mainHandHeight);
if(!net.neoforged.neoforge.client.ClientHooks.renderSpecificFirstPersonHand(InteractionHand.MAIN_HAND, poseStack, buffer, combinedLight, partialTicks, f1, f4, f5, this.mainHandItem))
this.renderArmWithItem(playerEntity, partialTicks, f1, InteractionHand.MAIN_HAND, f4, this.mainHandItem, f5, poseStack, buffer, combinedLight);
}
if (iteminhandrenderer$handrenderselection.renderOffHand) {
float f6 = interactionhand == InteractionHand.OFF_HAND ? f : 0.0F;
float f7 = 1.0F - Mth.lerp(partialTicks, this.oOffHandHeight, this.offHandHeight);
if(!net.neoforged.neoforge.client.ClientHooks.renderSpecificFirstPersonHand(InteractionHand.OFF_HAND, poseStack, buffer, combinedLight, partialTicks, f1, f6, f7, this.offHandItem))
this.renderArmWithItem(playerEntity, partialTicks, f1, InteractionHand.OFF_HAND, f6, this.offHandItem, f7, poseStack, buffer, combinedLight);
}
buffer.endBatch();
}
@VisibleForTesting
static ItemInHandRenderer.HandRenderSelection evaluateWhichHandsToRender(LocalPlayer player) {
ItemStack itemstack = player.getMainHandItem();
ItemStack itemstack1 = player.getOffhandItem();
boolean flag = itemstack.is(Items.BOW) || itemstack1.is(Items.BOW);
boolean flag1 = itemstack.is(Items.CROSSBOW) || itemstack1.is(Items.CROSSBOW);
if (!flag && !flag1) {
return ItemInHandRenderer.HandRenderSelection.RENDER_BOTH_HANDS;
} else if (player.isUsingItem()) {
return selectionUsingItemWhileHoldingBowLike(player);
} else {
return isChargedCrossbow(itemstack)
? ItemInHandRenderer.HandRenderSelection.RENDER_MAIN_HAND_ONLY
: ItemInHandRenderer.HandRenderSelection.RENDER_BOTH_HANDS;
}
}
private static ItemInHandRenderer.HandRenderSelection selectionUsingItemWhileHoldingBowLike(LocalPlayer player) {
ItemStack itemstack = player.getUseItem();
InteractionHand interactionhand = player.getUsedItemHand();
if (!itemstack.is(Items.BOW) && !itemstack.is(Items.CROSSBOW)) {
return interactionhand == InteractionHand.MAIN_HAND && isChargedCrossbow(player.getOffhandItem())
? ItemInHandRenderer.HandRenderSelection.RENDER_MAIN_HAND_ONLY
: ItemInHandRenderer.HandRenderSelection.RENDER_BOTH_HANDS;
} else {
return ItemInHandRenderer.HandRenderSelection.onlyForHand(interactionhand);
}
}
private static boolean isChargedCrossbow(ItemStack stack) {
return stack.is(Items.CROSSBOW) && CrossbowItem.isCharged(stack);
}
private void renderArmWithItem(
AbstractClientPlayer player,
float partialTicks,
float pitch,
InteractionHand hand,
float swingProgress,
ItemStack stack,
float equippedProgress,
PoseStack poseStack,
MultiBufferSource buffer,
int combinedLight
) {
if (!player.isScoping()) {
boolean flag = hand == InteractionHand.MAIN_HAND;
HumanoidArm humanoidarm = flag ? player.getMainArm() : player.getMainArm().getOpposite();
poseStack.pushPose();
if (stack.isEmpty()) {
if (flag && !player.isInvisible()) {
this.renderPlayerArm(poseStack, buffer, combinedLight, equippedProgress, swingProgress, humanoidarm);
}
} else if (stack.getItem() instanceof MapItem) {
if (flag && this.offHandItem.isEmpty()) {
this.renderTwoHandedMap(poseStack, buffer, combinedLight, pitch, equippedProgress, swingProgress);
} else {
this.renderOneHandedMap(poseStack, buffer, combinedLight, equippedProgress, humanoidarm, swingProgress, stack);
}
} else if (stack.getItem() instanceof CrossbowItem) {
boolean flag1 = CrossbowItem.isCharged(stack);
boolean flag2 = humanoidarm == HumanoidArm.RIGHT;
int i = flag2 ? 1 : -1;
if (player.isUsingItem() && player.getUseItemRemainingTicks() > 0 && player.getUsedItemHand() == hand) {
this.applyItemArmTransform(poseStack, humanoidarm, equippedProgress);
poseStack.translate((float)i * -0.4785682F, -0.094387F, 0.05731531F);
poseStack.mulPose(Axis.XP.rotationDegrees(-11.935F));
poseStack.mulPose(Axis.YP.rotationDegrees((float)i * 65.3F));
poseStack.mulPose(Axis.ZP.rotationDegrees((float)i * -9.785F));
float f9 = (float)stack.getUseDuration(player) - ((float)player.getUseItemRemainingTicks() - partialTicks + 1.0F);
float f13 = f9 / (float)CrossbowItem.getChargeDuration(stack, player);
if (f13 > 1.0F) {
f13 = 1.0F;
}
if (f13 > 0.1F) {
float f16 = Mth.sin((f9 - 0.1F) * 1.3F);
float f3 = f13 - 0.1F;
float f4 = f16 * f3;
poseStack.translate(f4 * 0.0F, f4 * 0.004F, f4 * 0.0F);
}
poseStack.translate(f13 * 0.0F, f13 * 0.0F, f13 * 0.04F);
poseStack.scale(1.0F, 1.0F, 1.0F + f13 * 0.2F);
poseStack.mulPose(Axis.YN.rotationDegrees((float)i * 45.0F));
} else {
float f = -0.4F * Mth.sin(Mth.sqrt(swingProgress) * (float) Math.PI);
float f1 = 0.2F * Mth.sin(Mth.sqrt(swingProgress) * (float) (Math.PI * 2));
float f2 = -0.2F * Mth.sin(swingProgress * (float) Math.PI);
poseStack.translate((float)i * f, f1, f2);
this.applyItemArmTransform(poseStack, humanoidarm, equippedProgress);
this.applyItemArmAttackTransform(poseStack, humanoidarm, swingProgress);
if (flag1 && swingProgress < 0.001F && flag) {
poseStack.translate((float)i * -0.641864F, 0.0F, 0.0F);
poseStack.mulPose(Axis.YP.rotationDegrees((float)i * 10.0F));
}
}
this.renderItem(
player,
stack,
flag2 ? ItemDisplayContext.FIRST_PERSON_RIGHT_HAND : ItemDisplayContext.FIRST_PERSON_LEFT_HAND,
!flag2,
poseStack,
buffer,
combinedLight
);
} else {
boolean flag3 = humanoidarm == HumanoidArm.RIGHT;
if (!net.neoforged.neoforge.client.extensions.common.IClientItemExtensions.of(stack).applyForgeHandTransform(poseStack, minecraft.player, humanoidarm, stack, partialTicks, equippedProgress, swingProgress)) // FORGE: Allow items to define custom arm animation
if (player.isUsingItem() && player.getUseItemRemainingTicks() > 0 && player.getUsedItemHand() == hand) {
int k = flag3 ? 1 : -1;
switch (stack.getUseAnimation()) {
case NONE:
this.applyItemArmTransform(poseStack, humanoidarm, equippedProgress);
break;
case EAT:
case DRINK:
this.applyEatTransform(poseStack, partialTicks, humanoidarm, stack, player);
this.applyItemArmTransform(poseStack, humanoidarm, equippedProgress);
break;
case BLOCK:
this.applyItemArmTransform(poseStack, humanoidarm, equippedProgress);
break;
case BOW:
this.applyItemArmTransform(poseStack, humanoidarm, equippedProgress);
poseStack.translate((float)k * -0.2785682F, 0.18344387F, 0.15731531F);
poseStack.mulPose(Axis.XP.rotationDegrees(-13.935F));
poseStack.mulPose(Axis.YP.rotationDegrees((float)k * 35.3F));
poseStack.mulPose(Axis.ZP.rotationDegrees((float)k * -9.785F));
float f8 = (float)stack.getUseDuration(player) - ((float)player.getUseItemRemainingTicks() - partialTicks + 1.0F);
float f12 = f8 / 20.0F;
f12 = (f12 * f12 + f12 * 2.0F) / 3.0F;
if (f12 > 1.0F) {
f12 = 1.0F;
}
if (f12 > 0.1F) {
float f15 = Mth.sin((f8 - 0.1F) * 1.3F);
float f18 = f12 - 0.1F;
float f20 = f15 * f18;
poseStack.translate(f20 * 0.0F, f20 * 0.004F, f20 * 0.0F);
}
poseStack.translate(f12 * 0.0F, f12 * 0.0F, f12 * 0.04F);
poseStack.scale(1.0F, 1.0F, 1.0F + f12 * 0.2F);
poseStack.mulPose(Axis.YN.rotationDegrees((float)k * 45.0F));
break;
case SPEAR:
this.applyItemArmTransform(poseStack, humanoidarm, equippedProgress);
poseStack.translate((float)k * -0.5F, 0.7F, 0.1F);
poseStack.mulPose(Axis.XP.rotationDegrees(-55.0F));
poseStack.mulPose(Axis.YP.rotationDegrees((float)k * 35.3F));
poseStack.mulPose(Axis.ZP.rotationDegrees((float)k * -9.785F));
float f7 = (float)stack.getUseDuration(player) - ((float)player.getUseItemRemainingTicks() - partialTicks + 1.0F);
float f11 = f7 / 10.0F;
if (f11 > 1.0F) {
f11 = 1.0F;
}
if (f11 > 0.1F) {
float f14 = Mth.sin((f7 - 0.1F) * 1.3F);
float f17 = f11 - 0.1F;
float f19 = f14 * f17;
poseStack.translate(f19 * 0.0F, f19 * 0.004F, f19 * 0.0F);
}
poseStack.translate(0.0F, 0.0F, f11 * 0.2F);
poseStack.scale(1.0F, 1.0F, 1.0F + f11 * 0.2F);
poseStack.mulPose(Axis.YN.rotationDegrees((float)k * 45.0F));
break;
case BRUSH:
this.applyBrushTransform(poseStack, partialTicks, humanoidarm, stack, player, equippedProgress);
}
} else if (player.isAutoSpinAttack()) {
this.applyItemArmTransform(poseStack, humanoidarm, equippedProgress);
int j = flag3 ? 1 : -1;
poseStack.translate((float)j * -0.4F, 0.8F, 0.3F);
poseStack.mulPose(Axis.YP.rotationDegrees((float)j * 65.0F));
poseStack.mulPose(Axis.ZP.rotationDegrees((float)j * -85.0F));
} else {
float f5 = -0.4F * Mth.sin(Mth.sqrt(swingProgress) * (float) Math.PI);
float f6 = 0.2F * Mth.sin(Mth.sqrt(swingProgress) * (float) (Math.PI * 2));
float f10 = -0.2F * Mth.sin(swingProgress * (float) Math.PI);
int l = flag3 ? 1 : -1;
poseStack.translate((float)l * f5, f6, f10);
this.applyItemArmTransform(poseStack, humanoidarm, equippedProgress);
this.applyItemArmAttackTransform(poseStack, humanoidarm, swingProgress);
}
this.renderItem(
player,
stack,
flag3 ? ItemDisplayContext.FIRST_PERSON_RIGHT_HAND : ItemDisplayContext.FIRST_PERSON_LEFT_HAND,
!flag3,
poseStack,
buffer,
combinedLight
);
}
poseStack.popPose();
}
}
public void tick() {
this.oMainHandHeight = this.mainHandHeight;
this.oOffHandHeight = this.offHandHeight;
LocalPlayer localplayer = this.minecraft.player;
ItemStack itemstack = localplayer.getMainHandItem();
ItemStack itemstack1 = localplayer.getOffhandItem();
if (ItemStack.matches(this.mainHandItem, itemstack)) {
this.mainHandItem = itemstack;
}
if (ItemStack.matches(this.offHandItem, itemstack1)) {
this.offHandItem = itemstack1;
}
if (localplayer.isHandsBusy()) {
this.mainHandHeight = Mth.clamp(this.mainHandHeight - 0.4F, 0.0F, 1.0F);
this.offHandHeight = Mth.clamp(this.offHandHeight - 0.4F, 0.0F, 1.0F);
} else {
float f = localplayer.getAttackStrengthScale(1.0F);
boolean requipM = net.neoforged.neoforge.client.ClientHooks.shouldCauseReequipAnimation(this.mainHandItem, itemstack, localplayer.getInventory().selected);
boolean requipO = net.neoforged.neoforge.client.ClientHooks.shouldCauseReequipAnimation(this.offHandItem, itemstack1, -1);
if (!requipM && this.mainHandItem != itemstack)
this.mainHandItem = itemstack;
if (!requipO && this.offHandItem != itemstack1)
this.offHandItem = itemstack1;
this.mainHandHeight += Mth.clamp((!requipM ? f * f * f : 0.0F) - this.mainHandHeight, -0.4F, 0.4F);
this.offHandHeight += Mth.clamp((float)(!requipO ? 1 : 0) - this.offHandHeight, -0.4F, 0.4F);
}
if (this.mainHandHeight < 0.1F) {
this.mainHandItem = itemstack;
}
if (this.offHandHeight < 0.1F) {
this.offHandItem = itemstack1;
}
}
public void itemUsed(InteractionHand hand) {
if (hand == InteractionHand.MAIN_HAND) {
this.mainHandHeight = 0.0F;
} else {
this.offHandHeight = 0.0F;
}
}
@OnlyIn(Dist.CLIENT)
@VisibleForTesting
static enum HandRenderSelection {
RENDER_BOTH_HANDS(true, true),
RENDER_MAIN_HAND_ONLY(true, false),
RENDER_OFF_HAND_ONLY(false, true);
final boolean renderMainHand;
final boolean renderOffHand;
private HandRenderSelection(boolean renderMainHand, boolean renderOffHand) {
this.renderMainHand = renderMainHand;
this.renderOffHand = renderOffHand;
}
public static ItemInHandRenderer.HandRenderSelection onlyForHand(InteractionHand hand) {
return hand == InteractionHand.MAIN_HAND ? RENDER_MAIN_HAND_ONLY : RENDER_OFF_HAND_ONLY;
}
}
}

View File

@@ -0,0 +1,120 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.neoforged.neoforge.client.event;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.renderer.LightTexture;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.item.ItemStack;
import net.neoforged.bus.api.Event;
import net.neoforged.bus.api.ICancellableEvent;
import net.neoforged.fml.LogicalSide;
import net.neoforged.neoforge.common.NeoForge;
import org.jetbrains.annotations.ApiStatus;
/**
* Fired before a hand is rendered in the first person view.
*
* <p>This event is {@linkplain ICancellableEvent cancellable}, and does not {@linkplain HasResult have a result}.
* If this event is cancelled, then the hand will not be rendered.</p>
*
* <p>This event is fired on the {@linkplain NeoForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*
* @see RenderArmEvent
*/
public class RenderHandEvent extends Event implements ICancellableEvent {
private final InteractionHand hand;
private final PoseStack poseStack;
private final MultiBufferSource multiBufferSource;
private final int packedLight;
private final float partialTick;
private final float interpolatedPitch;
private final float swingProgress;
private final float equipProgress;
private final ItemStack stack;
@ApiStatus.Internal
public RenderHandEvent(InteractionHand hand, PoseStack poseStack, MultiBufferSource multiBufferSource, int packedLight,
float partialTick, float interpolatedPitch,
float swingProgress, float equipProgress, ItemStack stack) {
this.hand = hand;
this.poseStack = poseStack;
this.multiBufferSource = multiBufferSource;
this.packedLight = packedLight;
this.partialTick = partialTick;
this.interpolatedPitch = interpolatedPitch;
this.swingProgress = swingProgress;
this.equipProgress = equipProgress;
this.stack = stack;
}
/**
* {@return the hand being rendered}
*/
public InteractionHand getHand() {
return hand;
}
/**
* {@return the pose stack used for rendering}
*/
public PoseStack getPoseStack() {
return poseStack;
}
/**
* {@return the source of rendering buffers}
*/
public MultiBufferSource getMultiBufferSource() {
return multiBufferSource;
}
/**
* {@return the amount of packed (sky and block) light for rendering}
*
* @see LightTexture
*/
public int getPackedLight() {
return packedLight;
}
/**
* {@return the partial tick}
*/
public float getPartialTick() {
return partialTick;
}
/**
* {@return the interpolated pitch of the player entity}
*/
public float getInterpolatedPitch() {
return interpolatedPitch;
}
/**
* {@return the swing progress of the hand being rendered}
*/
public float getSwingProgress() {
return swingProgress;
}
/**
* {@return the progress of the equip animation, from {@code 0.0} to {@code 1.0}}
*/
public float getEquipProgress() {
return equipProgress;
}
/**
* {@return the item stack to be rendered}
*/
public ItemStack getItemStack() {
return stack;
}
}

Binary file not shown.

View File

@@ -0,0 +1,460 @@
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Random;
import javax.imageio.ImageIO;
public final class PassiveMobArtGenerator {
private static final int MASTER_SIZE = 256;
private static final int CARD_SIZE = 64;
private static final int PIXEL = 4;
private static final Path ROOT = Path.of("").toAbsolutePath();
private static final Path FULL_ART_DIR = ROOT.resolve("Full Art").resolve("passive_mobs");
private static final Path CARD_ART_DIR = ROOT.resolve("src")
.resolve("main")
.resolve("resources")
.resolve("assets")
.resolve("minetriad")
.resolve("textures")
.resolve("item")
.resolve("cards");
private record MobArt(String name, Color base, Color dark, Color light, Color backgroundTop, Color backgroundBottom,
Color accent, List<String> features) {
}
private static final List<MobArt> MOBS = List.of(
mob("axolotl", rgb(244, 173, 203), rgb(232, 144, 187), rgb(124, 62, 110), rgb(95, 173, 207), rgb(19, 76, 113), rgb(255, 214, 122), "gills"),
mob("bee", rgb(255, 213, 62), rgb(84, 57, 29), rgb(255, 246, 196), rgb(131, 200, 95), rgb(50, 112, 49), rgb(226, 248, 255), "wings", "stripe"),
mob("camel", rgb(193, 154, 101), rgb(128, 94, 57), rgb(232, 203, 155), rgb(241, 194, 120), rgb(189, 110, 49), rgb(77, 148, 192), "ears"),
mob("cat", rgb(217, 176, 92), rgb(105, 76, 44), rgb(247, 229, 174), rgb(142, 186, 228), rgb(71, 99, 143), rgb(158, 219, 110), "ears", "snout"),
mob("chicken", rgb(243, 246, 239), rgb(208, 72, 46), rgb(251, 205, 61), rgb(161, 212, 127), rgb(89, 130, 70), rgb(164, 199, 245), "beak", "crest"),
mob("cod", rgb(145, 117, 88), rgb(95, 76, 52), rgb(211, 185, 149), rgb(57, 136, 170), rgb(15, 62, 97), rgb(230, 120, 74), "fins"),
mob("cow", rgb(116, 77, 52), rgb(59, 37, 25), rgb(231, 219, 206), rgb(144, 196, 110), rgb(75, 115, 59), rgb(162, 212, 255), "horns", "snout", "spots"),
mob("donkey", rgb(143, 118, 92), rgb(81, 62, 44), rgb(218, 202, 182), rgb(131, 170, 210), rgb(66, 90, 129), rgb(125, 193, 98), "long_ears", "snout"),
mob("fox", rgb(222, 110, 57), rgb(108, 55, 34), rgb(250, 239, 221), rgb(145, 204, 130), rgb(68, 109, 71), rgb(247, 214, 119), "ears", "snout"),
mob("frog", rgb(132, 176, 92), rgb(70, 108, 54), rgb(208, 219, 171), rgb(121, 180, 116), rgb(63, 110, 68), rgb(255, 220, 128), "frog_eyes"),
mob("glow_squid", rgb(96, 172, 188), rgb(41, 87, 101), rgb(163, 231, 246), rgb(38, 52, 102), rgb(11, 18, 39), rgb(119, 243, 255), "tentacles", "glow"),
mob("goat", rgb(208, 196, 184), rgb(121, 110, 98), rgb(245, 240, 233), rgb(160, 196, 193), rgb(92, 124, 122), rgb(192, 171, 101), "horns", "beard"),
mob("horse", rgb(122, 83, 55), rgb(65, 44, 28), rgb(236, 219, 187), rgb(134, 188, 121), rgb(64, 111, 67), rgb(187, 223, 255), "ears", "snout"),
mob("llama", rgb(220, 197, 162), rgb(140, 112, 80), rgb(248, 236, 219), rgb(170, 197, 122), rgb(88, 118, 72), rgb(197, 122, 67), "long_ears"),
mob("mooshroom", rgb(187, 54, 44), rgb(98, 26, 24), rgb(239, 226, 208), rgb(114, 162, 94), rgb(61, 103, 52), rgb(237, 247, 215), "horns", "snout", "spots", "mushroom"),
mob("mule", rgb(133, 104, 80), rgb(76, 57, 42), rgb(220, 205, 186), rgb(150, 188, 132), rgb(76, 112, 74), rgb(113, 166, 211), "long_ears", "snout"),
mob("ocelot", rgb(220, 185, 82), rgb(112, 85, 33), rgb(249, 228, 158), rgb(117, 178, 113), rgb(56, 104, 59), rgb(236, 191, 94), "ears", "snout", "spots"),
mob("panda", rgb(235, 238, 242), rgb(42, 46, 52), rgb(255, 255, 255), rgb(137, 198, 124), rgb(70, 111, 64), rgb(198, 227, 141), "ears", "panda_eyes"),
mob("parrot", rgb(205, 61, 57), rgb(42, 88, 182), rgb(255, 214, 54), rgb(115, 190, 155), rgb(52, 94, 102), rgb(182, 238, 93), "beak", "crest", "wings"),
mob("pig", rgb(233, 167, 182), rgb(168, 99, 122), rgb(250, 208, 217), rgb(133, 193, 115), rgb(74, 116, 67), rgb(204, 230, 255), "snout"),
mob("polar_bear", rgb(238, 243, 247), rgb(152, 173, 191), rgb(255, 255, 255), rgb(146, 188, 217), rgb(82, 117, 149), rgb(195, 225, 255), "ears", "snout"),
mob("rabbit", rgb(187, 173, 162), rgb(117, 98, 86), rgb(240, 231, 222), rgb(166, 203, 111), rgb(87, 118, 63), rgb(255, 215, 145), "long_ears", "snout"),
mob("salmon", rgb(214, 120, 95), rgb(121, 76, 60), rgb(247, 183, 162), rgb(72, 146, 175), rgb(17, 69, 107), rgb(225, 212, 117), "fins"),
mob("sheep", rgb(233, 235, 241), rgb(128, 100, 74), rgb(255, 255, 255), rgb(142, 197, 117), rgb(81, 123, 68), rgb(168, 218, 255), "wool", "snout"),
mob("sniffer", rgb(127, 95, 66), rgb(73, 52, 39), rgb(180, 141, 106), rgb(102, 152, 88), rgb(54, 93, 55), rgb(222, 106, 58), "snout", "moss"),
mob("squid", rgb(133, 92, 176), rgb(63, 41, 91), rgb(212, 181, 236), rgb(58, 102, 153), rgb(20, 35, 74), rgb(185, 132, 236), "tentacles"),
mob("strider", rgb(209, 84, 72), rgb(111, 36, 36), rgb(246, 166, 133), rgb(231, 144, 71), rgb(129, 68, 35), rgb(101, 38, 38), "tentacles", "snout"),
mob("turtle", rgb(107, 150, 77), rgb(57, 92, 45), rgb(194, 176, 88), rgb(92, 166, 143), rgb(44, 100, 100), rgb(223, 232, 176), "shell"),
mob("wolf", rgb(181, 188, 197), rgb(91, 98, 109), rgb(231, 235, 239), rgb(118, 164, 188), rgb(63, 97, 115), rgb(209, 67, 57), "ears", "snout"));
public static void main(String[] args) throws IOException {
Files.createDirectories(FULL_ART_DIR);
Files.createDirectories(CARD_ART_DIR);
for (MobArt mob : MOBS) {
BufferedImage master = generateArt(mob);
ImageIO.write(master, "png", FULL_ART_DIR.resolve(mob.name() + ".png").toFile());
ImageIO.write(scale(master, CARD_SIZE, CARD_SIZE), "png", CARD_ART_DIR.resolve(mob.name() + ".png").toFile());
}
System.out.println("Generated " + MOBS.size() + " passive mob paintings.");
}
private static BufferedImage generateArt(MobArt mob) {
BufferedImage image = new BufferedImage(MASTER_SIZE, MASTER_SIZE, BufferedImage.TYPE_INT_ARGB);
Random random = new Random(mob.name().hashCode());
paintBackground(image, mob, random);
Graphics2D g = image.createGraphics();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
g.setComposite(AlphaComposite.SrcOver);
drawHead(g, mob, random);
addForeground(g, mob, random);
addAtmosphere(g, mob, random);
g.dispose();
return image;
}
private static void paintBackground(BufferedImage image, MobArt mob, Random random) {
for (int y = 0; y < MASTER_SIZE; y++) {
double blend = (double) y / (MASTER_SIZE - 1);
double wave = Math.sin((y / (double) MASTER_SIZE) * Math.PI * 3.0) * 0.08;
Color row = mix(mob.backgroundTop(), mob.backgroundBottom(), clamp01(blend + wave));
for (int x = 0; x < MASTER_SIZE; x++) {
double distanceX = x - (MASTER_SIZE / 2.0);
double distanceY = y - (MASTER_SIZE / 2.0);
double vignette = (distanceX * distanceX + distanceY * distanceY) / (MASTER_SIZE * (double) MASTER_SIZE);
Color shaded = shade(row, vignette * 0.65);
int noise = random.nextInt(17) - 8;
image.setRGB(x, y, shift(shaded, noise).getRGB());
}
}
Graphics2D g = image.createGraphics();
for (int i = 0; i < 10; i++) {
int radius = random.nextInt(35) + 18;
int x = random.nextInt(MASTER_SIZE + 40) - 20;
int y = random.nextInt(MASTER_SIZE + 40) - 20;
g.setColor(alpha(mob.accent(), random.nextInt(56) + 40));
g.fillOval(x, y, radius, radius);
}
for (int i = 0; i < 55; i++) {
int x = random.nextInt(MASTER_SIZE);
int y = random.nextInt(MASTER_SIZE);
int size = random.nextInt(5) + 2;
g.setColor(alpha(tint(mob.accent(), 0.35), random.nextInt(31) + 18));
g.fillRect(x, y, size, size);
}
g.dispose();
}
private static void drawHead(Graphics2D g, MobArt mob, Random random) {
Color outline = shade(mob.dark(), 0.18);
block(g, 16, 15, 32, 26, mob.base(), outline);
block(g, 18, 17, 28, 9, tint(mob.light(), 0.08), null);
block(g, 18, 26, 28, 13, mob.base(), null);
block(g, 22, 41, 20, 8, shade(mob.base(), 0.12), outline);
if (has(mob, "spots")) {
spot(g, 20, 20, 4, 4, shade(mob.dark(), 0.08));
spot(g, 39, 23, 5, 4, shade(mob.dark(), 0.08));
spot(g, 24, 31, 4, 3, shade(mob.dark(), 0.08));
}
if (has(mob, "wool")) {
puff(g, 16, 14, mob, outline);
puff(g, 22, 11, mob, outline);
puff(g, 30, 10, mob, outline);
puff(g, 38, 12, mob, outline);
puff(g, 44, 15, mob, outline);
}
if (has(mob, "moss")) {
block(g, 18, 14, 9, 4, rgb(92, 138, 78), null);
block(g, 37, 15, 7, 3, rgb(92, 138, 78), null);
block(g, 30, 18, 8, 3, rgb(92, 138, 78), null);
}
if (has(mob, "shell")) {
block(g, 21, 21, 22, 17, mob.light(), outline);
for (int stripeX : new int[]{25, 32, 39}) {
block(g, stripeX, 23, 2, 13, tint(mob.base(), 0.1), null);
}
}
if (has(mob, "frog_eyes")) {
eyeBump(g, 18, mob.light(), outline);
eyeBump(g, 40, mob.light(), outline);
}
if (has(mob, "ears")) {
ear(g, 15, mob.base(), outline);
ear(g, 43, mob.base(), outline);
}
if (has(mob, "long_ears")) {
longEar(g, 15, mob.base(), mob.light(), outline);
longEar(g, 44, mob.base(), mob.light(), outline);
}
if (has(mob, "horns")) {
horn(g, 16, mob.light(), outline);
horn(g, 45, mob.light(), outline);
}
if (has(mob, "gills")) {
block(g, 9, 22, 4, 12, tint(mob.base(), 0.06), outline);
block(g, 51, 22, 4, 12, tint(mob.base(), 0.06), outline);
}
if (has(mob, "beard")) {
block(g, 30, 47, 4, 5, tint(mob.light(), 0.12), outline);
}
if (has(mob, "mushroom")) {
mushroom(g, 15, outline);
mushroom(g, 39, outline);
}
if (has(mob, "wings")) {
wing(g, 2, tint(mob.light(), 0.25), tint(mob.light(), 0.05));
wing(g, 54, tint(mob.light(), 0.25), tint(mob.light(), 0.05));
}
if (has(mob, "tentacles")) {
int[] tentacles = {19, 24, 29, 34, 39, 44};
for (int i = 0; i < tentacles.length; i++) {
block(g, tentacles[i], 41 + (i % 2), 3, 12, shade(mob.base(), 0.12), outline);
}
}
if (has(mob, "fins")) {
fin(g, 8, tint(mob.light(), 0.18), outline);
fin(g, 51, tint(mob.light(), 0.18), outline);
block(g, 29, 11, 6, 7, tint(mob.light(), 0.18), outline);
}
if (has(mob, "stripe")) {
block(g, 18, 22, 28, 3, mob.dark(), null);
block(g, 18, 28, 28, 3, mob.dark(), null);
block(g, 18, 34, 28, 3, mob.dark(), null);
}
if (has(mob, "glow")) {
sparkle(g, 20, 18, 4, 4, tint(mob.accent(), 0.25));
sparkle(g, 40, 21, 3, 3, tint(mob.accent(), 0.25));
sparkle(g, 24, 31, 3, 3, tint(mob.accent(), 0.25));
sparkle(g, 37, 34, 4, 4, tint(mob.accent(), 0.25));
}
if (has(mob, "panda_eyes")) {
eyePatch(g, 16, mob.dark(), outline);
eyePatch(g, 40, mob.dark(), outline);
}
if (has(mob, "crest")) {
block(g, 28, 11, 8, 4, mob.accent(), outline);
}
if (has(mob, "snout")) {
block(g, 24, 34, 16, 9, mob.light(), outline);
block(g, 27, 37, 3, 3, mob.dark(), null);
block(g, 34, 37, 3, 3, mob.dark(), null);
}
Color eyeWhite = tint(mob.light(), 0.25);
Color eyeFill = ("panda".equals(mob.name()) || "wolf".equals(mob.name())) ? rgb(212, 61, 56) : mob.accent();
eye(g, 21, eyeWhite, eyeFill, outline);
eye(g, 37, eyeWhite, eyeFill, outline);
if ("cod".equals(mob.name()) || "salmon".equals(mob.name())) {
block(g, 18, 30, 4, 2, mob.dark(), null);
block(g, 42, 30, 4, 2, mob.dark(), null);
}
switch (mob.name()) {
case "bee" -> block(g, 24, 35, 16, 6, tint(mob.base(), 0.1), outline);
case "parrot" -> {
block(g, 28, 33, 8, 10, mob.accent(), outline);
block(g, 31, 38, 2, 3, mob.dark(), null);
}
case "chicken" -> {
block(g, 27, 33, 10, 7, mob.accent(), outline);
block(g, 29, 40, 6, 3, rgb(220, 41, 38), null);
}
case "frog" -> block(g, 25, 37, 14, 3, shade(mob.base(), 0.2), null);
case "glow_squid" -> block(g, 18, 18, 28, 20, shade(mob.base(), 0.05), outline);
case "strider" -> {
block(g, 24, 17, 16, 10, shade(mob.base(), 0.08), outline);
block(g, 20, 24, 24, 4, tint(mob.light(), 0.1), null);
}
case "sniffer" -> {
block(g, 18, 24, 28, 16, shade(mob.base(), 0.04), outline);
block(g, 23, 35, 18, 6, rgb(229, 120, 67), outline);
}
default -> {
}
}
Color shadow = shade(mob.base(), 0.18);
fill(g, 18, 40, 28, 1, shadow);
fill(g, 16, 39, 2, 2, shadow);
fill(g, 46, 39, 2, 2, shadow);
for (int i = 0; i < 8; i++) {
int x = random.nextInt(33) + 14;
int y = random.nextInt(37) + 13;
if (random.nextDouble() < 0.35) {
fill(g, x, y, 1, 1, tint(mob.light(), 0.18));
}
}
}
private static void addForeground(Graphics2D g, MobArt mob, Random random) {
for (int i = 0; i < 12; i++) {
int x = random.nextInt(59);
int y = random.nextInt(15) + 48;
int height = random.nextInt(7) + 2;
fill(g, x, y, 2, height, tint(mob.backgroundTop(), 0.06));
}
if (List.of("cod", "salmon", "squid", "glow_squid", "axolotl", "turtle").contains(mob.name())) {
Color wave = tint(mob.backgroundTop(), 0.12);
for (int y : new int[]{45, 50, 55}) {
for (int x = 0; x < 64; x += 6) {
fill(g, x, y, 4, 1, wave);
}
}
}
if (List.of("bee", "parrot").contains(mob.name())) {
fill(g, 10, 16, 2, 2, tint(mob.light(), 0.35));
fill(g, 49, 16, 2, 2, tint(mob.light(), 0.35));
}
if (List.of("mooshroom", "cow", "sheep", "pig", "horse", "goat").contains(mob.name())) {
fill(g, 11, 52, 4, 6, shade(mob.dark(), 0.1));
fill(g, 50, 52, 4, 6, shade(mob.dark(), 0.1));
}
}
private static void addAtmosphere(Graphics2D g, MobArt mob, Random random) {
for (int i = 0; i < 18; i++) {
int x = random.nextInt(MASTER_SIZE - 20) + 10;
int y = random.nextInt(MASTER_SIZE - 16) + 8;
int size = random.nextInt(5) + 3;
g.setColor(alpha(tint(mob.accent(), 0.25), random.nextInt(36) + 20));
g.fillOval(x, y, size, size);
}
}
private static BufferedImage scale(BufferedImage source, int width, int height) {
BufferedImage scaled = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = scaled.createGraphics();
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
g.drawImage(source, 0, 0, width, height, null);
g.dispose();
return scaled;
}
private static MobArt mob(String name, Color base, Color dark, Color light, Color backgroundTop, Color backgroundBottom,
Color accent, String... features) {
return new MobArt(name, base, dark, light, backgroundTop, backgroundBottom, accent, List.of(features));
}
private static boolean has(MobArt mob, String feature) {
return mob.features().contains(feature);
}
private static void puff(Graphics2D g, int x, int y, MobArt mob, Color outline) {
block(g, x, y, 6, 6, tint(mob.light(), 0.08), outline);
}
private static void ear(Graphics2D g, int x, Color base, Color outline) {
block(g, x, 9, 6, 8, base, outline);
}
private static void longEar(Graphics2D g, int x, Color base, Color light, Color outline) {
block(g, x, 4, 5, 14, base, outline);
block(g, x + 1, 7, 2, 8, tint(light, 0.14), null);
}
private static void horn(Graphics2D g, int x, Color light, Color outline) {
block(g, x, 8, 3, 9, tint(light, 0.2), outline);
}
private static void mushroom(Graphics2D g, int x, Color outline) {
block(g, x, 7, 10, 4, rgb(222, 204, 196), outline);
block(g, x + 2, 3, 6, 6, rgb(194, 51, 47), outline);
}
private static void wing(Graphics2D g, int x, Color fill, Color outline) {
block(g, x, 20, 8, 14, fill, outline);
}
private static void fin(Graphics2D g, int x, Color fill, Color outline) {
block(g, x, 25, 5, 10, fill, outline);
}
private static void eyeBump(Graphics2D g, int x, Color fill, Color outline) {
block(g, x, 10, 6, 6, fill, outline);
}
private static void eyePatch(Graphics2D g, int x, Color fill, Color outline) {
block(g, x, 24, 8, 8, fill, outline);
}
private static void eye(Graphics2D g, int x, Color white, Color iris, Color outline) {
block(g, x, 24, 5, 5, white, outline);
block(g, x + 1, 25, 2, 2, iris, null);
}
private static void sparkle(Graphics2D g, int x, int y, int w, int h, Color fill) {
block(g, x, y, w, h, fill, null);
}
private static void spot(Graphics2D g, int x, int y, int w, int h, Color fill) {
block(g, x, y, w, h, fill, null);
}
private static void block(Graphics2D g, int x, int y, int w, int h, Color fill, Color outline) {
fill(g, x, y, w, h, fill);
if (outline != null) {
fill(g, x, y, w, 1, outline);
fill(g, x, y + h - 1, w, 1, outline);
fill(g, x, y, 1, h, outline);
fill(g, x + w - 1, y, 1, h, outline);
}
}
private static void fill(Graphics2D g, int x, int y, int w, int h, Color color) {
g.setColor(color);
g.fillRect(x * PIXEL, y * PIXEL, w * PIXEL, h * PIXEL);
}
private static Color tint(Color color, double amount) {
return new Color(
clamp(color.getRed() + ((255 - color.getRed()) * amount)),
clamp(color.getGreen() + ((255 - color.getGreen()) * amount)),
clamp(color.getBlue() + ((255 - color.getBlue()) * amount))
);
}
private static Color shade(Color color, double amount) {
return new Color(
clamp(color.getRed() * (1.0 - amount)),
clamp(color.getGreen() * (1.0 - amount)),
clamp(color.getBlue() * (1.0 - amount))
);
}
private static Color shift(Color color, int amount) {
return new Color(
clamp(color.getRed() + amount),
clamp(color.getGreen() + amount),
clamp(color.getBlue() + amount)
);
}
private static Color mix(Color a, Color b, double amount) {
return new Color(
clamp(a.getRed() + ((b.getRed() - a.getRed()) * amount)),
clamp(a.getGreen() + ((b.getGreen() - a.getGreen()) * amount)),
clamp(a.getBlue() + ((b.getBlue() - a.getBlue()) * amount))
);
}
private static Color alpha(Color color, int alpha) {
return new Color(color.getRed(), color.getGreen(), color.getBlue(), alpha);
}
private static Color rgb(int red, int green, int blue) {
return new Color(red, green, blue);
}
private static int clamp(double value) {
return Math.max(0, Math.min(255, (int) Math.round(value)));
}
private static double clamp01(double value) {
return Math.max(0.0, Math.min(1.0, value));
}
}

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

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