6
6
import com .mojang .brigadier .exceptions .SimpleCommandExceptionType ;
7
7
import net .fabricmc .fabric .api .client .command .v2 .FabricClientCommandSource ;
8
8
import net .minecraft .block .MapColor ;
9
+ import net .minecraft .block .entity .BlockEntity ;
9
10
import net .minecraft .client .MinecraftClient ;
10
11
import net .minecraft .client .network .ClientPlayerEntity ;
11
12
import net .minecraft .client .texture .NativeImage ;
12
13
import net .minecraft .client .util .ScreenshotRecorder ;
14
+ import net .minecraft .entity .Entity ;
15
+ import net .minecraft .entity .decoration .ItemFrameEntity ;
13
16
import net .minecraft .item .FilledMapItem ;
14
17
import net .minecraft .item .ItemStack ;
15
18
import net .minecraft .item .map .MapState ;
16
19
import net .minecraft .text .ClickEvent ;
17
20
import net .minecraft .text .Text ;
21
+ import net .minecraft .util .math .BlockPos ;
22
+ import net .minecraft .util .math .Direction ;
23
+ import net .minecraft .util .math .Vec3i ;
24
+ import net .minecraft .world .World ;
18
25
19
26
import java .io .File ;
20
27
import java .io .IOException ;
28
+ import java .util .*;
29
+ import java .util .function .Function ;
30
+ import java .util .stream .Collectors ;
31
+ import java .util .stream .StreamSupport ;
21
32
22
33
import static com .mojang .brigadier .arguments .IntegerArgumentType .*;
23
34
import static net .fabricmc .fabric .api .client .command .v2 .ClientCommandManager .*;
@@ -37,40 +48,33 @@ public static void register(CommandDispatcher<FabricClientCommandSource> dispatc
37
48
);
38
49
}
39
50
40
- private static MapState getMapState (ClientPlayerEntity player ) throws CommandSyntaxException {
41
- ItemStack map ;
42
-
43
- // detect if map in hand
44
- if (player .getMainHandStack ().getItem () instanceof FilledMapItem ) {
45
- map = player .getMainHandStack ();
46
- } else if (player .getOffHandStack ().getItem () instanceof FilledMapItem ) {
47
- map = player .getOffHandStack ();
48
- } else {
49
- throw NO_HELD_MAP_EXCEPTION .create ();
51
+ private static MapInfo [][] getMaps (ClientPlayerEntity player , MinecraftClient client ) throws CommandSyntaxException {
52
+ MapInfo hand = fromHand (player );
53
+ if (hand != null ) {
54
+ return new MapInfo [][]{{hand }};
50
55
}
51
-
52
- Integer mapId = FilledMapItem .getMapId (map );
53
- MapState mapState = FilledMapItem .getMapState (mapId , player .world );
54
- if (mapState == null ) {
56
+ MapInfo [][] mapInfo = fromWorld (player , client .targetedEntity );
57
+ if (mapInfo == null ) {
55
58
throw NO_HELD_MAP_EXCEPTION .create ();
56
59
}
57
- return mapState ;
60
+ return mapInfo ;
58
61
}
59
62
60
63
61
64
private static int exportMap (FabricClientCommandSource source , int upscale ) throws CommandSyntaxException {
62
- MapState mapState = getMapState (source .getPlayer ());
63
- try ( NativeImage image = new NativeImage ( NativeImage . Format . RGBA , 128 * upscale , 128 * upscale , false )) {
64
- for ( int i = 0 ; i < 128 * upscale ; i += upscale ) {
65
- for ( int j = 0 ; j < 128 * upscale ; j += upscale ) {
66
- int x = i / upscale ;
67
- int y = j / upscale ;
68
- int color = MapColor . getRenderColor ( mapState . colors [ x + y * 128 ]);
69
- for (int k = 0 ; k < upscale ; k ++ ) {
70
- for ( int l = 0 ; l < upscale ; l ++) {
71
- image . setColor ( i + k , j + l , color );
72
- }
65
+ MapInfo [][] mapInfo = getMaps (source .getPlayer (), source . getClient ());
66
+ // calculate width and height
67
+ int width = mapInfo [ 0 ]. length * 128 * upscale ;
68
+ int height = mapInfo . length * 128 * upscale ;
69
+
70
+ try ( NativeImage image = new NativeImage ( NativeImage . Format . RGBA , width , height , true )) {
71
+ for ( int i = 0 ; i < mapInfo . length ; ++ i ) {
72
+ for (int j = 0 ; j < mapInfo [ i ]. length ; ++ j ) {
73
+ MapInfo info = mapInfo [ i ][ j ];
74
+ if ( info == null ) {
75
+ continue ;
73
76
}
77
+ drawMapOffset (image , info , j * 128 * upscale , i * 128 * upscale , upscale );
74
78
}
75
79
}
76
80
File screenshotDir = new File (source .getClient ().runDirectory , "screenshots" );
@@ -88,4 +92,178 @@ private static int exportMap(FabricClientCommandSource source, int upscale) thro
88
92
return Command .SINGLE_SUCCESS ;
89
93
}
90
94
95
+ private static void drawMapOffset (NativeImage image , MapInfo map , int xOff , int yOff , int upscale ) {
96
+ for (int i = 0 ; i < 128 ; ++i ) {
97
+ for (int j = 0 ; j < 128 ; ++j ) {
98
+ int color = MapColor .getRenderColor (map .getColor (i , j ));
99
+ for (int k = 0 ; k < upscale ; k ++) {
100
+ for (int l = 0 ; l < upscale ; l ++) {
101
+ image .setColor (i * upscale + k + xOff , j * upscale + l + yOff , color );
102
+ }
103
+ }
104
+ }
105
+ }
106
+ }
107
+
108
+ private static MapInfo fromHand (ClientPlayerEntity player ) throws CommandSyntaxException {
109
+ ItemStack map ;
110
+
111
+ // detect if map in hand
112
+ if (player .getMainHandStack ().getItem () instanceof FilledMapItem ) {
113
+ map = player .getMainHandStack ();
114
+ } else if (player .getOffHandStack ().getItem () instanceof FilledMapItem ) {
115
+ map = player .getOffHandStack ();
116
+ } else {
117
+ return null ;
118
+ }
119
+
120
+ return new MapInfo (fromItemStack (player .world , map ), 0 );
121
+ }
122
+
123
+ private static MapInfo [][] fromWorld (ClientPlayerEntity player , Entity target ) {
124
+ if (target instanceof ItemFrameEntity frame ) {
125
+ ItemStack map = frame .getHeldItemStack ();
126
+ if (map == null || !(map .getItem () instanceof FilledMapItem )) {
127
+ return null ;
128
+ }
129
+ Direction facing = frame .getHorizontalFacing ();
130
+ // find in world
131
+ BlockPos pos = frame .getBlockPos ();
132
+ FramePos current = new FramePos (pos , facing );
133
+ // get all loaded item frames
134
+ Map <FramePos , ItemFrameEntity > entities = StreamSupport .stream (player .clientWorld .getEntities ().spliterator (), true ).filter (e -> e instanceof ItemFrameEntity && ((ItemFrameEntity ) e ).getMapId ().isPresent ()).map (e -> (ItemFrameEntity ) e ).collect (Collectors .toMap (e -> new FramePos (e .getBlockPos (), e .getHorizontalFacing ()), Function .identity ()));
135
+ Set <ItemFrameEntity > connected = new HashSet <>();
136
+ getConnectedItemFrames (connected , entities , current );
137
+ // put them in proper traversal order
138
+ Direction yAxis = switch (facing ) {
139
+ case NORTH , SOUTH , EAST , WEST -> Direction .DOWN ;
140
+ case UP -> player .getHorizontalFacing ().getOpposite ();
141
+ case DOWN -> player .getHorizontalFacing ();
142
+ };
143
+ Direction xAxis = switch (facing ) {
144
+ case NORTH -> Direction .WEST ;
145
+ case EAST -> Direction .NORTH ;
146
+ case SOUTH -> Direction .EAST ;
147
+ case WEST -> Direction .SOUTH ;
148
+ case UP -> Direction .fromHorizontal ((yAxis .getHorizontal () + 3 ) % 4 );
149
+ case DOWN -> Direction .fromHorizontal ((yAxis .getHorizontal () + 1 ) % 4 );
150
+ };
151
+ Map <Vec2i , ItemFrameEntity > flattened = new HashMap <>();
152
+ Iterator <ItemFrameEntity > iterator = connected .iterator ();
153
+ ItemFrameEntity origin = iterator .next ();
154
+ flattened .put (new Vec2i (0 , 0 ), origin );
155
+ BlockPos zero = origin .getBlockPos ();
156
+ while (iterator .hasNext ()) {
157
+ ItemFrameEntity next = iterator .next ();
158
+ // calculate offset in 2d from 0 0
159
+ BlockPos nextPos = next .getBlockPos ();
160
+ Vec3i vec = nextPos .subtract (zero );
161
+ int y = vec .getComponentAlongAxis (yAxis .getAxis ()) * yAxis .getDirection ().offset ();
162
+ int x = vec .getComponentAlongAxis (xAxis .getAxis ()) * xAxis .getDirection ().offset ();
163
+ // this should never be possible
164
+ if (flattened .put (new Vec2i (x , y ), next ) != null ) throw new IllegalStateException ("Duplicate item frame at " + nextPos );
165
+ }
166
+ System .out .println (flattened );
167
+ // find min and max
168
+ Iterator <Vec2i > vec2iIterator = flattened .keySet ().iterator ();
169
+ Vec2i first = vec2iIterator .next ();
170
+ int minX = first .x ();
171
+ int maxX = first .x ();
172
+ int minY = first .z ();
173
+ int maxY = first .z ();
174
+ while (vec2iIterator .hasNext ()) {
175
+ Vec2i next = vec2iIterator .next ();
176
+ minX = Math .min (minX , next .x ());
177
+ maxX = Math .max (maxX , next .x ());
178
+ minY = Math .min (minY , next .z ());
179
+ maxY = Math .max (maxY , next .z ());
180
+ }
181
+ // create array
182
+ MapInfo [][] mapInfo = new MapInfo [maxY - minY + 1 ][maxX - minX + 1 ];
183
+ for (Map .Entry <Vec2i , ItemFrameEntity > entry : flattened .entrySet ()) {
184
+ Vec2i pos2 = entry .getKey ();
185
+ ItemFrameEntity entity = entry .getValue ();
186
+ int rotationOffset = 0 ;
187
+ if (yAxis != Direction .DOWN ) {
188
+ rotationOffset = switch (yAxis ) {
189
+ case WEST -> 3 ;
190
+ case EAST -> 1 ;
191
+ case SOUTH -> facing == Direction .UP ? 0 : 2 ;
192
+ default -> facing == Direction .UP ? 2 : 0 ;
193
+ };
194
+ }
195
+ mapInfo [pos2 .z () - minY ][pos2 .x () - minX ] = new MapInfo (fromItemFrame (entry .getValue ()), entity .getRotation () + rotationOffset );
196
+ }
197
+ return mapInfo ;
198
+ }
199
+ return null ;
200
+ }
201
+
202
+ private static void getConnectedItemFrames (Set <ItemFrameEntity > already , Map <FramePos , ItemFrameEntity > frames , FramePos current ) {
203
+ if (frames .containsKey (current )) {
204
+ ItemFrameEntity frame = frames .get (current );
205
+ if (!already .contains (frame )) {
206
+ already .add (frame );
207
+ BlockPos [] adjacent = switch (current .facing ().getAxis ()) {
208
+ case X -> new BlockPos []{current .pos ().north (), current .pos ().south (), current .pos ().up (), current .pos ().down ()};
209
+ case Y -> new BlockPos []{current .pos ().north (), current .pos ().south (), current .pos ().east (), current .pos ().west ()};
210
+ case Z -> new BlockPos []{current .pos ().east (), current .pos ().west (), current .pos ().up (), current .pos ().down ()};
211
+ };
212
+ for (BlockPos pos : adjacent ) {
213
+ getConnectedItemFrames (already , frames , new FramePos (pos , current .facing ()));
214
+ }
215
+ }
216
+ }
217
+ }
218
+
219
+ private static MapState fromItemStack (World world , ItemStack map ) throws CommandSyntaxException {
220
+ Integer mapId = FilledMapItem .getMapId (map );
221
+ MapState mapState = FilledMapItem .getMapState (mapId , world );
222
+ if (mapState == null ) {
223
+ throw NO_HELD_MAP_EXCEPTION .create ();
224
+ }
225
+
226
+ return mapState ;
227
+ }
228
+
229
+ private static MapState fromItemFrame (ItemFrameEntity entity ) {
230
+ Integer mapId = entity .getMapId ().orElseThrow ();
231
+ return FilledMapItem .getMapState (mapId , entity .world );
232
+ }
233
+
234
+ private record MapInfo (MapState state , int rotation ) {
235
+
236
+ public int getColor (int x , int y ) {
237
+ // rotate x/y
238
+ switch (rotation % 4 ) {
239
+ case 0 -> {
240
+ return state .colors [x + y * 128 ];
241
+ }
242
+ case 1 -> {
243
+ // 90 clockwise
244
+ int newX = y ;
245
+ int newY = 127 - x ;
246
+ return state .colors [newX + newY * 128 ];
247
+ }
248
+ case 2 -> {
249
+ // 180 clockwise
250
+ int newX = 127 - x ;
251
+ int newY = 127 - y ;
252
+ return state .colors [newX + newY * 128 ];
253
+ }
254
+ case 3 -> {
255
+ // 270 clockwise
256
+ int newX = 127 - y ;
257
+ int newY = x ;
258
+ return state .colors [newX + newY * 128 ];
259
+ }
260
+ // this should never be possible
261
+ default -> throw new IllegalStateException ("Unexpected value: " + rotation );
262
+ }
263
+ }
264
+ }
265
+
266
+ private record FramePos (BlockPos pos , Direction facing ) {
267
+ }
268
+
91
269
}
0 commit comments