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