1+ package com .github .retrooper .packetevents .test ;
2+
3+ import com .github .retrooper .packetevents .manager .server .ServerVersion ;
4+ import com .github .retrooper .packetevents .protocol .player .ClientVersion ;
5+ import com .github .retrooper .packetevents .protocol .world .states .WrappedBlockState ;
6+ import com .github .retrooper .packetevents .protocol .world .states .defaulttags .BlockTags ;
7+ import com .github .retrooper .packetevents .protocol .world .states .type .StateType ;
8+ import com .github .retrooper .packetevents .protocol .world .states .type .StateTypes ;
9+ import com .github .retrooper .packetevents .test .base .BaseDummyAPITest ;
10+ import io .github .retrooper .packetevents .util .SpigotConversionUtil ;
11+ import org .junit .jupiter .api .DisplayName ;
12+ import org .junit .jupiter .api .Test ;
13+
14+ import java .util .ArrayList ;
15+ import java .util .Collection ;
16+ import java .util .List ;
17+ import java .util .function .Function ;
18+
19+ import com .github .retrooper .packetevents .PacketEvents ;
20+ import org .bukkit .Material ;
21+ import org .bukkit .material .MaterialData ; //Needed for 1.12 and below support.
22+ import org .junit .jupiter .params .ParameterizedTest ;
23+ import org .junit .jupiter .params .provider .EnumSource ;
24+
25+ import static org .junit .jupiter .api .Assertions .assertEquals ;
26+ import static org .junit .jupiter .api .Assertions .fail ;
27+
28+ public class StateTypeMappingTest extends BaseDummyAPITest {
29+
30+ @ ParameterizedTest
31+ @ EnumSource (ClientVersion .class )
32+ @ DisplayName ("Verify StateType mappings for all client versions" )
33+ public void testStateTypeMappings (ClientVersion version ) {
34+ testStateTypeMappings (version , false );
35+ }
36+
37+ @ Test
38+ @ DisplayName ("Verify StateType mappings (fail fast)" )
39+ public void testStateTypeMappingsFailFast () {
40+ // Use the server version.
41+ ServerVersion serverVersion = PacketEvents .getAPI ().getServerManager ().getVersion ();
42+ ClientVersion version = serverVersion .toClientVersion ();
43+ testStateTypeMappings (version , true );
44+ }
45+
46+ public void testStateTypeMappings (ClientVersion version , boolean failFast ) {
47+ final ServerVersion serverVersion = PacketEvents .getAPI ().getServerManager ().getVersion ();
48+ Function <Material , WrappedBlockState > blockStateFunction = getBlockStateFunction (serverVersion );
49+
50+ StringBuilder errorMessages = new StringBuilder (); // Accumulate error messages for diagnostic mode
51+
52+ Collection <StateType > stateValues = StateTypes .values ();
53+ int found = 0 ;
54+ int idsMatched = 0 ;
55+
56+ // Get all BlockTags for versions newer than the server version
57+ List <BlockTags > newerBlockTags = getVersionBlockTagsNewerThan (serverVersion );
58+ int expected = stateValues .size ();
59+
60+ // Check if the client version is newer than the server version
61+ ClientVersion serverClientVersion = serverVersion .toClientVersion ();
62+ boolean isClientNewer = version .isNewerThan (serverClientVersion );
63+
64+ for (StateType value : stateValues ) {
65+ String name = value .getName ();
66+ int id = value .getMapped ().getId (version );
67+
68+ // Case 1: Block is from a newer version than the server (id == -1)
69+ if (id == -1 && isBlockFromNewerVersion (value , newerBlockTags )) {
70+ // Skip validation for blocks from newer versions and mark as found so there is no count error at the end
71+ expected --;
72+ continue ;
73+ }
74+
75+ Material material = Material .matchMaterial (name ); // This will return null for materials like potted_open_eyeblossom (added in 1.21.4) on 1.21 server
76+
77+ // Case 2: Client is newer, block exists in client (id != -1), but not in server (material == null)
78+ if (isClientNewer && id != -1 && material == null && isBlockFromNewerVersion (value , newerBlockTags )) {
79+ // This is expected behavior: the client knows the block, but the server does not
80+ expected --;
81+ continue ;
82+ }
83+
84+ // Case 3: Material is missing unexpectedly (not from a newer version)
85+ if (material == null ) {
86+ String errorMessage = String .format ("Material not found for statetype %s, id=%d" , name , id );
87+ if (failFast ) {
88+ fail (errorMessage );
89+ return ; // Just to make sure it exits.
90+ } else {
91+ errorMessages .append (errorMessage ).append ("\n " );
92+ }
93+ continue ;
94+ }
95+ found ++;
96+
97+ WrappedBlockState state = blockStateFunction .apply (material );
98+ if (state == null ) {
99+ String errorMessage = String .format ("Failed to create BlockState from material %s, id=%d" , material .name (), id );
100+ if (failFast ) {
101+ fail (errorMessage );
102+ return ;
103+ } else {
104+ errorMessages .append (errorMessage ).append ("\n " );
105+ }
106+ continue ;
107+ }
108+
109+ if (state .getType () != value ) {
110+ String errorMessage = String .format ("State type mismatch for material %s, type=%s, value=%s" , material .name (), state .getType (), value );
111+ if (failFast ) {
112+ fail (errorMessage );
113+ return ;
114+ } else {
115+ errorMessages .append (errorMessage ).append ("\n " );
116+ }
117+ continue ;
118+ }
119+ idsMatched ++;
120+ }
121+
122+ final int missing = expected - found ;
123+
124+ // Diagnostic output (non-fail-fast mode)
125+ if (!failFast && errorMessages .length () > 0 ) {
126+ System .err .println ("StateType Mapping Errors:" );
127+ System .err .println (errorMessages );
128+
129+ // Output summary
130+ System .err .println (String .format ("%d/%d statetypes found" , found , expected ));
131+ if (missing > 0 ) {
132+ double percent = ((double ) found / expected ) * 100 ;
133+ System .err .println (String .format ("%d missing (%.2f%%)" , missing , percent ));
134+ }
135+ System .err .println (String .format ("%d/%d ids matched" , idsMatched , found ));
136+ }
137+
138+ // Only fail the test if there are unexpected missing StateTypes
139+ assertEquals (expected , found , String .format ("Not all StateTypes found for version %s. Missing: %d. See error log for details." , version .getReleaseName (), missing ));
140+ assertEquals (found , idsMatched , String .format ("Not all StateType IDs matched for version %s. See error log for details." , version .getReleaseName ()));
141+ }
142+
143+ private Function <Material , WrappedBlockState > getBlockStateFunction (ServerVersion serverVersion ) {
144+ if (serverVersion .isOlderThanOrEquals (ServerVersion .V_1_12 )) {
145+ return material -> SpigotConversionUtil .fromBukkitMaterialData (new MaterialData (material ));
146+ } else {
147+ return material -> SpigotConversionUtil .fromBukkitBlockData (material .createBlockData ());
148+ }
149+ }
150+
151+ /**
152+ * Gets all BlockTags for versions newer than the server version.
153+ * Relies on BlockTags existing with names V_1_20_5, V_1_21_2, V_1_21_4, etc...
154+ * for versions newer than the Mocked server version (currently 1.21.1 from MockBukkit)
155+ */
156+ private List <BlockTags > getVersionBlockTagsNewerThan (ServerVersion serverVersion ) {
157+ List <BlockTags > blockTags = new ArrayList <>();
158+ for (ServerVersion version : ServerVersion .values ()) {
159+ if (version .isNewerThan (serverVersion )) { // Use isNewerThan to exclude the server's own version
160+ BlockTags blockTag = BlockTags .getByName (version .name ()); // Use name() to match enum naming convention
161+ if (blockTag != null ) { // Only add non-null tags
162+ blockTags .add (blockTag );
163+ }
164+ }
165+ }
166+ return blockTags ;
167+ }
168+
169+ /**
170+ * Determines if the block is from a version newer than the server's version.
171+ */
172+ private boolean isBlockFromNewerVersion (StateType stateType , List <BlockTags > newerBlockTags ) {
173+ // Check if the StateType is tagged in any of the newer BlockTags
174+ for (BlockTags tag : newerBlockTags ) {
175+ if (tag .contains (stateType )) {
176+ return true ;
177+ }
178+ }
179+ return false ;
180+ }
181+ }
0 commit comments