1+ package net .swofty .type .generic .utility ;
2+
3+ import lombok .Getter ;
4+ import net .minestom .server .component .DataComponents ;
5+ import net .minestom .server .coordinate .Pos ;
6+ import net .minestom .server .entity .Entity ;
7+ import net .minestom .server .entity .EntityType ;
8+ import net .minestom .server .entity .metadata .other .ItemFrameMeta ;
9+ import net .minestom .server .instance .Instance ;
10+ import net .minestom .server .item .ItemStack ;
11+ import net .minestom .server .item .Material ;
12+ import net .minestom .server .network .packet .server .play .MapDataPacket ;
13+ import net .minestom .server .utils .Direction ;
14+ import net .swofty .type .generic .user .HypixelPlayer ;
15+
16+ import java .io .ByteArrayInputStream ;
17+ import java .io .IOException ;
18+ import java .util .Base64 ;
19+ import java .util .List ;
20+ import java .util .concurrent .ConcurrentHashMap ;
21+ import java .util .zip .GZIPInputStream ;
22+
23+ public abstract class AbstractMapSystem {
24+ private static final ConcurrentHashMap <Integer , byte []> MAP_COLOR_CACHE = new ConcurrentHashMap <>(64 , 0.75f , 1 );
25+
26+ private MapDataPacket [] mapPacketCache ;
27+ private volatile boolean initialized = false ;
28+
29+ protected abstract MapConfiguration getConfiguration ();
30+ protected abstract String [] getCompressedMapData ();
31+
32+ private void initializeMapPackets () {
33+ if (initialized ) return ;
34+
35+ synchronized (this ) {
36+ if (initialized ) return ;
37+
38+ MapConfiguration config = getConfiguration ();
39+ String [] compressedData = getCompressedMapData ();
40+ int totalMaps = config .getColumns () * config .getRows ();
41+
42+ if (compressedData .length != totalMaps ) {
43+ throw new IllegalStateException (
44+ "Expected " + totalMaps + " map data strings, got " + compressedData .length
45+ );
46+ }
47+
48+ mapPacketCache = new MapDataPacket [totalMaps ];
49+
50+ for (int i = 0 ; i < totalMaps ; i ++) {
51+ mapPacketCache [i ] = new MapDataPacket (
52+ 1 + i ,
53+ (byte ) 0 ,
54+ false ,
55+ false ,
56+ List .of (),
57+ new MapDataPacket .ColorContent (
58+ (byte ) 128 ,
59+ (byte ) 128 ,
60+ (byte ) 0 ,
61+ (byte ) 0 ,
62+ decodeMapColors (compressedData [i ])
63+ )
64+ );
65+ }
66+
67+ initialized = true ;
68+ }
69+ }
70+
71+ public void placeItemFrames (Instance instance ) {
72+ MapConfiguration c = getConfiguration ();
73+ int yStart = c .topLeft .blockY ();
74+
75+ for (int row = 0 ; row < c .rows ; row ++) {
76+ int y = yStart - row ;
77+
78+ for (int col = 0 ; col < c .columns ; col ++) {
79+ Pos pos = c .facing .resolvePosition (c .topLeft , col , y );
80+
81+ Entity frame = new Entity (EntityType .ITEM_FRAME );
82+ int index = row * c .columns + col ;
83+ int id = 1 + index ;
84+
85+ frame .editEntityMeta (ItemFrameMeta .class , meta -> {
86+ meta .setItem (
87+ ItemStack .builder (Material .FILLED_MAP )
88+ .set (DataComponents .MAP_ID , id )
89+ .build ()
90+ );
91+ meta .setDirection (c .facing .getAttachmentFace ());
92+ });
93+
94+ frame .setInstance (instance , pos );
95+ }
96+ }
97+ }
98+
99+ public void sendMapData (HypixelPlayer player ) {
100+ if (!initialized ) {
101+ initializeMapPackets ();
102+ }
103+ player .sendPackets (mapPacketCache );
104+ }
105+
106+ protected byte [] decodeMapColors (String compressedData ) {
107+ int cacheKey = compressedData .hashCode ();
108+ byte [] cached = MAP_COLOR_CACHE .get (cacheKey );
109+ if (cached != null ) {
110+ return cached ;
111+ }
112+
113+ try {
114+ byte [] decompressed = Base64 .getDecoder ().decode (compressedData );
115+ try (ByteArrayInputStream bais = new ByteArrayInputStream (decompressed );
116+ GZIPInputStream gis = new GZIPInputStream (bais )) {
117+ byte [] result = gis .readAllBytes ();
118+ MAP_COLOR_CACHE .put (cacheKey , result );
119+ return result ;
120+ }
121+ } catch (IOException e ) {
122+ throw new RuntimeException ("Failed to decode map colors for " + getClass ().getSimpleName (), e );
123+ }
124+ }
125+
126+ public static class MapConfiguration {
127+ @ Getter private final Pos topLeft ;
128+ @ Getter private final Pos bottomRight ;
129+ @ Getter private final MapFacing facing ;
130+
131+ @ Getter private final int columns ;
132+ @ Getter private final int rows ;
133+
134+ public MapConfiguration (Pos topLeft , Pos bottomRight , MapFacing facing ) {
135+ this .topLeft = topLeft ;
136+ this .bottomRight = bottomRight ;
137+ this .facing = facing ;
138+
139+ this .rows = topLeft .blockY () - bottomRight .blockY () + 1 ;
140+
141+ if (facing == MapFacing .WEST || facing == MapFacing .EAST ) {
142+ this .columns = Math .abs (topLeft .blockZ () - bottomRight .blockZ ()) + 1 ;
143+ } else {
144+ this .columns = Math .abs (topLeft .blockX () - bottomRight .blockX ()) + 1 ;
145+ }
146+ }
147+ }
148+
149+ public enum MapFacing {
150+ WEST {
151+ public Pos resolvePosition (Pos tl , int col , int y ) {
152+ return new Pos (tl .blockX (), y , tl .blockZ () - col + 0.5 );
153+ }
154+ public Direction getAttachmentFace () { return Direction .EAST ; }
155+ },
156+ EAST {
157+ public Pos resolvePosition (Pos tl , int col , int y ) {
158+ return new Pos (tl .blockX (), y , tl .blockZ () + col + 0.5 );
159+ }
160+ public Direction getAttachmentFace () { return Direction .WEST ; }
161+ },
162+ NORTH {
163+ public Pos resolvePosition (Pos tl , int col , int y ) {
164+ return new Pos (tl .blockX () + col + 0.5 , y , tl .blockZ ());
165+ }
166+ public Direction getAttachmentFace () { return Direction .SOUTH ; }
167+ },
168+ SOUTH {
169+ public Pos resolvePosition (Pos tl , int col , int y ) {
170+ return new Pos (tl .blockX () - col + 0.5 , y , tl .blockZ ());
171+ }
172+ public Direction getAttachmentFace () { return Direction .NORTH ; }
173+ };
174+
175+ public abstract Pos resolvePosition (Pos topLeft , int col , int y );
176+ public abstract Direction getAttachmentFace ();
177+ }
178+
179+ }
0 commit comments