Code/net/minecraft/world/level/block/SculkSpreader.java

350 lines
16 KiB
Java

package net.minecraft.world.level.block;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import com.mojang.serialization.codecs.RecordCodecBuilder.Instance;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import it.unimi.dsi.fastutil.objects.Object2IntMap.Entry;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import javax.annotation.Nullable;
import net.minecraft.Util;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Vec3i;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.tags.BlockTags;
import net.minecraft.tags.TagKey;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.state.BlockState;
public class SculkSpreader {
public static final int MAX_GROWTH_RATE_RADIUS = 24;
public static final int MAX_CHARGE = 1000;
public static final float MAX_DECAY_FACTOR = 0.5F;
private static final int MAX_CURSORS = 32;
public static final int SHRIEKER_PLACEMENT_RATE = 11;
public static final int MAX_CURSOR_DISTANCE = 1024;
final boolean isWorldGeneration;
private final TagKey<Block> replaceableBlocks;
private final int growthSpawnCost;
private final int noGrowthRadius;
private final int chargeDecayRate;
private final int additionalDecayRate;
private List<SculkSpreader.ChargeCursor> cursors = new ArrayList<>();
public SculkSpreader(boolean p_222248_, TagKey<Block> p_222249_, int p_222250_, int p_222251_, int p_222252_, int p_222253_) {
this.isWorldGeneration = p_222248_;
this.replaceableBlocks = p_222249_;
this.growthSpawnCost = p_222250_;
this.noGrowthRadius = p_222251_;
this.chargeDecayRate = p_222252_;
this.additionalDecayRate = p_222253_;
}
public static SculkSpreader createLevelSpreader() {
return new SculkSpreader(false, BlockTags.SCULK_REPLACEABLE, 10, 4, 10, 5);
}
public static SculkSpreader createWorldGenSpreader() {
return new SculkSpreader(true, BlockTags.SCULK_REPLACEABLE_WORLD_GEN, 50, 1, 5, 10);
}
public TagKey<Block> replaceableBlocks() {
return this.replaceableBlocks;
}
public int growthSpawnCost() {
return this.growthSpawnCost;
}
public int noGrowthRadius() {
return this.noGrowthRadius;
}
public int chargeDecayRate() {
return this.chargeDecayRate;
}
public int additionalDecayRate() {
return this.additionalDecayRate;
}
public boolean isWorldGeneration() {
return this.isWorldGeneration;
}
@VisibleForTesting
public List<SculkSpreader.ChargeCursor> getCursors() {
return this.cursors;
}
public void clear() {
this.cursors.clear();
}
public void load(CompoundTag p_222270_) {
this.cursors.clear();
p_222270_.read("cursors", SculkSpreader.ChargeCursor.CODEC.sizeLimitedListOf(32)).orElse(List.of()).forEach(this::addCursor);
}
public void save(CompoundTag p_222276_) {
p_222276_.store("cursors", SculkSpreader.ChargeCursor.CODEC.listOf(), this.cursors);
}
public void addCursors(BlockPos p_222267_, int p_222268_) {
while (p_222268_ > 0) {
int i = Math.min(p_222268_, 1000);
this.addCursor(new SculkSpreader.ChargeCursor(p_222267_, i));
p_222268_ -= i;
}
}
private void addCursor(SculkSpreader.ChargeCursor p_222261_) {
if (this.cursors.size() < 32) {
this.cursors.add(p_222261_);
}
}
public void updateCursors(LevelAccessor p_222256_, BlockPos p_222257_, RandomSource p_222258_, boolean p_222259_) {
if (!this.cursors.isEmpty()) {
List<SculkSpreader.ChargeCursor> list = new ArrayList<>();
Map<BlockPos, SculkSpreader.ChargeCursor> map = new HashMap<>();
Object2IntMap<BlockPos> object2intmap = new Object2IntOpenHashMap<>();
for (SculkSpreader.ChargeCursor sculkspreader$chargecursor : this.cursors) {
if (!sculkspreader$chargecursor.isPosUnreasonable(p_222257_)) {
sculkspreader$chargecursor.update(p_222256_, p_222257_, p_222258_, this, p_222259_);
if (sculkspreader$chargecursor.charge <= 0) {
p_222256_.levelEvent(3006, sculkspreader$chargecursor.getPos(), 0);
} else {
BlockPos blockpos = sculkspreader$chargecursor.getPos();
object2intmap.computeInt(blockpos, (p_222264_, p_222265_) -> (p_222265_ == null ? 0 : p_222265_) + sculkspreader$chargecursor.charge);
SculkSpreader.ChargeCursor sculkspreader$chargecursor1 = map.get(blockpos);
if (sculkspreader$chargecursor1 == null) {
map.put(blockpos, sculkspreader$chargecursor);
list.add(sculkspreader$chargecursor);
} else if (!this.isWorldGeneration() && sculkspreader$chargecursor.charge + sculkspreader$chargecursor1.charge <= 1000) {
sculkspreader$chargecursor1.mergeWith(sculkspreader$chargecursor);
} else {
list.add(sculkspreader$chargecursor);
if (sculkspreader$chargecursor.charge < sculkspreader$chargecursor1.charge) {
map.put(blockpos, sculkspreader$chargecursor);
}
}
}
}
}
for (Entry<BlockPos> entry : object2intmap.object2IntEntrySet()) {
BlockPos blockpos1 = entry.getKey();
int k = entry.getIntValue();
SculkSpreader.ChargeCursor sculkspreader$chargecursor2 = map.get(blockpos1);
Collection<Direction> collection = sculkspreader$chargecursor2 == null ? null : sculkspreader$chargecursor2.getFacingData();
if (k > 0 && collection != null) {
int i = (int)(Math.log1p(k) / 2.3F) + 1;
int j = (i << 6) + MultifaceBlock.pack(collection);
p_222256_.levelEvent(3006, blockpos1, j);
}
}
this.cursors = list;
}
}
public static class ChargeCursor {
private static final ObjectArrayList<Vec3i> NON_CORNER_NEIGHBOURS = Util.make(
new ObjectArrayList<>(18),
p_222338_ -> BlockPos.betweenClosedStream(new BlockPos(-1, -1, -1), new BlockPos(1, 1, 1))
.filter(
p_222336_ -> (p_222336_.getX() == 0 || p_222336_.getY() == 0 || p_222336_.getZ() == 0)
&& !p_222336_.equals(BlockPos.ZERO)
)
.map(BlockPos::immutable)
.forEach(p_222338_::add)
);
public static final int MAX_CURSOR_DECAY_DELAY = 1;
private BlockPos pos;
int charge;
private int updateDelay;
private int decayDelay;
@Nullable
private Set<Direction> facings;
private static final Codec<Set<Direction>> DIRECTION_SET = Direction.CODEC
.listOf()
.xmap(p_222340_ -> Sets.newEnumSet(p_222340_, Direction.class), Lists::newArrayList);
public static final Codec<SculkSpreader.ChargeCursor> CODEC = RecordCodecBuilder.create(
p_222330_ -> p_222330_.group(
BlockPos.CODEC.fieldOf("pos").forGetter(SculkSpreader.ChargeCursor::getPos),
Codec.intRange(0, 1000).fieldOf("charge").orElse(0).forGetter(SculkSpreader.ChargeCursor::getCharge),
Codec.intRange(0, 1).fieldOf("decay_delay").orElse(1).forGetter(SculkSpreader.ChargeCursor::getDecayDelay),
Codec.intRange(0, Integer.MAX_VALUE).fieldOf("update_delay").orElse(0).forGetter(p_222346_ -> p_222346_.updateDelay),
DIRECTION_SET.lenientOptionalFieldOf("facings").forGetter(p_222343_ -> Optional.ofNullable(p_222343_.getFacingData()))
)
.apply(p_222330_, SculkSpreader.ChargeCursor::new)
);
private ChargeCursor(BlockPos p_222299_, int p_222300_, int p_222301_, int p_222302_, Optional<Set<Direction>> p_222303_) {
this.pos = p_222299_;
this.charge = p_222300_;
this.decayDelay = p_222301_;
this.updateDelay = p_222302_;
this.facings = p_222303_.orElse(null);
}
public ChargeCursor(BlockPos p_222296_, int p_222297_) {
this(p_222296_, p_222297_, 1, 0, Optional.empty());
}
public BlockPos getPos() {
return this.pos;
}
boolean isPosUnreasonable(BlockPos p_363353_) {
return this.pos.distChessboard(p_363353_) > 1024;
}
public int getCharge() {
return this.charge;
}
public int getDecayDelay() {
return this.decayDelay;
}
@Nullable
public Set<Direction> getFacingData() {
return this.facings;
}
private boolean shouldUpdate(LevelAccessor p_222326_, BlockPos p_222327_, boolean p_222328_) {
if (this.charge <= 0) {
return false;
} else if (p_222328_) {
return true;
} else {
return p_222326_ instanceof ServerLevel serverlevel ? serverlevel.shouldTickBlocksAt(p_222327_) : false;
}
}
public void update(LevelAccessor p_222312_, BlockPos p_222313_, RandomSource p_222314_, SculkSpreader p_222315_, boolean p_222316_) {
if (this.shouldUpdate(p_222312_, p_222313_, p_222315_.isWorldGeneration)) {
if (this.updateDelay > 0) {
this.updateDelay--;
} else {
BlockState blockstate = p_222312_.getBlockState(this.pos);
SculkBehaviour sculkbehaviour = getBlockBehaviour(blockstate);
if (p_222316_ && sculkbehaviour.attemptSpreadVein(p_222312_, this.pos, blockstate, this.facings, p_222315_.isWorldGeneration())) {
if (sculkbehaviour.canChangeBlockStateOnSpread()) {
blockstate = p_222312_.getBlockState(this.pos);
sculkbehaviour = getBlockBehaviour(blockstate);
}
p_222312_.playSound(null, this.pos, SoundEvents.SCULK_BLOCK_SPREAD, SoundSource.BLOCKS, 1.0F, 1.0F);
}
this.charge = sculkbehaviour.attemptUseCharge(this, p_222312_, p_222313_, p_222314_, p_222315_, p_222316_);
if (this.charge <= 0) {
sculkbehaviour.onDischarged(p_222312_, blockstate, this.pos, p_222314_);
} else {
BlockPos blockpos = getValidMovementPos(p_222312_, this.pos, p_222314_);
if (blockpos != null) {
sculkbehaviour.onDischarged(p_222312_, blockstate, this.pos, p_222314_);
this.pos = blockpos.immutable();
if (p_222315_.isWorldGeneration()
&& !this.pos.closerThan(new Vec3i(p_222313_.getX(), this.pos.getY(), p_222313_.getZ()), 15.0)) {
this.charge = 0;
return;
}
blockstate = p_222312_.getBlockState(blockpos);
}
if (blockstate.getBlock() instanceof SculkBehaviour) {
this.facings = MultifaceBlock.availableFaces(blockstate);
}
this.decayDelay = sculkbehaviour.updateDecayDelay(this.decayDelay);
this.updateDelay = sculkbehaviour.getSculkSpreadDelay();
}
}
}
}
void mergeWith(SculkSpreader.ChargeCursor p_222332_) {
this.charge = this.charge + p_222332_.charge;
p_222332_.charge = 0;
this.updateDelay = Math.min(this.updateDelay, p_222332_.updateDelay);
}
private static SculkBehaviour getBlockBehaviour(BlockState p_222334_) {
return p_222334_.getBlock() instanceof SculkBehaviour sculkbehaviour ? sculkbehaviour : SculkBehaviour.DEFAULT;
}
private static List<Vec3i> getRandomizedNonCornerNeighbourOffsets(RandomSource p_222306_) {
return Util.shuffledCopy(NON_CORNER_NEIGHBOURS, p_222306_);
}
@Nullable
private static BlockPos getValidMovementPos(LevelAccessor p_222308_, BlockPos p_222309_, RandomSource p_222310_) {
BlockPos.MutableBlockPos blockpos$mutableblockpos = p_222309_.mutable();
BlockPos.MutableBlockPos blockpos$mutableblockpos1 = p_222309_.mutable();
for (Vec3i vec3i : getRandomizedNonCornerNeighbourOffsets(p_222310_)) {
blockpos$mutableblockpos1.setWithOffset(p_222309_, vec3i);
BlockState blockstate = p_222308_.getBlockState(blockpos$mutableblockpos1);
if (blockstate.getBlock() instanceof SculkBehaviour && isMovementUnobstructed(p_222308_, p_222309_, blockpos$mutableblockpos1)) {
blockpos$mutableblockpos.set(blockpos$mutableblockpos1);
if (SculkVeinBlock.hasSubstrateAccess(p_222308_, blockstate, blockpos$mutableblockpos1)) {
break;
}
}
}
return blockpos$mutableblockpos.equals(p_222309_) ? null : blockpos$mutableblockpos;
}
private static boolean isMovementUnobstructed(LevelAccessor p_222318_, BlockPos p_222319_, BlockPos p_222320_) {
if (p_222319_.distManhattan(p_222320_) == 1) {
return true;
} else {
BlockPos blockpos = p_222320_.subtract(p_222319_);
Direction direction = Direction.fromAxisAndDirection(
Direction.Axis.X, blockpos.getX() < 0 ? Direction.AxisDirection.NEGATIVE : Direction.AxisDirection.POSITIVE
);
Direction direction1 = Direction.fromAxisAndDirection(
Direction.Axis.Y, blockpos.getY() < 0 ? Direction.AxisDirection.NEGATIVE : Direction.AxisDirection.POSITIVE
);
Direction direction2 = Direction.fromAxisAndDirection(
Direction.Axis.Z, blockpos.getZ() < 0 ? Direction.AxisDirection.NEGATIVE : Direction.AxisDirection.POSITIVE
);
if (blockpos.getX() == 0) {
return isUnobstructed(p_222318_, p_222319_, direction1) || isUnobstructed(p_222318_, p_222319_, direction2);
} else {
return blockpos.getY() == 0
? isUnobstructed(p_222318_, p_222319_, direction) || isUnobstructed(p_222318_, p_222319_, direction2)
: isUnobstructed(p_222318_, p_222319_, direction) || isUnobstructed(p_222318_, p_222319_, direction1);
}
}
}
private static boolean isUnobstructed(LevelAccessor p_222322_, BlockPos p_222323_, Direction p_222324_) {
BlockPos blockpos = p_222323_.relative(p_222324_);
return !p_222322_.getBlockState(blockpos).isFaceSturdy(p_222322_, blockpos, p_222324_.getOpposite());
}
}
}