com.yungnickyoung.minecraft.yungsapi.world.structure.jigsaw.JigsawManager Maven / Gradle / Ivy
Show all versions of YungsApi-1.21-Fabric Show documentation
package com.yungnickyoung.minecraft.yungsapi.world.structure.jigsaw;
import com.mojang.datafixers.util.Pair;
import com.yungnickyoung.minecraft.yungsapi.YungsApiCommon;
import com.yungnickyoung.minecraft.yungsapi.mixin.accessor.StructureTemplatePoolAccessor;
import com.yungnickyoung.minecraft.yungsapi.util.BoxOctree;
import com.yungnickyoung.minecraft.yungsapi.world.structure.context.StructureContext;
import com.yungnickyoung.minecraft.yungsapi.world.structure.jigsaw.assembler.JigsawStructureAssembler;
import com.yungnickyoung.minecraft.yungsapi.world.structure.jigsaw.element.YungJigsawPoolElement;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.util.ConcurrentModificationException;
import java.util.List;
import java.util.Optional;
import net.minecraft.class_156;
import net.minecraft.class_2338;
import net.minecraft.class_2378;
import net.minecraft.class_238;
import net.minecraft.class_2382;
import net.minecraft.class_2470;
import net.minecraft.class_2794;
import net.minecraft.class_2902;
import net.minecraft.class_2919;
import net.minecraft.class_2960;
import net.minecraft.class_3195;
import net.minecraft.class_3341;
import net.minecraft.class_3485;
import net.minecraft.class_3499;
import net.minecraft.class_3777;
import net.minecraft.class_3784;
import net.minecraft.class_3785;
import net.minecraft.class_3790;
import net.minecraft.class_5455;
import net.minecraft.class_5539;
import net.minecraft.class_5819;
import net.minecraft.class_6880;
import net.minecraft.class_7924;
import net.minecraft.class_9778;
import net.minecraft.class_9822;
public class JigsawManager {
public static Optional assembleJigsawStructure(
class_3195.class_7149 generationContext,
class_6880 startPool,
Optional startJigsawNameOptional,
int maxDepth,
class_2338 locatePos, // The original starting position of the structure, also where /locate points to
boolean useExpansionHack, // Used to be doBoundaryAdjustments
Optional projectStartToHeightmap,
int maxDistanceFromCenter, // Used to be structureBoundingBoxRadius
Optional maxY,
Optional minY,
class_9778 dimensionPadding,
class_9822 liquidSettings
) {
// Extract data from context
class_5455 registryAccess = generationContext.comp_561();
class_2794 chunkGenerator = generationContext.comp_562();
class_3485 structureManager = generationContext.comp_565();
class_5539 levelHeightAccessor = generationContext.comp_569();
class_2919 worldgenRandom = generationContext.comp_566();
class_2378 registry = registryAccess.method_30530(class_7924.field_41249);
// Grab a random starting piece from the start pool
Optional startPieceOptional = getStartPiece(startPool, startJigsawNameOptional, locatePos, structureManager, worldgenRandom, liquidSettings);
if (startPieceOptional.isEmpty()) {
return Optional.empty();
}
class_3790 startPiece = startPieceOptional.get();
// Offset vector from the /locate position to the piece's starting position.
// This will be a zero vector if no start jigsaw name was specified.
class_2382 startingPosOffset = locatePos.method_10059(startPiece.method_16648());
// Grab some data regarding starting piece's bounding box & position
class_3341 pieceBoundingBox = startPiece.method_14935();
int bbCenterX = (pieceBoundingBox.method_35418() + pieceBoundingBox.method_35415()) / 2;
int bbCenterZ = (pieceBoundingBox.method_35420() + pieceBoundingBox.method_35417()) / 2;
// Note that the bbCenterY does not actually refer to the center of the piece, unlike the bbCenterX/Z variables.
// If a heightmap is used, the bbCenterY will be the y-value of the /locate position (anchor pos) after adjusting for the heightmap.
// Otherwise, the bbCenterY is simply the starting position's y-value. I'm not sure why it uses that position and not the /locate position,
// but that's vanilla behavior. It almost certainly won't make a difference anyway, as structures are basically never that tall.
int bbCenterY = projectStartToHeightmap
.map(types -> locatePos.method_10264() + chunkGenerator.method_20402(bbCenterX, bbCenterZ, types, levelHeightAccessor, generationContext.comp_564()))
.orElseGet(() -> startPiece.method_16648().method_10264());
int adjustedPieceCenterY = bbCenterY + startingPosOffset.method_10264();
// Move the starting piece to account for any y-level change due to heightmap and/or groundLevelDelta
int yAdjustment = pieceBoundingBox.method_35416() + startPiece.method_16646();
startPiece.method_14922(0, bbCenterY - yAdjustment, 0);
// Establish max bounds of entire structure.
// Make sure the supplied distance is large enough to cover the size of your entire structure.
class_238 aABB = new class_238(
bbCenterX - maxDistanceFromCenter, adjustedPieceCenterY - maxDistanceFromCenter, bbCenterZ - maxDistanceFromCenter,
bbCenterX + maxDistanceFromCenter + 1, adjustedPieceCenterY + maxDistanceFromCenter + 1, bbCenterZ + maxDistanceFromCenter + 1);
BoxOctree maxStructureBounds = new BoxOctree(aABB); // The maximum boundary of the entire structure
maxStructureBounds.addBox(class_238.method_19316(pieceBoundingBox)); // Add start piece to our structure's bounds
return Optional.of(new class_3195.class_7150(
new class_2338(bbCenterX, adjustedPieceCenterY, bbCenterZ),
(structurePiecesBuilder) -> {
if (maxDepth <= 0) { // Realistically this should never be true. Why make a jigsaw config with a non-positive size?
return;
}
// Create assembler + initial entry
JigsawStructureAssembler assembler = new JigsawStructureAssembler(new JigsawStructureAssembler.Settings()
.poolRegistry(registry)
.maxDepth(maxDepth)
.chunkGenerator(chunkGenerator)
.structureTemplateManager(structureManager)
.randomState(generationContext.comp_564())
.rand(worldgenRandom)
.maxY(maxY)
.minY(minY)
.useExpansionHack(useExpansionHack)
.levelHeightAccessor(levelHeightAccessor)
.dimensionPadding(dimensionPadding)
.liquidSettings(liquidSettings));
// Add the start piece to the assembler & assemble the structure
assembler.assembleStructure(startPiece, maxStructureBounds);
assembler.addAllPiecesToStructureBuilder(structurePiecesBuilder);
}));
}
/**
* Returns a piece from the provided pool to be used as the starting piece for a structure.
* Pieces are chosen randomly, but some conditions as well as the isPriority flag are respected.
*
* Note that only some conditions are supported. Conditions checking for things like piece position or orientation
* should not be used, as instead those checks can be performed on the structure's placement itself.
*/
private static Optional getStartPiece(
class_6880 startPoolHolder,
Optional startJigsawNameOptional,
class_2338 locatePos,
class_3485 structureTemplateManager,
class_5819 rand,
class_9822 liquidSettings
) {
class_3785 startPool = startPoolHolder.comp_349();
ObjectArrayList> candidatePoolElements = new ObjectArrayList<>(((StructureTemplatePoolAccessor) startPool).getRawTemplates());
// Shuffle our candidate pool elements
class_156.method_43028(candidatePoolElements, rand);
// Get a random orientation for starting piece
class_2470 rotation = class_2470.method_16548(rand);
// Sum of weights in all pieces in the pool.
// When choosing a piece, we will remove its weight from this sum.
int totalWeightSum = candidatePoolElements.stream().mapToInt(Pair::getSecond).reduce(0, Integer::sum);
while (candidatePoolElements.size() > 0 && totalWeightSum > 0) {
Pair chosenPoolElementPair = null;
// First, check for any priority pieces
for (Pair candidatePiecePair : candidatePoolElements) {
class_3784 candidatePiece = candidatePiecePair.getFirst();
if (candidatePiece instanceof YungJigsawPoolElement yungElement && yungElement.isPriorityPiece()) {
chosenPoolElementPair = candidatePiecePair;
break;
}
}
// Randomly choose piece if priority piece wasn't selected
if (chosenPoolElementPair == null) {
// Random weight used to choose random piece from the pool of candidates
int chosenWeight = rand.method_43048(totalWeightSum) + 1;
// Randomly choose a candidate piece
for (Pair candidate : candidatePoolElements) {
chosenWeight -= candidate.getSecond();
if (chosenWeight <= 0) {
chosenPoolElementPair = candidate;
break;
}
}
}
// Extract data from the chosen piece pair.
class_3784 chosenPoolElement = chosenPoolElementPair.getFirst();
int chosenPieceWeight = chosenPoolElementPair.getSecond();
if (chosenPoolElement == class_3777.field_16663) {
return Optional.empty();
}
class_2338 anchorPos;
if (startJigsawNameOptional.isPresent()) {
class_2960 name = startJigsawNameOptional.get();
Optional optional = getPosOfJigsawBlockWithName(chosenPoolElement, name, locatePos, rotation, structureTemplateManager, rand);
if (optional.isEmpty()) {
YungsApiCommon.LOGGER.error("No starting jigsaw with Name {} found in start pool {}", name, startPoolHolder.method_40230()
.map(pool -> pool.method_29177().toString())
.orElse(""));
return Optional.empty();
}
anchorPos = optional.get();
} else {
anchorPos = locatePos;
}
// We adjust the starting position such that, if a named start jigsaw is being used (i.e. an anchor),
// then the anchor's position will be located at the original starting position.
class_2382 startingPosOffset = anchorPos.method_10059(locatePos);
class_2338 adjustedStartPos = locatePos.method_10059(startingPosOffset);
// Validate conditions for this piece, if applicable
if (chosenPoolElement instanceof YungJigsawPoolElement yungElement) {
StructureContext ctx = new StructureContext.Builder()
.structureTemplateManager(structureTemplateManager)
.pos(adjustedStartPos)
.rotation(rotation)
.depth(0)
.random(rand)
.build();
if (!yungElement.passesConditions(ctx)) {
totalWeightSum -= chosenPieceWeight;
candidatePoolElements.remove(chosenPoolElementPair);
continue; // Abort this piece if it doesn't pass conditions check
}
}
// Instantiate piece
return Optional.of(new class_3790(
structureTemplateManager,
chosenPoolElement,
adjustedStartPos,
chosenPoolElement.method_19308(),
rotation,
chosenPoolElement.method_16628(structureTemplateManager, adjustedStartPos, rotation),
liquidSettings
));
}
return Optional.empty();
}
/**
* Returns a jigsaw block with the specified name in the StructurePoolElement.
* If no such jigsaw block is found, returns an empty Optional.
*
* This is used for starting pieces, when you want /locate to point to a position other than the
* corner of the start piece, such as the center of ancient cities.
*/
private static Optional getPosOfJigsawBlockWithName(
class_3784 structurePoolElement,
class_2960 name,
class_2338 startPos,
class_2470 rotation,
class_3485 structureTemplateManager,
class_5819 rand
) {
// Wrap in try-catch because for some reason, getShuffledJigsawBlocks rarely throws a ConcurrentModificationException.
// We'd rather just ignore the anchor jigsaw block than crash the game.
try {
List shuffledJigsawBlocks = structurePoolElement.method_16627(structureTemplateManager, startPos, rotation, rand);
for (class_3499.class_3501 jigsawBlockInfo : shuffledJigsawBlocks) {
class_2960 jigsawBlockName = class_2960.method_12829(jigsawBlockInfo.comp_1343().method_10558("name"));
if (name.equals(jigsawBlockName)) {
return Optional.of(jigsawBlockInfo.comp_1341());
}
}
} catch (ConcurrentModificationException e) {
YungsApiCommon.LOGGER.error("Encountered unexpected ConcurrentModException while trying to get jigsaw block with name {} from structure pool element {}", name, structurePoolElement);
YungsApiCommon.LOGGER.error("Ignoring - the structure will still generate, but /locate will not point to the structure's anchor block.");
return Optional.empty();
}
return Optional.empty();
}
}