Clean repo for open source release
Remove build artifacts, dev tool configs, unused dependencies, and third-party source dumps. Add proper README, update .gitignore, clean up Makefile.
This commit is contained in:
656
src/main/java/com/tiedup/remake/worldgen/HangingCagePiece.java
Normal file
656
src/main/java/com/tiedup/remake/worldgen/HangingCagePiece.java
Normal file
@@ -0,0 +1,656 @@
|
||||
package com.tiedup.remake.worldgen;
|
||||
|
||||
import com.tiedup.remake.blocks.ModBlocks;
|
||||
import com.tiedup.remake.blocks.entity.TrappedChestBlockEntity;
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
import com.tiedup.remake.items.ModItems;
|
||||
import com.tiedup.remake.items.base.BindVariant;
|
||||
import com.tiedup.remake.items.base.BlindfoldVariant;
|
||||
import com.tiedup.remake.items.base.GagVariant;
|
||||
import com.tiedup.remake.v2.V2Blocks;
|
||||
import com.tiedup.remake.v2.blocks.PetCageBlock;
|
||||
import com.tiedup.remake.v2.blocks.PetCagePartBlock;
|
||||
import javax.annotation.Nullable;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.DoubleTag;
|
||||
import net.minecraft.nbt.FloatTag;
|
||||
import net.minecraft.nbt.ListTag;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.WorldGenLevel;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.ChestBlock;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.ProtoChunk;
|
||||
import net.minecraft.world.level.levelgen.structure.BoundingBox;
|
||||
import net.minecraft.world.level.levelgen.structure.StructurePiece;
|
||||
import net.minecraft.world.level.levelgen.structure.pieces.StructurePieceSerializationContext;
|
||||
import net.minecraft.world.level.storage.loot.BuiltInLootTables;
|
||||
|
||||
/**
|
||||
* Structure piece for the hanging cage.
|
||||
*
|
||||
* Scans the cave geometry in postProcess() and places:
|
||||
* - Iron bars column from ceiling down to the top of the cage
|
||||
* - A Pet Cage (3x3x2 multi-block) suspended in the cave
|
||||
* - A Damsel entity inside the cage (25% shiny)
|
||||
*/
|
||||
public class HangingCagePiece extends StructurePiece {
|
||||
|
||||
private final BlockPos candidatePos;
|
||||
private final Direction facing;
|
||||
private boolean placed = false;
|
||||
|
||||
/** Minimum air gap between cave floor and bottom of cage. */
|
||||
private static final int MIN_GAP = 4;
|
||||
/** Height of the cage in blocks. */
|
||||
private static final int CAGE_HEIGHT = 2;
|
||||
/** Minimum cave height: gap + cage + at least 1 iron bar. */
|
||||
private static final int MIN_CAVE_HEIGHT = MIN_GAP + CAGE_HEIGHT + 1;
|
||||
/** Max scan distance up/down from candidate pos. */
|
||||
private static final int SCAN_RANGE = 64;
|
||||
|
||||
/** X/Z offsets to try around chunk center. Cage is 3x3 so we space by 4 to avoid overlap. */
|
||||
private static final int[][] COLUMN_OFFSETS = {
|
||||
{ 0, 0 },
|
||||
{ 4, 0 },
|
||||
{ -4, 0 },
|
||||
{ 0, 4 },
|
||||
{ 0, -4 },
|
||||
{ 4, 4 },
|
||||
{ -4, 4 },
|
||||
{ 4, -4 },
|
||||
{ -4, -4 },
|
||||
{ 2, 2 },
|
||||
{ -2, 2 },
|
||||
{ 2, -2 },
|
||||
{ -2, -2 },
|
||||
};
|
||||
|
||||
public HangingCagePiece(BlockPos candidatePos, RandomSource random) {
|
||||
super(
|
||||
ModStructures.HANGING_CAGE_PIECE.get(),
|
||||
0,
|
||||
makeBoundingBox(candidatePos)
|
||||
);
|
||||
this.candidatePos = candidatePos;
|
||||
this.facing = Direction.Plane.HORIZONTAL.getRandomDirection(random);
|
||||
}
|
||||
|
||||
public HangingCagePiece(CompoundTag tag) {
|
||||
super(ModStructures.HANGING_CAGE_PIECE.get(), tag);
|
||||
this.candidatePos = new BlockPos(
|
||||
tag.getInt("CandX"),
|
||||
tag.getInt("CandY"),
|
||||
tag.getInt("CandZ")
|
||||
);
|
||||
this.facing = Direction.from2DDataValue(tag.getInt("Facing"));
|
||||
}
|
||||
|
||||
private static BoundingBox makeBoundingBox(BlockPos pos) {
|
||||
// Generous bounding box: ±7 covers 13-wide room (±6) + column offsets, vertical covers scan + room
|
||||
return new BoundingBox(
|
||||
pos.getX() - 7,
|
||||
pos.getY() - SCAN_RANGE,
|
||||
pos.getZ() - 7,
|
||||
pos.getX() + 7,
|
||||
pos.getY() + SCAN_RANGE,
|
||||
pos.getZ() + 7
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addAdditionalSaveData(
|
||||
StructurePieceSerializationContext context,
|
||||
CompoundTag tag
|
||||
) {
|
||||
tag.putInt("CandX", candidatePos.getX());
|
||||
tag.putInt("CandY", candidatePos.getY());
|
||||
tag.putInt("CandZ", candidatePos.getZ());
|
||||
tag.putInt("Facing", facing.get2DDataValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postProcess(
|
||||
WorldGenLevel level,
|
||||
net.minecraft.world.level.StructureManager structureManager,
|
||||
net.minecraft.world.level.chunk.ChunkGenerator chunkGenerator,
|
||||
RandomSource random,
|
||||
BoundingBox chunkBB,
|
||||
ChunkPos chunkPos,
|
||||
BlockPos pivot
|
||||
) {
|
||||
if (placed) return;
|
||||
|
||||
// Try multiple columns around chunk center to maximize chance of finding a cave.
|
||||
// A single column has low odds of hitting a cave; 13 columns covers a wide area.
|
||||
for (int[] offset : COLUMN_OFFSETS) {
|
||||
int x = candidatePos.getX() + offset[0];
|
||||
int z = candidatePos.getZ() + offset[1];
|
||||
|
||||
if (scanColumnAndPlace(level, random, x, z, chunkBB)) {
|
||||
placed = true;
|
||||
return; // Success
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: carve a dungeon room and place the cage inside
|
||||
carveAndPlaceRoom(
|
||||
level,
|
||||
random,
|
||||
candidatePos.getX(),
|
||||
candidatePos.getZ(),
|
||||
chunkBB
|
||||
);
|
||||
placed = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan a single column from Y=50 down to Y=-50, looking for caves tall enough.
|
||||
* @return true if a cage was placed
|
||||
*/
|
||||
private boolean scanColumnAndPlace(
|
||||
WorldGenLevel level,
|
||||
RandomSource random,
|
||||
int x,
|
||||
int z,
|
||||
BoundingBox chunkBB
|
||||
) {
|
||||
int scanTop = 50;
|
||||
int scanBottom = -50;
|
||||
|
||||
int ceilingY = -1;
|
||||
boolean inAir = false;
|
||||
|
||||
for (int y = scanTop; y >= scanBottom; y--) {
|
||||
BlockState state = level.getBlockState(new BlockPos(x, y, z));
|
||||
boolean solid = !state.isAir() && !state.liquid();
|
||||
|
||||
if (!inAir && !solid) {
|
||||
// Transition solid -> air: ceiling is at y+1
|
||||
ceilingY = y + 1;
|
||||
inAir = true;
|
||||
} else if (inAir && solid) {
|
||||
// Transition air -> solid: floor is at y
|
||||
int floorY = y;
|
||||
inAir = false;
|
||||
|
||||
int caveHeight = ceilingY - floorY - 1;
|
||||
if (caveHeight >= MIN_CAVE_HEIGHT) {
|
||||
if (
|
||||
tryPlaceCage(
|
||||
level,
|
||||
random,
|
||||
x,
|
||||
z,
|
||||
floorY,
|
||||
ceilingY,
|
||||
chunkBB
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
ceilingY = -1;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to place the cage in a cave with the given floor and ceiling Y.
|
||||
* @return true if placement succeeded
|
||||
*/
|
||||
private boolean tryPlaceCage(
|
||||
WorldGenLevel level,
|
||||
RandomSource random,
|
||||
int x,
|
||||
int z,
|
||||
int floorY,
|
||||
int ceilingY,
|
||||
BoundingBox chunkBB
|
||||
) {
|
||||
int cageMasterY = floorY + 1 + MIN_GAP;
|
||||
int cageTopY = cageMasterY + CAGE_HEIGHT;
|
||||
int numBars = ceilingY - cageTopY;
|
||||
if (numBars < 1) return false;
|
||||
|
||||
BlockPos masterPos = new BlockPos(x, cageMasterY, z);
|
||||
|
||||
// Validate 3x3x2 footprint is all air
|
||||
if (!level.getBlockState(masterPos).isAir()) return false;
|
||||
|
||||
BlockPos[] partPositions = PetCageBlock.getPartPositions(
|
||||
masterPos,
|
||||
facing
|
||||
);
|
||||
for (BlockPos partPos : partPositions) {
|
||||
if (!level.getBlockState(partPos).isAir()) return false;
|
||||
}
|
||||
|
||||
// Place iron bars from ceiling down to top of cage
|
||||
for (int y = cageTopY; y < ceilingY; y++) {
|
||||
safeSetBlock(
|
||||
level,
|
||||
new BlockPos(x, y, z),
|
||||
Blocks.IRON_BARS.defaultBlockState(),
|
||||
chunkBB
|
||||
);
|
||||
}
|
||||
|
||||
// Place Pet Cage master block
|
||||
BlockState masterState = V2Blocks.PET_CAGE.get()
|
||||
.defaultBlockState()
|
||||
.setValue(PetCageBlock.FACING, facing);
|
||||
safeSetBlock(level, masterPos, masterState, chunkBB);
|
||||
|
||||
// Place Pet Cage part blocks
|
||||
BlockState partState = V2Blocks.PET_CAGE_PART.get()
|
||||
.defaultBlockState()
|
||||
.setValue(PetCagePartBlock.FACING, facing);
|
||||
for (BlockPos partPos : partPositions) {
|
||||
safeSetBlock(level, partPos, partState, chunkBB);
|
||||
}
|
||||
|
||||
if (chunkBB.isInside(masterPos)) {
|
||||
scheduleDamselSpawn(level, masterPos, random);
|
||||
}
|
||||
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[HangingCage] Placed cage in cave at {}",
|
||||
masterPos.toShortString()
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
// RoomLayout and RoomTheme extracted to separate files in this package.
|
||||
|
||||
// ── Fallback room generation ─────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Fallback: carve a 13x13x12 dungeon room and place the cage inside.
|
||||
* Randomly selects a theme (Oubliette/Inferno) and layout (Square/Octagonal).
|
||||
*/
|
||||
private void carveAndPlaceRoom(
|
||||
WorldGenLevel level,
|
||||
RandomSource random,
|
||||
int centerX,
|
||||
int centerZ,
|
||||
BoundingBox chunkBB
|
||||
) {
|
||||
RoomTheme theme = RoomTheme.values()[random.nextInt(
|
||||
RoomTheme.values().length
|
||||
)];
|
||||
RoomLayout layout = RoomLayout.values()[random.nextInt(
|
||||
RoomLayout.values().length
|
||||
)];
|
||||
|
||||
int ROOM = 13;
|
||||
int HEIGHT = 12;
|
||||
int baseX = centerX - 6;
|
||||
int baseZ = centerZ - 6;
|
||||
int floorY = candidatePos.getY() - 5;
|
||||
|
||||
// Phase 1: Shell — walls, floor, ceiling, air interior
|
||||
for (int rx = 0; rx < ROOM; rx++) {
|
||||
for (int rz = 0; rz < ROOM; rz++) {
|
||||
if (!layout.isInShape(rx, rz)) continue;
|
||||
|
||||
for (int ry = 0; ry < HEIGHT; ry++) {
|
||||
BlockPos pos = new BlockPos(
|
||||
baseX + rx,
|
||||
floorY + ry,
|
||||
baseZ + rz
|
||||
);
|
||||
boolean isWall = layout.isWall(rx, rz);
|
||||
boolean isFloor = ry == 0;
|
||||
boolean isCeiling = ry == 11;
|
||||
|
||||
if (isFloor) {
|
||||
if (isWall) {
|
||||
safeSetBlock(
|
||||
level,
|
||||
pos,
|
||||
theme.wallShellBlock(),
|
||||
chunkBB
|
||||
);
|
||||
} else {
|
||||
boolean isEdge = layout.isWallAdjacent(rx, rz);
|
||||
safeSetBlock(
|
||||
level,
|
||||
pos,
|
||||
theme.floorBlock(random, rx, rz, isEdge),
|
||||
chunkBB
|
||||
);
|
||||
}
|
||||
} else if (isCeiling) {
|
||||
safeSetBlock(
|
||||
level,
|
||||
pos,
|
||||
theme.ceilingBlock(random),
|
||||
chunkBB
|
||||
);
|
||||
} else if (isWall) {
|
||||
safeSetBlock(
|
||||
level,
|
||||
pos,
|
||||
theme.wallBlock(random, ry),
|
||||
chunkBB
|
||||
);
|
||||
} else {
|
||||
safeSetBlock(
|
||||
level,
|
||||
pos,
|
||||
Blocks.AIR.defaultBlockState(),
|
||||
chunkBB
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 2: Theme-specific decorations
|
||||
theme.placeDecorations(
|
||||
level,
|
||||
random,
|
||||
baseX,
|
||||
baseZ,
|
||||
floorY,
|
||||
layout,
|
||||
chunkBB
|
||||
);
|
||||
|
||||
// Phase 2b: Shared structural features
|
||||
RoomTheme.placeSharedPillars(
|
||||
level,
|
||||
random,
|
||||
baseX,
|
||||
baseZ,
|
||||
floorY,
|
||||
layout,
|
||||
theme,
|
||||
chunkBB
|
||||
);
|
||||
RoomTheme.placeSharedFloorScatter(
|
||||
level,
|
||||
random,
|
||||
baseX,
|
||||
baseZ,
|
||||
floorY,
|
||||
layout,
|
||||
theme,
|
||||
chunkBB
|
||||
);
|
||||
RoomTheme.placeSharedCeilingDecor(
|
||||
level,
|
||||
random,
|
||||
baseX,
|
||||
baseZ,
|
||||
floorY,
|
||||
layout,
|
||||
chunkBB
|
||||
);
|
||||
RoomTheme.placeSharedWallLighting(
|
||||
level,
|
||||
random,
|
||||
baseX,
|
||||
baseZ,
|
||||
floorY,
|
||||
layout,
|
||||
chunkBB
|
||||
);
|
||||
RoomTheme.placeSharedWallBands(
|
||||
level,
|
||||
baseX,
|
||||
baseZ,
|
||||
floorY,
|
||||
layout,
|
||||
theme,
|
||||
chunkBB
|
||||
);
|
||||
|
||||
// Phase 2c: Chests
|
||||
placeVanillaChest(level, random, baseX, baseZ, floorY, layout, chunkBB);
|
||||
placeTrappedChest(level, random, baseX, baseZ, floorY, layout, chunkBB);
|
||||
|
||||
// Phase 3: Iron bars from ry=7 to ry=10 at center [6,6]
|
||||
for (int ry = 7; ry <= 10; ry++) {
|
||||
safeSetBlock(
|
||||
level,
|
||||
new BlockPos(centerX, floorY + ry, centerZ),
|
||||
Blocks.IRON_BARS.defaultBlockState(),
|
||||
chunkBB
|
||||
);
|
||||
}
|
||||
|
||||
// Phase 4: Pet Cage at [6,6] ry=5
|
||||
BlockPos masterPos = new BlockPos(centerX, floorY + 5, centerZ);
|
||||
|
||||
BlockState masterState = V2Blocks.PET_CAGE.get()
|
||||
.defaultBlockState()
|
||||
.setValue(PetCageBlock.FACING, facing);
|
||||
safeSetBlock(level, masterPos, masterState, chunkBB);
|
||||
|
||||
BlockState partState = V2Blocks.PET_CAGE_PART.get()
|
||||
.defaultBlockState()
|
||||
.setValue(PetCagePartBlock.FACING, facing);
|
||||
for (BlockPos partPos : PetCageBlock.getPartPositions(
|
||||
masterPos,
|
||||
facing
|
||||
)) {
|
||||
safeSetBlock(level, partPos, partState, chunkBB);
|
||||
}
|
||||
|
||||
if (chunkBB.isInside(masterPos)) {
|
||||
scheduleDamselSpawn(level, masterPos, random);
|
||||
}
|
||||
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[HangingCage] Placed cage in carved room (theme={}, layout={}) at {}",
|
||||
theme.name(),
|
||||
layout.name(),
|
||||
masterPos.toShortString()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Place a vanilla dungeon chest in the 3rd inner corner.
|
||||
* Uses BuiltInLootTables.SIMPLE_DUNGEON for standard dungeon loot.
|
||||
*/
|
||||
private void placeVanillaChest(
|
||||
WorldGenLevel level,
|
||||
RandomSource random,
|
||||
int baseX,
|
||||
int baseZ,
|
||||
int floorY,
|
||||
RoomLayout layout,
|
||||
BoundingBox chunkBB
|
||||
) {
|
||||
int[][] corners = layout.innerCorners();
|
||||
if (corners.length < 3) return;
|
||||
int[] c = corners[2];
|
||||
if (!layout.isInShape(c[0], c[1]) || layout.isWall(c[0], c[1])) return;
|
||||
|
||||
BlockPos chestPos = new BlockPos(
|
||||
baseX + c[0],
|
||||
floorY + 1,
|
||||
baseZ + c[1]
|
||||
);
|
||||
if (!chunkBB.isInside(chestPos)) return;
|
||||
|
||||
// Face chest toward room center
|
||||
Direction chestFacing;
|
||||
if (c[0] < 6) chestFacing = Direction.EAST;
|
||||
else if (c[0] > 6) chestFacing = Direction.WEST;
|
||||
else if (c[1] < 6) chestFacing = Direction.SOUTH;
|
||||
else chestFacing = Direction.NORTH;
|
||||
|
||||
BlockState chestState = Blocks.CHEST.defaultBlockState().setValue(
|
||||
ChestBlock.FACING,
|
||||
chestFacing
|
||||
);
|
||||
level.setBlock(chestPos, chestState, 2);
|
||||
|
||||
BlockEntity be = level.getBlockEntity(chestPos);
|
||||
if (be instanceof RandomizableContainerBlockEntity container) {
|
||||
container.setLootTable(
|
||||
BuiltInLootTables.SIMPLE_DUNGEON,
|
||||
random.nextLong()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Place a TiedUp trapped chest in the 4th inner corner.
|
||||
* Filled with random bondage items (bind, gag, blindfold).
|
||||
*/
|
||||
private void placeTrappedChest(
|
||||
WorldGenLevel level,
|
||||
RandomSource random,
|
||||
int baseX,
|
||||
int baseZ,
|
||||
int floorY,
|
||||
RoomLayout layout,
|
||||
BoundingBox chunkBB
|
||||
) {
|
||||
int[][] corners = layout.innerCorners();
|
||||
if (corners.length < 4) return;
|
||||
int[] c = corners[3];
|
||||
if (!layout.isInShape(c[0], c[1]) || layout.isWall(c[0], c[1])) return;
|
||||
|
||||
BlockPos chestPos = new BlockPos(
|
||||
baseX + c[0],
|
||||
floorY + 1,
|
||||
baseZ + c[1]
|
||||
);
|
||||
if (!chunkBB.isInside(chestPos)) return;
|
||||
|
||||
// Face chest toward room center
|
||||
Direction chestFacing;
|
||||
if (c[0] < 6) chestFacing = Direction.EAST;
|
||||
else if (c[0] > 6) chestFacing = Direction.WEST;
|
||||
else if (c[1] < 6) chestFacing = Direction.SOUTH;
|
||||
else chestFacing = Direction.NORTH;
|
||||
|
||||
BlockState chestState = ModBlocks.TRAPPED_CHEST.get()
|
||||
.defaultBlockState()
|
||||
.setValue(ChestBlock.FACING, chestFacing);
|
||||
level.setBlock(chestPos, chestState, 2);
|
||||
|
||||
BlockEntity be = level.getBlockEntity(chestPos);
|
||||
if (be instanceof TrappedChestBlockEntity trappedChest) {
|
||||
// Random bind
|
||||
BindVariant[] bindVariants = BindVariant.values();
|
||||
BindVariant chosenBind = bindVariants[random.nextInt(
|
||||
bindVariants.length
|
||||
)];
|
||||
ItemStack bindStack = new ItemStack(ModItems.getBind(chosenBind));
|
||||
trappedChest.setBind(bindStack);
|
||||
|
||||
// Random gag (50% chance)
|
||||
if (random.nextFloat() < 0.50f) {
|
||||
GagVariant[] gagVariants = GagVariant.values();
|
||||
GagVariant chosenGag = gagVariants[random.nextInt(
|
||||
gagVariants.length
|
||||
)];
|
||||
ItemStack gagStack = new ItemStack(ModItems.getGag(chosenGag));
|
||||
trappedChest.setGag(gagStack);
|
||||
}
|
||||
|
||||
// Random blindfold (30% chance)
|
||||
if (random.nextFloat() < 0.30f) {
|
||||
BlindfoldVariant[] bfVariants = BlindfoldVariant.values();
|
||||
BlindfoldVariant chosenBf = bfVariants[random.nextInt(
|
||||
bfVariants.length
|
||||
)];
|
||||
ItemStack bfStack = new ItemStack(
|
||||
ModItems.getBlindfold(chosenBf)
|
||||
);
|
||||
trappedChest.setBlindfold(bfStack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void safeSetBlock(
|
||||
WorldGenLevel level,
|
||||
BlockPos pos,
|
||||
BlockState state,
|
||||
BoundingBox chunkBB
|
||||
) {
|
||||
if (chunkBB.isInside(pos)) {
|
||||
level.setBlock(pos, state, 2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write damsel entity NBT directly into the ProtoChunk, avoiding entity
|
||||
* construction on the worker thread (which deadlocks due to PlayerAnimator).
|
||||
* The entity will be created naturally when the chunk is promoted to a LevelChunk.
|
||||
*/
|
||||
private void scheduleDamselSpawn(
|
||||
WorldGenLevel level,
|
||||
BlockPos masterPos,
|
||||
RandomSource random
|
||||
) {
|
||||
boolean shiny = random.nextFloat() < 0.25f;
|
||||
String entityId = shiny
|
||||
? TiedUpMod.MOD_ID + ":damsel_shiny"
|
||||
: TiedUpMod.MOD_ID + ":damsel";
|
||||
|
||||
CompoundTag entityTag = new CompoundTag();
|
||||
entityTag.putString("id", entityId);
|
||||
|
||||
// Position: +1Y to be inside cage (above the thin 2px cage floor)
|
||||
ListTag posList = new ListTag();
|
||||
posList.add(DoubleTag.valueOf(masterPos.getX() + 0.5));
|
||||
posList.add(DoubleTag.valueOf(masterPos.getY() + 1.0));
|
||||
posList.add(DoubleTag.valueOf(masterPos.getZ() + 0.5));
|
||||
entityTag.put("Pos", posList);
|
||||
|
||||
// Motion (stationary)
|
||||
ListTag motionList = new ListTag();
|
||||
motionList.add(DoubleTag.valueOf(0.0));
|
||||
motionList.add(DoubleTag.valueOf(0.0));
|
||||
motionList.add(DoubleTag.valueOf(0.0));
|
||||
entityTag.put("Motion", motionList);
|
||||
|
||||
// Rotation (random yaw)
|
||||
ListTag rotList = new ListTag();
|
||||
rotList.add(FloatTag.valueOf(random.nextFloat() * 360F));
|
||||
rotList.add(FloatTag.valueOf(0.0F));
|
||||
entityTag.put("Rotation", rotList);
|
||||
|
||||
// Persistence + prevent fall death
|
||||
entityTag.putBoolean("PersistenceRequired", true);
|
||||
entityTag.putBoolean("OnGround", true);
|
||||
entityTag.putFloat("FallDistance", 0.0F);
|
||||
entityTag.putFloat("AbsorptionAmount", 20.0F);
|
||||
entityTag.putUUID("UUID", java.util.UUID.randomUUID());
|
||||
|
||||
// Random bind item — the damsel spawns already restrained
|
||||
BindVariant[] variants = BindVariant.values();
|
||||
BindVariant chosenBind = variants[random.nextInt(variants.length)];
|
||||
ItemStack bindStack = new ItemStack(ModItems.getBind(chosenBind));
|
||||
bindStack.getOrCreateTag().putString("bindMode", "full");
|
||||
entityTag.put("Bind", bindStack.save(new CompoundTag()));
|
||||
|
||||
// Add directly to chunk's pending entity list
|
||||
ChunkAccess chunk = level.getChunk(masterPos);
|
||||
if (chunk instanceof ProtoChunk protoChunk) {
|
||||
protoChunk.addEntity(entityTag);
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[HangingCage] Scheduled {} damsel with {} at {}",
|
||||
shiny ? "shiny" : "regular",
|
||||
chosenBind.getRegistryName(),
|
||||
masterPos.toShortString()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user