66import com .mojang .brigadier .exceptions .SimpleCommandExceptionType ;
77import net .fabricmc .fabric .api .client .command .v2 .FabricClientCommandSource ;
88import net .minecraft .block .MapColor ;
9+ import net .minecraft .block .entity .BlockEntity ;
910import net .minecraft .client .MinecraftClient ;
1011import net .minecraft .client .network .ClientPlayerEntity ;
1112import net .minecraft .client .texture .NativeImage ;
1213import net .minecraft .client .util .ScreenshotRecorder ;
14+ import net .minecraft .entity .Entity ;
15+ import net .minecraft .entity .decoration .ItemFrameEntity ;
1316import net .minecraft .item .FilledMapItem ;
1417import net .minecraft .item .ItemStack ;
1518import net .minecraft .item .map .MapState ;
1619import net .minecraft .text .ClickEvent ;
1720import 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 ;
1825
1926import java .io .File ;
2027import 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 ;
2132
2233import static com .mojang .brigadier .arguments .IntegerArgumentType .*;
2334import static net .fabricmc .fabric .api .client .command .v2 .ClientCommandManager .*;
@@ -37,40 +48,33 @@ public static void register(CommandDispatcher<FabricClientCommandSource> dispatc
3748 );
3849 }
3950
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 }};
5055 }
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 ) {
5558 throw NO_HELD_MAP_EXCEPTION .create ();
5659 }
57- return mapState ;
60+ return mapInfo ;
5861 }
5962
6063
6164 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 ;
7376 }
77+ drawMapOffset (image , info , j * 128 * upscale , i * 128 * upscale , upscale );
7478 }
7579 }
7680 File screenshotDir = new File (source .getClient ().runDirectory , "screenshots" );
@@ -88,4 +92,178 @@ private static int exportMap(FabricClientCommandSource source, int upscale) thro
8892 return Command .SINGLE_SUCCESS ;
8993 }
9094
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+
91269}
0 commit comments