1818import java .util .PrimitiveIterator ;
1919import java .util .Random ;
2020
21+ import net .minecraft .core .component .DataComponents ;
2122import net .minecraft .network .chat .Component ;
23+ import net .minecraft .server .network .Filterable ;
24+ import net .minecraft .world .item .component .WrittenBookContent ;
2225import net .minecraft .network .protocol .game .ServerboundEditBookPacket ;
2326import net .minecraft .world .item .ItemStack ;
2427import net .minecraft .world .item .Items ;
@@ -58,6 +61,10 @@ private enum RandomType
5861 "Number of pages to write per book (Random mode)." , 50 , 1 , 100 , 1 ,
5962 ValueDisplay .INTEGER );
6063
64+ private final SliderSetting characters = new SliderSetting ("Characters" ,
65+ "Number of characters to write per page (Random mode)." , 128 , 1 , 1024 ,
66+ 1 , ValueDisplay .INTEGER );
67+
6168 private final EnumSetting <RandomType > randomType = new EnumSetting <>(
6269 "Random type" , "Character profile used in Random mode." ,
6370 RandomType .values (), RandomType .UTF8 );
@@ -104,6 +111,7 @@ public BookBotHack()
104111
105112 addSetting (mode );
106113 addSetting (pages );
114+ addSetting (characters );
107115 addSetting (randomType );
108116 addSetting (delay );
109117 addSetting (sign );
@@ -177,17 +185,20 @@ public void onUpdate()
177185 break ;
178186 case ASCII :
179187 {
180- PrimitiveIterator .OfInt chars = random .ints (0x21 , 0x7F )
181- .filter (this ::isValidRandomCodePoint ).iterator ();
188+ PrimitiveIterator .OfInt chars = random .ints (0x21 , 0x80 )
189+ .filter (i -> !Character .isWhitespace (i ) && i != '\r'
190+ && i != '\n' )
191+ .iterator ();
182192 generatedPages = randomPages (chars );
183193 break ;
184194 }
185195 case UTF8 :
186196 default :
187197 {
188- PrimitiveIterator .OfInt chars =
189- random .ints (0x0800 , 0x110000 )
190- .filter (this ::isValidRandomCodePoint ).iterator ();
198+ PrimitiveIterator .OfInt chars = random .ints (0x21 , 0xD800 )
199+ .filter (i -> !Character .isWhitespace (i ) && i != '\r'
200+ && i != '\n' )
201+ .iterator ();
191202 generatedPages = randomPages (chars );
192203 break ;
193204 }
@@ -256,71 +267,21 @@ private List<String> randomPages(PrimitiveIterator.OfInt chars)
256267 {
257268 ArrayList <String > outPages = new ArrayList <>();
258269 StringBuilder page = new StringBuilder ();
259- StringBuilder line = new StringBuilder ();
260270 int maxPages = pages .getValueI ();
261- int lineWidth = 0 ;
262- int linesOnPage = 0 ;
271+ int pageIndex = 0 ;
263272
264- while (chars . hasNext () && outPages . size () < maxPages )
273+ while (pageIndex != maxPages )
265274 {
266- int cp = chars .nextInt ();
267- if (cp == '\r' || cp == '\n' )
268- continue ; // skip explicit newlines in random mode
269-
270- // width of this code point in pixels
271- String s = new String (Character .toChars (cp ));
272- int w = MC .font .width (s );
275+ for (int i = 0 ; i < characters .getValueI () && chars .hasNext (); i ++)
276+ page .appendCodePoint (chars .nextInt ());
273277
274- if (lineWidth + w <= 114 )
275- {
276- line .append (s );
277- lineWidth += w ;
278- }else
279- {
280- // finish current line
281- page .append (line ).append ('\n' );
282- line .setLength (0 );
283- lineWidth = 0 ;
284- linesOnPage ++;
285-
286- if (linesOnPage >= 14 )
287- {
288- // finish page
289- outPages .add (page .toString ());
290- page .setLength (0 );
291- linesOnPage = 0 ;
292-
293- if (outPages .size () >= maxPages )
294- break ;
295- }
296-
297- // start next line with current char if it fits (it should,
298- // unless glyph is wider than 114px)
299- if (w <= 114 )
300- {
301- line .append (s );
302- lineWidth = w ;
303- }
304- }
305- }
306-
307- // flush last line
308- if (line .length () > 0 )
309- {
310- page .append (line );
311- linesOnPage ++;
312- }
313-
314- // pad remaining lines with explicit newlines to close the page properly
315- if (page .length () > 0 && outPages .size () < maxPages )
316- {
317- // ensure max 14 lines per page
318- while (linesOnPage > 0 && linesOnPage < 14 )
278+ if (!page .isEmpty ())
319279 {
320- page .append ( '\n' );
321- linesOnPage ++ ;
280+ outPages . add ( page .toString () );
281+ page . setLength ( 0 ) ;
322282 }
323- outPages .add (page .toString ());
283+
284+ pageIndex ++;
324285 }
325286
326287 return outPages ;
@@ -329,12 +290,11 @@ private List<String> randomPages(PrimitiveIterator.OfInt chars)
329290 private List <String > paperMcPages ()
330291 {
331292 ArrayList <String > outPages = new ArrayList <>(100 );
332- PrimitiveIterator .OfInt oneByte = random .ints (0x21 , 0x80 )
333- .filter (this ::isValidRandomCodePoint ).iterator ();
334- PrimitiveIterator .OfInt twoBytes = random .ints (0x0080 , 0x0800 )
335- .filter (this ::isValidRandomCodePoint ).iterator ();
336- PrimitiveIterator .OfInt threeBytes = random .ints (0x0800 , 0xD800 )
337- .filter (this ::isValidRandomCodePoint ).iterator ();
293+ PrimitiveIterator .OfInt oneByte = random .ints (0x21 , 0x80 ).iterator ();
294+ PrimitiveIterator .OfInt twoBytes =
295+ random .ints (0x0080 , 0x0800 ).iterator ();
296+ PrimitiveIterator .OfInt threeBytes =
297+ random .ints (0x0800 , 0xD800 ).iterator ();
338298
339299 for (int page = 0 ; page < 50 ; page ++)
340300 {
@@ -369,21 +329,6 @@ private void appendCodePoints(StringBuilder sb,
369329 sb .appendCodePoint (chars .nextInt ());
370330 }
371331
372- private boolean isValidRandomCodePoint (int cp )
373- {
374- if (cp == '\r' || cp == '\n' )
375- return false ;
376- if (Character .isWhitespace (cp ))
377- return false ;
378- if (!Character .isValidCodePoint (cp ))
379- return false ;
380- if (cp >= Character .MIN_SURROGATE && cp <= Character .MAX_SURROGATE )
381- return false ;
382- if (Character .isISOControl (cp ))
383- return false ;
384- return Character .isDefined (cp );
385- }
386-
387332 private List <String > filePages (String text )
388333 {
389334 ArrayList <String > pages = new ArrayList <>();
@@ -434,9 +379,17 @@ private void writeBook(List<String> pages)
434379 String title = name .getValue ();
435380 if (appendCount .isChecked () && bookCount != 0 )
436381 title += " #" + bookCount ;
437-
438- // Keep packet-only behavior to avoid writing brittle local
439- // WRITABLE_BOOK content components across mappings.
382+
383+ // Build filtered pages for the component
384+ ArrayList <Filterable <Component >> filteredPages = new ArrayList <>();
385+ for (String page : pages )
386+ filteredPages .add (Filterable .passThrough (Component .literal (page )));
387+
388+ // Write data to book (client-side component)
389+ MC .player .getMainHandItem ().set (DataComponents .WRITTEN_BOOK_CONTENT ,
390+ new WrittenBookContent (Filterable .passThrough (title ),
391+ MC .player .getGameProfile ().name (), 0 , filteredPages , true ));
392+
440393 logBookPayloadEstimate (pages , title );
441394
442395 // Send packet
0 commit comments