2424import dev .xpple .seedmapper .feature .StructureVariantFeedbackHelper ;
2525import dev .xpple .seedmapper .seedmap .SeedMapScreen ;
2626import dev .xpple .seedmapper .util .ComponentUtils ;
27+ import dev .xpple .seedmapper .util .CubiomesCompat ;
2728import dev .xpple .seedmapper .util .SeedIdentifier ;
2829import dev .xpple .seedmapper .util .SpiralLoop ;
2930import dev .xpple .seedmapper .util .SpiralSpliterator ;
@@ -376,7 +377,7 @@ record StructureIterationState(MemorySegment structureConfig, StructureChecks.Ge
376377 */
377378 for (int stateIndex = 0 , radius = 0 ;; stateIndex ++) {
378379 if (structureStates .isEmpty ()) {
379- throw CommandExceptions . LOOT_NOT_AVAILABLE_EXCEPTION . create () ;
380+ break ;
380381 }
381382 if (stateIndex >= structureStates .size ()) {
382383 stateIndex = 0 ;
@@ -424,6 +425,9 @@ record StructureIterationState(MemorySegment structureConfig, StructureChecks.Ge
424425 MemorySegment lootSeeds = Piece .lootSeeds (piece );
425426 for (int j = 0 ; j < chestCount ; j ++) {
426427 MemorySegment lootTable = lootTables .getAtIndex (ValueLayout .ADDRESS , j ).reinterpret (Long .MAX_VALUE );
428+ if (lootTable .equals (MemorySegment .NULL )) {
429+ continue ;
430+ }
427431 MemorySegment lootTableContext ;
428432 if (Cubiomes .init_loot_table_name (ltcPtr , lootTable , version ) == 0 ) {
429433 lootTableContext = null ;
@@ -432,9 +436,11 @@ record StructureIterationState(MemorySegment structureConfig, StructureChecks.Ge
432436 }
433437 if (lootTableContext == null || Cubiomes .has_item (lootTableContext , itemPredicate .item ()) == 0 ) {
434438 Set <String > structureIgnoredLootTables = ignoredLootTables .computeIfAbsent (structure , _ -> new HashSet <>());
435- structureIgnoredLootTables .add (lootTable .getString (0 ));
436- // if structure has no loot tables with the desired item, remove structure from state loop
437- if (structureIgnoredLootTables .size () == lootTableCount .get (structure )) {
439+ structureIgnoredLootTables .add (CubiomesCompat .safeCString (lootTable , "unknown_loot_table" ));
440+ // If cubiomes reports a known positive loot-table count for this structure,
441+ // we can prune it once all loot tables are known misses for the requested item.
442+ int knownLootTableCount = lootTableCount .getOrDefault (structure , 0 );
443+ if (knownLootTableCount > 0 && structureIgnoredLootTables .size () == knownLootTableCount ) {
438444 return ;
439445 }
440446 continue ;
@@ -462,7 +468,9 @@ record StructureIterationState(MemorySegment structureConfig, StructureChecks.Ge
462468 structureStates .remove (stateIndex );
463469 break ;
464470 }
465- if (ignoredLootTables .getOrDefault (structure , Collections .emptySet ()).size () == lootTableCount .get (structure )) {
471+ int knownLootTableCount = lootTableCount .getOrDefault (structure , 0 );
472+ if (knownLootTableCount > 0
473+ && ignoredLootTables .getOrDefault (structure , Collections .emptySet ()).size () == knownLootTableCount ) {
466474 structureStates .remove (stateIndex );
467475 break ;
468476 }
@@ -472,18 +480,149 @@ record StructureIterationState(MemorySegment structureConfig, StructureChecks.Ge
472480 }
473481 int newlyFound = found [0 ] - previouslyFound ;
474482 if (newlyFound > 0 ) {
475- String structureName = Cubiomes . struct2str (StructureConfig .structType (structureConfig )). getString ( 0 );
483+ String structureName = CubiomesCompat . structureName (StructureConfig .structType (structureConfig ));
476484 structureResults .add (new LootStructureResult (structureName , newlyFound , List .copyOf (aggregatedLootPositions )));
477485 }
478486 if (found [0 ] >= amount ) {
479487 break ;
480488 }
481489 }
482- String itemName = Cubiomes .global_id2item_name (itemPredicate .item (), version ).getString (0 );
490+
491+ if (found [0 ] < amount && dimension == Cubiomes .DIM_OVERWORLD ()) {
492+ int newlyFound = scanStrongholdLoot (center , seed , version , generator , amount - found [0 ], itemPredicate , found , primaryPos , structureResults , arena );
493+ if (newlyFound > 0 && primaryPos [0 ] == null && !structureResults .isEmpty ()) {
494+ LootStructureResult last = structureResults .getLast ();
495+ if (!last .positions ().isEmpty ()) {
496+ primaryPos [0 ] = last .positions ().getFirst ();
497+ }
498+ }
499+ }
500+
501+ if (found [0 ] <= 0 ) {
502+ throw CommandExceptions .LOOT_NOT_AVAILABLE_EXCEPTION .create ();
503+ }
504+
505+ String itemName = CubiomesCompat .itemName (itemPredicate .item (), version );
483506 return new LootLocateResult (found [0 ], itemName , primaryPos [0 ] == null ? center : primaryPos [0 ], center , List .copyOf (structureResults ));
484507 }
485508 }
486509
510+ private static int scanStrongholdLoot (
511+ BlockPos center ,
512+ long seed ,
513+ int version ,
514+ MemorySegment generator ,
515+ int amountNeeded ,
516+ EnchantedItem itemPredicate ,
517+ int [] foundTotal ,
518+ BlockPos [] primaryPos ,
519+ List <LootStructureResult > structureResults ,
520+ Arena arena
521+ ) {
522+ if (amountNeeded <= 0 ) {
523+ return 0 ;
524+ }
525+
526+ List <BlockPos > strongholds = new ArrayList <>();
527+ TwoDTree strongholdTree = calculateStrongholds (seed , Cubiomes .DIM_OVERWORLD (), version , Generator .flags (generator ));
528+ for (BlockPos pos : strongholdTree ) {
529+ strongholds .add (pos );
530+ }
531+ strongholds .sort (Comparator .comparingDouble (pos -> pos .distSqr (center .atY (0 ))));
532+
533+ int structure = Cubiomes .Stronghold ();
534+ List <BlockPos > aggregatedLootPositions = new ArrayList <>();
535+ int foundInStrongholds = 0 ;
536+
537+ MemorySegment structureVariant = StructureVariant .allocate (arena );
538+ MemorySegment structureSaltConfig = StructureSaltConfig .allocate (arena );
539+ MemorySegment pieces = Piece .allocateArray (StructureChecks .MAX_END_CITY_AND_FORTRESS_PIECES , arena );
540+ MemorySegment ltcPtr = arena .allocate (Cubiomes .C_POINTER );
541+
542+ for (BlockPos strongholdPos : strongholds ) {
543+ int posX = strongholdPos .getX ();
544+ int posZ = strongholdPos .getZ ();
545+
546+ int biome = Cubiomes .getBiomeAt (generator , 4 , posX >> 2 , 320 >> 2 , posZ >> 2 );
547+ Cubiomes .getVariant (structureVariant , structure , version , seed , posX , posZ , biome );
548+ biome = StructureVariant .biome (structureVariant ) != -1 ? StructureVariant .biome (structureVariant ) : biome ;
549+ if (Cubiomes .getStructureSaltConfig (structure , version , biome , structureSaltConfig ) == 0 ) {
550+ continue ;
551+ }
552+ int numPieces = Cubiomes .getStructurePieces (
553+ pieces ,
554+ StructureChecks .MAX_END_CITY_AND_FORTRESS_PIECES ,
555+ structure ,
556+ structureSaltConfig ,
557+ structureVariant ,
558+ version ,
559+ seed ,
560+ posX ,
561+ posZ
562+ );
563+ if (numPieces <= 0 ) {
564+ continue ;
565+ }
566+
567+ int foundAtThisStronghold = 0 ;
568+ for (int i = 0 ; i < numPieces ; i ++) {
569+ MemorySegment piece = Piece .asSlice (pieces , i );
570+ int chestCount = Piece .chestCount (piece );
571+ if (chestCount == 0 ) {
572+ continue ;
573+ }
574+ MemorySegment lootTables = Piece .lootTables (piece );
575+ MemorySegment lootSeeds = Piece .lootSeeds (piece );
576+ for (int j = 0 ; j < chestCount ; j ++) {
577+ MemorySegment lootTable = lootTables .getAtIndex (ValueLayout .ADDRESS , j ).reinterpret (Long .MAX_VALUE );
578+ if (lootTable .equals (MemorySegment .NULL )) {
579+ continue ;
580+ }
581+ if (Cubiomes .init_loot_table_name (ltcPtr , lootTable , version ) == 0 ) {
582+ continue ;
583+ }
584+ MemorySegment lootTableContext = ltcPtr .get (ValueLayout .ADDRESS , 0 ).reinterpret (LootTableContext .sizeof ());
585+ if (Cubiomes .has_item (lootTableContext , itemPredicate .item ()) == 0 ) {
586+ continue ;
587+ }
588+ Cubiomes .set_loot_seed (lootTableContext , lootSeeds .getAtIndex (Cubiomes .C_LONG_LONG , j ));
589+ Cubiomes .generate_loot (lootTableContext );
590+ int lootCount = LootTableContext .generated_item_count (lootTableContext );
591+ for (int k = 0 ; k < lootCount ; k ++) {
592+ MemorySegment itemStack = ItemStack .asSlice (LootTableContext .generated_items (lootTableContext ), k );
593+ if (Cubiomes .get_global_item_id (lootTableContext , ItemStack .item (itemStack )) == itemPredicate .item ()
594+ && itemPredicate .enchantmensPredicate ().test (itemStack )) {
595+ int stackCount = ItemStack .count (itemStack );
596+ foundAtThisStronghold += stackCount ;
597+ foundTotal [0 ] += stackCount ;
598+ }
599+ }
600+ }
601+ }
602+
603+ if (foundAtThisStronghold > 0 ) {
604+ foundInStrongholds += foundAtThisStronghold ;
605+ aggregatedLootPositions .add (new BlockPos (posX , 0 , posZ ));
606+ if (primaryPos [0 ] == null ) {
607+ primaryPos [0 ] = new BlockPos (posX , 0 , posZ );
608+ }
609+ }
610+
611+ if (foundInStrongholds >= amountNeeded ) {
612+ break ;
613+ }
614+ }
615+
616+ if (foundInStrongholds > 0 ) {
617+ structureResults .add (new LootStructureResult (
618+ CubiomesCompat .structureName (structure ),
619+ foundInStrongholds ,
620+ List .copyOf (aggregatedLootPositions )
621+ ));
622+ }
623+ return foundInStrongholds ;
624+ }
625+
487626 private static int locateSpawn (CustomClientCommandSource source ) throws CommandSyntaxException {
488627 SeedIdentifier seed = source .getSeed ().getSecond ();
489628 try (Arena arena = Arena .ofConfined ()) {
0 commit comments