kings tree implementation

tons of 1.20 files
This commit is contained in:
craisinlord committed 2026-09-10 09:51:32 -05:00
1 parent f4f4e0c717
commit 326c503ddb
3734 files changed
+121959 -8

No files matched your search

@@ -0,0 +1,55 @@
package com.craisinlord.antarchy.compat.item;
import java.util.function.Function;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
/** NBT-backed stand-in for the 1.20.1 port of Minecraft's 1.20.5+ item components. */
public final class AntarchyItemComponent<T> {
private final String key;
private final Function<CompoundTag, T> reader;
private final Function<T, Object> writer;
public AntarchyItemComponent(String key, Function<CompoundTag, T> reader, Function<T, Object> writer) {
this.key = key;
this.reader = reader;
this.writer = writer;
}
public boolean has(ItemStack stack) {
return stack.hasTag() && stack.getTag().contains(this.key);
}
public T get(ItemStack stack) {
return this.reader.apply(stack.getTag());
}
public T getOrDefault(ItemStack stack, T fallback) {
return this.has(stack) ? this.get(stack) : fallback;
}
public void set(ItemStack stack, T value) {
Object encoded = this.writer.apply(value);
CompoundTag tag = stack.getOrCreateTag();
if (encoded instanceof Integer integer) tag.putInt(this.key, integer);
else if (encoded instanceof String string) tag.putString(this.key, string);
else if (encoded instanceof Boolean bool) tag.putBoolean(this.key, bool);
}
public void remove(ItemStack stack) {
if (stack.hasTag()) stack.getTag().remove(this.key);
}
public static <T> AntarchyItemComponent<T> string(String key, Function<String, T> decoder, Function<T, String> encoder) {
return new AntarchyItemComponent<>(key, tag -> decoder.apply(tag.getString(key)), encoder::apply);
}
public static AntarchyItemComponent<Integer> integer(String key) {
return new AntarchyItemComponent<>(key, tag -> tag.getInt(key), value -> value);
}
public static <T> AntarchyItemComponent<T> marker(String key) {
return new AntarchyItemComponent<>(key, tag -> null, value -> true);
}
}
@@ -103,6 +103,8 @@ public final class AntarchyObjects {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static Supplier<EntityType<com.craisinlord.antarchy.content.entity.royal.RoyalBlackHoleEntity>> ROYAL_BLACK_HOLE = (Supplier<EntityType<com.craisinlord.antarchy.content.entity.royal.RoyalBlackHoleEntity>>) UNBOUND; public static Supplier<EntityType<com.craisinlord.antarchy.content.entity.royal.RoyalBlackHoleEntity>> ROYAL_BLACK_HOLE = (Supplier<EntityType<com.craisinlord.antarchy.content.entity.royal.RoyalBlackHoleEntity>>) UNBOUND;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static Supplier<EntityType<com.craisinlord.antarchy.content.entity.royal.RoyalAssailantBlackHoleEntity>> ROYAL_ASSAILANT_BLACK_HOLE = (Supplier<EntityType<com.craisinlord.antarchy.content.entity.royal.RoyalAssailantBlackHoleEntity>>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Block> VORTEX_LENS = (Supplier<Block>) UNBOUND; public static Supplier<Block> VORTEX_LENS = (Supplier<Block>) UNBOUND;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static Supplier<BlockEntityType<VortexLensBlockEntity>> VORTEX_LENS_BLOCK_ENTITY = (Supplier<BlockEntityType<VortexLensBlockEntity>>) UNBOUND; public static Supplier<BlockEntityType<VortexLensBlockEntity>> VORTEX_LENS_BLOCK_ENTITY = (Supplier<BlockEntityType<VortexLensBlockEntity>>) UNBOUND;
@@ -324,6 +326,8 @@ public final class AntarchyObjects {
public static Supplier<MobEffect> GLIMMERING_EFFECT = (Supplier<MobEffect>) UNBOUND; public static Supplier<MobEffect> GLIMMERING_EFFECT = (Supplier<MobEffect>) UNBOUND;
public static Supplier<MobEffect> DILATED_EFFECT = (Supplier<MobEffect>) UNBOUND; public static Supplier<MobEffect> DILATED_EFFECT = (Supplier<MobEffect>) UNBOUND;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static Supplier<MobEffect> CONTRACTED_EFFECT = (Supplier<MobEffect>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Fluid> LUMEN = (Supplier<Fluid>) UNBOUND; public static Supplier<Fluid> LUMEN = (Supplier<Fluid>) UNBOUND;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static Supplier<Fluid> FLOWING_LUMEN = (Supplier<Fluid>) UNBOUND; public static Supplier<Fluid> FLOWING_LUMEN = (Supplier<Fluid>) UNBOUND;
@@ -699,6 +703,10 @@ public final class AntarchyObjects {
ROYAL_BLACK_HOLE = supplier; ROYAL_BLACK_HOLE = supplier;
} }
public static void setRoyalAssailantBlackHole(Supplier<EntityType<com.craisinlord.antarchy.content.entity.royal.RoyalAssailantBlackHoleEntity>> supplier) {
ROYAL_ASSAILANT_BLACK_HOLE = supplier;
}
public static void setVortexLens(Supplier<Block> supplier) { public static void setVortexLens(Supplier<Block> supplier) {
VORTEX_LENS = supplier; VORTEX_LENS = supplier;
} }
@@ -819,6 +827,10 @@ public final class AntarchyObjects {
DILATED_EFFECT = supplier; DILATED_EFFECT = supplier;
} }
public static void setContractedEffect(Supplier<MobEffect> supplier) {
CONTRACTED_EFFECT = supplier;
}
public static void setElka(Supplier<EntityType<com.craisinlord.antarchy.content.entity.ElkaEntity>> supplier) { public static void setElka(Supplier<EntityType<com.craisinlord.antarchy.content.entity.ElkaEntity>> supplier) {
ELKA = supplier; ELKA = supplier;
} }
@@ -0,0 +1,61 @@
package com.craisinlord.antarchy.content.block;
import com.craisinlord.antarchy.content.block.entity.DimensionalTearMarkerBlockEntity;
import java.util.function.Supplier;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.BaseEntityBlock;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.RenderShape;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityTicker;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
public class DimensionalTearMarkerBlock extends BaseEntityBlock {
private static final VoxelShape EMPTY = Shapes.empty();
private final Supplier<? extends BlockEntityType<DimensionalTearMarkerBlockEntity>> blockEntityTypeSupplier;
public DimensionalTearMarkerBlock(Supplier<? extends BlockEntityType<DimensionalTearMarkerBlockEntity>> blockEntityTypeSupplier, BlockBehaviour.Properties properties) {
super(properties);
this.blockEntityTypeSupplier = blockEntityTypeSupplier;
}
@Override
public RenderShape getRenderShape(BlockState state) {
return RenderShape.INVISIBLE;
}
@Override
public VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
return EMPTY;
}
@Override
public VoxelShape getCollisionShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
return EMPTY;
}
@Override
public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
return new DimensionalTearMarkerBlockEntity(pos, state, this.blockEntityTypeSupplier);
}
@Override
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level, BlockState state, BlockEntityType<T> blockEntityType) {
if (level.isClientSide) {
return null;
}
return createTickerHelper(blockEntityType, this.blockEntityTypeSupplier.get(), (tickLevel, tickPos, tickState, blockEntity) -> {
if (tickLevel instanceof ServerLevel serverLevel) {
DimensionalTearMarkerBlockEntity.serverTick(serverLevel, tickPos, tickState, blockEntity);
}
});
}
}
@@ -0,0 +1,133 @@
package com.craisinlord.antarchy.content.block;
import com.craisinlord.antarchy.Antarchy;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.RandomSource;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.BonemealableBlock;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.DirectionProperty;
public class DirectionalNyxiumBlock extends Block implements BonemealableBlock {
public static final DirectionProperty VERTICAL_DIRECTION = BlockStateProperties.VERTICAL_DIRECTION;
private static final ResourceLocation NYXITE_ID = new ResourceLocation(Antarchy.MODID, "nyxite");
private static final ResourceLocation NADIR_FERN_ID = new ResourceLocation(Antarchy.MODID, "nadir_fern");
private static final ResourceLocation DUSKBELL_ID = new ResourceLocation(Antarchy.MODID, "duskbell");
private static final ResourceLocation NADIR_SAPLING_ID = new ResourceLocation(Antarchy.MODID, "nadir_sapling");
private static final ResourceLocation WHIRLFLOWER_ID = new ResourceLocation(Antarchy.MODID, "whirlflower");
private static final ResourceLocation SPIRALING_VINES_ID = new ResourceLocation(Antarchy.MODID, "spiraling_vines");
private static final ResourceLocation NADIR_ROOTS_ID = new ResourceLocation(Antarchy.MODID, "nadir_roots");
private static final ResourceLocation VERDANT_ROOTS_ID = new ResourceLocation(Antarchy.MODID, "verdant_roots");
private final Type type;
public DirectionalNyxiumBlock(BlockBehaviour.Properties properties, Type type) {
super(properties);
this.type = type;
this.registerDefaultState(this.stateDefinition.any().setValue(VERTICAL_DIRECTION, Direction.UP));
}
@Override
public BlockState getStateForPlacement(BlockPlaceContext context) {
return this.defaultBlockState().setValue(
VERTICAL_DIRECTION,
context.getClickedFace() == Direction.DOWN ? Direction.DOWN : Direction.UP
);
}
@Override
public void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
builder.add(VERTICAL_DIRECTION);
}
@Override
public boolean isValidBonemealTarget(LevelReader level, BlockPos pos, BlockState state, boolean isClientSide) {
return hasAdjacentNyxite(level, pos) || level.getBlockState(pos.relative(state.getValue(VERTICAL_DIRECTION))).isAir();
}
@Override
public boolean isBonemealSuccess(Level level, RandomSource random, BlockPos pos, BlockState state) {
return true;
}
@Override
public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) {
spreadToNearbyNyxite(level, random, pos, state);
BlockPos growthPos = pos.relative(state.getValue(VERTICAL_DIRECTION));
if (level.getBlockState(growthPos).isAir()) {
placeVegetation(level, random, growthPos, state.getValue(VERTICAL_DIRECTION));
}
}
private void spreadToNearbyNyxite(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) {
for (int i = 0; i < 24; i++) {
BlockPos targetPos = pos.offset(
random.nextInt(5) - 2,
random.nextInt(3) - 1,
random.nextInt(5) - 2
);
if (isNyxite(level.getBlockState(targetPos))) {
level.setBlock(targetPos, state, Block.UPDATE_ALL);
}
}
}
private void placeVegetation(ServerLevel level, RandomSource random, BlockPos pos, Direction direction) {
ResourceLocation id = switch (this.type) {
case NADIR -> {
if (direction == Direction.DOWN && random.nextInt(7) == 0) {
yield NADIR_SAPLING_ID;
}
yield switch (random.nextInt(5)) {
case 0 -> DUSKBELL_ID;
case 1, 2 -> NADIR_ROOTS_ID;
default -> NADIR_FERN_ID;
};
}
case VERDANT -> switch (random.nextInt(5)) {
case 0, 1 -> VERDANT_ROOTS_ID;
case 2 -> SPIRALING_VINES_ID;
default -> WHIRLFLOWER_ID;
};
};
Block block = BuiltInRegistries.BLOCK.get(id);
BlockState growthState = block.defaultBlockState();
if (growthState.hasProperty(SimpleDirectionalVineBlock.GROWTH_DIRECTION)) {
growthState = growthState.setValue(SimpleDirectionalVineBlock.GROWTH_DIRECTION, direction);
}
if (growthState.hasProperty(VERTICAL_DIRECTION)) {
growthState = growthState.setValue(VERTICAL_DIRECTION, direction);
}
if (growthState.canSurvive(level, pos)) {
level.setBlock(pos, growthState, Block.UPDATE_ALL);
}
}
private static boolean hasAdjacentNyxite(LevelReader level, BlockPos pos) {
for (Direction direction : Direction.values()) {
if (isNyxite(level.getBlockState(pos.relative(direction)))) {
return true;
}
}
return false;
}
private static boolean isNyxite(BlockState state) {
return BuiltInRegistries.BLOCK.getKey(state.getBlock()).equals(NYXITE_ID);
}
public enum Type {
NADIR,
VERDANT
}
}
@@ -0,0 +1,74 @@
package com.craisinlord.antarchy.content.block;
import com.craisinlord.antarchy.content.AntarchyTags;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.BushBlock;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.DirectionProperty;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.VoxelShape;
public class DirectionalThoraxisFlowerBlock extends BushBlock {
public static final DirectionProperty VERTICAL_DIRECTION = BlockStateProperties.VERTICAL_DIRECTION;
private static final VoxelShape UP_SHAPE = Block.box(3.0D, 0.0D, 3.0D, 13.0D, 13.0D, 13.0D);
private static final VoxelShape DOWN_SHAPE = Block.box(3.0D, 3.0D, 3.0D, 13.0D, 16.0D, 13.0D);
public DirectionalThoraxisFlowerBlock(BlockBehaviour.Properties properties) {
super(properties);
this.registerDefaultState(this.stateDefinition.any().setValue(VERTICAL_DIRECTION, Direction.UP));
}
@Override
public BlockState getStateForPlacement(BlockPlaceContext context) {
return this.defaultBlockState().setValue(
VERTICAL_DIRECTION,
context.getClickedFace() == Direction.DOWN ? Direction.DOWN : Direction.UP
);
}
@Override
public VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
return state.getValue(VERTICAL_DIRECTION) == Direction.DOWN ? DOWN_SHAPE : UP_SHAPE;
}
@Override
public boolean mayPlaceOn(BlockState state, BlockGetter level, BlockPos pos) {
return state.is(AntarchyTags.Blocks.NYXIUM);
}
@Override
public boolean canSurvive(BlockState state, LevelReader level, BlockPos pos) {
Direction growthDirection = state.getValue(VERTICAL_DIRECTION);
BlockPos supportPos = pos.relative(growthDirection.getOpposite());
return this.mayPlaceOn(level.getBlockState(supportPos), level, supportPos);
}
@Override
public BlockState updateShape(
BlockState state,
Direction direction,
BlockState neighborState,
LevelAccessor level,
BlockPos pos,
BlockPos neighborPos
) {
return state.canSurvive(level, pos)
? super.updateShape(state, direction, neighborState, level, pos, neighborPos)
: Blocks.AIR.defaultBlockState();
}
@Override
public void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
builder.add(VERTICAL_DIRECTION);
}
}
@@ -0,0 +1,32 @@
package com.craisinlord.antarchy.content.block;
import net.minecraft.core.Direction;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.DirectionProperty;
public class DreamSandstoneBlock extends Block {
public static final DirectionProperty VERTICAL_DIRECTION = BlockStateProperties.VERTICAL_DIRECTION;
public DreamSandstoneBlock(BlockBehaviour.Properties properties) {
super(properties);
this.registerDefaultState(this.stateDefinition.any().setValue(VERTICAL_DIRECTION, Direction.UP));
}
@Override
public BlockState getStateForPlacement(BlockPlaceContext context) {
return this.defaultBlockState().setValue(
VERTICAL_DIRECTION,
context.getClickedFace() == Direction.DOWN ? Direction.DOWN : Direction.UP
);
}
@Override
public void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
builder.add(VERTICAL_DIRECTION);
}
}
@@ -0,0 +1,76 @@
package com.craisinlord.antarchy.content.block;
import net.minecraft.core.BlockPos;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.RandomSource;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.BonemealableBlock;
import net.minecraft.world.level.block.DoublePlantBlock;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.DoubleBlockHalf;
import net.minecraft.world.phys.BlockHitResult;
/** A two-block-tall, light-emitting reed found around Lumen pools. */
public final class GlimmeringReedBlock extends DoublePlantBlock implements BonemealableBlock {
public GlimmeringReedBlock(BlockBehaviour.Properties properties) {
super(properties);
}
@Override
public InteractionResult use(
BlockState state,
Level level,
BlockPos pos,
Player player,
InteractionHand hand,
BlockHitResult hitResult
) {
if (player.getItemInHand(hand).is(Items.BONE_MEAL)) {
popResource(level, pos, new ItemStack(state.getBlock().asItem(), 1));
return InteractionResult.PASS;
}
return super.use(state, level, pos, player, hand, hitResult);
}
@Override
public boolean isValidBonemealTarget(LevelReader level, BlockPos pos, BlockState state, boolean isClientSide) {
return true;
}
@Override
public boolean isBonemealSuccess(Level level, RandomSource random, BlockPos pos, BlockState state) {
return true;
}
@Override
public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) {
}
@Override
public void animateTick(BlockState state, Level level, BlockPos pos, RandomSource random) {
super.animateTick(state, level, pos, random);
if (state.getValue(HALF) != DoubleBlockHalf.UPPER || random.nextInt(8) != 0) {
return;
}
double x = pos.getX() + 0.15D + random.nextDouble() * 0.7D;
double y = pos.getY() + 0.76D + random.nextDouble() * 0.18D;
double z = pos.getZ() + 0.15D + random.nextDouble() * 0.7D;
level.addParticle(ParticleTypes.GLOW, x, y, z, 0.0D, 0.02D, 0.0D);
if (random.nextInt(4) == 0) {
level.addParticle(ParticleTypes.ENCHANT, x, y + 0.02D, z, 0.0D, 0.015D, 0.0D);
}
}
}
@@ -0,0 +1,17 @@
package com.craisinlord.antarchy.content.block;
import net.minecraft.world.level.block.BushBlock;
import net.minecraft.world.level.block.state.BlockBehaviour;
public class GorevineBlock extends SimpleDirectionalVineBlock {
private static final int MAX_GROWTH_DISTANCE = 7;
public GorevineBlock(BlockBehaviour.Properties properties) {
super(properties);
}
@Override
public int maxDistance() {
return MAX_GROWTH_DISTANCE;
}
}
@@ -0,0 +1,131 @@
package com.craisinlord.antarchy.content.block;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.util.StringRepresentable;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.DirectionProperty;
import net.minecraft.world.level.block.state.properties.EnumProperty;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.VoxelShape;
public class LargeNadirFernBlock extends Block {
public static final DirectionProperty VERTICAL_DIRECTION = BlockStateProperties.VERTICAL_DIRECTION;
public static final EnumProperty<Half> HALF = EnumProperty.create("half", Half.class);
private static final VoxelShape SHAPE = Block.box(2.0D, 0.0D, 2.0D, 14.0D, 16.0D, 14.0D);
public LargeNadirFernBlock(BlockBehaviour.Properties properties) {
super(properties);
this.registerDefaultState(this.stateDefinition.any()
.setValue(VERTICAL_DIRECTION, Direction.UP)
.setValue(HALF, Half.BOTTOM));
}
@Override
public BlockState getStateForPlacement(BlockPlaceContext context) {
Direction direction = context.getClickedFace() == Direction.DOWN ? Direction.DOWN : Direction.UP;
BlockPos otherPos = context.getClickedPos().relative(direction);
if (!context.getLevel().getBlockState(otherPos).canBeReplaced(context)) {
return null;
}
return this.defaultBlockState().setValue(VERTICAL_DIRECTION, direction);
}
@Override
public void setPlacedBy(net.minecraft.world.level.Level level, BlockPos pos, BlockState state, net.minecraft.world.entity.LivingEntity placer, net.minecraft.world.item.ItemStack stack) {
placeOtherHalf(level, pos, state, Block.UPDATE_ALL);
}
public static void placeAt(LevelAccessor level, BlockPos pos, BlockState bottomState, int flags) {
level.setBlock(pos, bottomState.setValue(HALF, Half.BOTTOM), flags);
placeOtherHalf(level, pos, bottomState, flags);
}
private static void placeOtherHalf(LevelAccessor level, BlockPos pos, BlockState state, int flags) {
Direction direction = state.getValue(VERTICAL_DIRECTION);
level.setBlock(pos.relative(direction), state.setValue(HALF, Half.TOP), flags);
}
@Override
public VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
return SHAPE;
}
@Override
public boolean canSurvive(BlockState state, LevelReader level, BlockPos pos) {
Direction direction = state.getValue(VERTICAL_DIRECTION);
if (state.getValue(HALF) == Half.TOP) {
BlockState otherState = level.getBlockState(pos.relative(direction.getOpposite()));
return otherState.is(this)
&& otherState.getValue(HALF) == Half.BOTTOM
&& otherState.getValue(VERTICAL_DIRECTION) == direction;
}
BlockPos supportPos = pos.relative(direction.getOpposite());
BlockState supportState = level.getBlockState(supportPos);
BlockState topState = level.getBlockState(pos.relative(direction));
boolean topMatches = topState.isAir()
|| topState.is(this)
&& topState.getValue(HALF) == Half.TOP
&& topState.getValue(VERTICAL_DIRECTION) == direction;
return topMatches && supportState.isFaceSturdy(level, supportPos, direction);
}
@Override
public BlockState updateShape(
BlockState state,
Direction direction,
BlockState neighborState,
LevelAccessor level,
BlockPos pos,
BlockPos neighborPos
) {
return state.canSurvive(level, pos)
? super.updateShape(state, direction, neighborState, level, pos, neighborPos)
: Blocks.AIR.defaultBlockState();
}
@Override
public void onRemove(BlockState state, net.minecraft.world.level.Level level, BlockPos pos, BlockState newState, boolean movedByPiston) {
if (!state.is(newState.getBlock())) {
Direction verticalDirection = state.getValue(VERTICAL_DIRECTION);
BlockPos otherPos = state.getValue(HALF) == Half.BOTTOM
? pos.relative(verticalDirection)
: pos.relative(verticalDirection.getOpposite());
if (level.getBlockState(otherPos).is(this)) {
level.setBlock(otherPos, Blocks.AIR.defaultBlockState(), Block.UPDATE_ALL);
}
}
super.onRemove(state, level, pos, newState, movedByPiston);
}
@Override
public void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
builder.add(VERTICAL_DIRECTION, HALF);
}
public enum Half implements StringRepresentable {
TOP("top"),
BOTTOM("bottom");
private final String name;
Half(String name) {
this.name = name;
}
@Override
public String getSerializedName() {
return this.name;
}
}
}
@@ -0,0 +1,43 @@
package com.craisinlord.antarchy.content.block;
import com.craisinlord.antarchy.Antarchy;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.BonemealableBlock;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
public class NadirFernBlock extends DirectionalThoraxisFlowerBlock implements BonemealableBlock {
private static final ResourceLocation LARGE_NADIR_FERN_ID = new ResourceLocation(Antarchy.MODID, "large_nadir_fern");
public NadirFernBlock(BlockBehaviour.Properties properties) {
super(properties);
}
@Override
public boolean isValidBonemealTarget(LevelReader level, BlockPos pos, BlockState state, boolean isClientSide) {
return level.getBlockState(pos.relative(state.getValue(VERTICAL_DIRECTION))).isAir();
}
@Override
public boolean isBonemealSuccess(Level level, RandomSource random, BlockPos pos, BlockState state) {
return true;
}
@Override
public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) {
Direction direction = state.getValue(VERTICAL_DIRECTION);
Block largeFern = BuiltInRegistries.BLOCK.get(LARGE_NADIR_FERN_ID);
BlockState lowerState = largeFern.defaultBlockState()
.setValue(LargeNadirFernBlock.VERTICAL_DIRECTION, direction)
.setValue(LargeNadirFernBlock.HALF, LargeNadirFernBlock.Half.BOTTOM);
LargeNadirFernBlock.placeAt(level, pos, lowerState, Block.UPDATE_ALL);
}
}
@@ -0,0 +1,33 @@
package com.craisinlord.antarchy.content.block;
import net.minecraft.core.BlockPos;
import net.minecraft.core.particles.DustParticleOptions;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import org.joml.Vector3f;
public class NadirVeilBlock extends Block {
private static final int GLOW_LIGHT_LEVEL = 6;
private static final DustParticleOptions DRIP_PARTICLE =
new DustParticleOptions(new Vector3f(0.63F, 0.26F, 0.92F), 1.0F);
public NadirVeilBlock(BlockBehaviour.Properties properties) {
super(properties.lightLevel(state -> GLOW_LIGHT_LEVEL));
}
@Override
public void animateTick(BlockState state, Level level, BlockPos pos, RandomSource random) {
if (random.nextInt(6) != 0) {
return;
}
double x = pos.getX() + 0.5D + (random.nextDouble() - 0.5D) * 0.7D;
double y = pos.getY() + 0.15D + random.nextDouble() * 0.4D;
double z = pos.getZ() + 0.5D + (random.nextDouble() - 0.5D) * 0.7D;
level.addParticle(DRIP_PARTICLE, x, y, z, 0.0D, 0.02D + random.nextDouble() * 0.02D, 0.0D);
}
}
@@ -0,0 +1,68 @@
package com.craisinlord.antarchy.content.block;
import com.craisinlord.antarchy.content.block.entity.PortalGunPortalBaseBlockEntity;
import java.util.function.Supplier;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.BaseEntityBlock;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.RenderShape;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityTicker;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
public class PortalGunPortalBaseBlock extends BaseEntityBlock {
private static final VoxelShape EMPTY = Shapes.empty();
private final Supplier<? extends BlockEntityType<PortalGunPortalBaseBlockEntity>> blockEntityTypeSupplier;
public PortalGunPortalBaseBlock(Supplier<? extends BlockEntityType<PortalGunPortalBaseBlockEntity>> blockEntityTypeSupplier, BlockBehaviour.Properties properties) {
super(properties);
this.blockEntityTypeSupplier = blockEntityTypeSupplier;
}
@Override
public RenderShape getRenderShape(BlockState state) {
return RenderShape.INVISIBLE;
}
@Override
public VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
return EMPTY;
}
@Override
public VoxelShape getCollisionShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
return EMPTY;
}
@Override
public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
return new PortalGunPortalBaseBlockEntity(pos, state, this.blockEntityTypeSupplier);
}
@Override
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level, BlockState state, BlockEntityType<T> blockEntityType) {
if (level.isClientSide) {
return null;
}
return createTickerHelper(blockEntityType, this.blockEntityTypeSupplier.get(), (tickLevel, tickPos, tickState, blockEntity) -> {
if (tickLevel instanceof net.minecraft.server.level.ServerLevel serverLevel) {
PortalGunPortalBaseBlockEntity.serverTick(serverLevel, tickPos, tickState, blockEntity);
}
});
}
@Override
public void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean movedByPiston) {
if (!state.is(newState.getBlock()) && level.getBlockEntity(pos) instanceof PortalGunPortalBaseBlockEntity blockEntity) {
blockEntity.onBroken();
}
super.onRemove(state, level, pos, newState, movedByPiston);
}
}
@@ -0,0 +1,68 @@
package com.craisinlord.antarchy.content.block;
import com.craisinlord.antarchy.content.block.entity.PortalGunPortalMasterBlockEntity;
import java.util.function.Supplier;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.BaseEntityBlock;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.RenderShape;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityTicker;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
public class PortalGunPortalMasterBlock extends BaseEntityBlock {
private static final VoxelShape EMPTY = Shapes.empty();
private final Supplier<? extends BlockEntityType<PortalGunPortalMasterBlockEntity>> blockEntityTypeSupplier;
public PortalGunPortalMasterBlock(Supplier<? extends BlockEntityType<PortalGunPortalMasterBlockEntity>> blockEntityTypeSupplier, BlockBehaviour.Properties properties) {
super(properties);
this.blockEntityTypeSupplier = blockEntityTypeSupplier;
}
@Override
public RenderShape getRenderShape(BlockState state) {
return RenderShape.INVISIBLE;
}
@Override
public VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
return EMPTY;
}
@Override
public VoxelShape getCollisionShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
return EMPTY;
}
@Override
public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
return new PortalGunPortalMasterBlockEntity(pos, state, this.blockEntityTypeSupplier);
}
@Override
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level, BlockState state, BlockEntityType<T> blockEntityType) {
if (level.isClientSide) {
return null;
}
return createTickerHelper(blockEntityType, this.blockEntityTypeSupplier.get(), (tickLevel, tickPos, tickState, blockEntity) -> {
if (tickLevel instanceof net.minecraft.server.level.ServerLevel serverLevel) {
PortalGunPortalMasterBlockEntity.serverTick(serverLevel, tickPos, tickState, blockEntity);
}
});
}
@Override
public void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean movedByPiston) {
if (!state.is(newState.getBlock()) && level.getBlockEntity(pos) instanceof PortalGunPortalMasterBlockEntity blockEntity) {
blockEntity.onBroken();
}
super.onRemove(state, level, pos, newState, movedByPiston);
}
}
@@ -0,0 +1,17 @@
package com.craisinlord.antarchy.content.block;
import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.content.entity.royal.RoyalMountEntity;
import net.minecraft.world.entity.EntityType;
public class PrinceEggBlock extends RoyalEggBlock {
public PrinceEggBlock(Properties properties) {
super(properties);
}
@Override
public EntityType<? extends RoyalMountEntity> mountType() {
return AntarchyObjects.PRINCE.get();
}
}
@@ -0,0 +1,17 @@
package com.craisinlord.antarchy.content.block;
import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.content.entity.royal.RoyalMountEntity;
import net.minecraft.world.entity.EntityType;
public class PrincessEggBlock extends RoyalEggBlock {
public PrincessEggBlock(Properties properties) {
super(properties);
}
@Override
public EntityType<? extends RoyalMountEntity> mountType() {
return AntarchyObjects.PRINCESS.get();
}
}
@@ -0,0 +1,185 @@
package com.craisinlord.antarchy.content.block;
import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.block.entity.RoyalEggBlockEntity;
import com.craisinlord.antarchy.content.entity.royal.RoyalMountEntity;
import java.util.UUID;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.util.RandomSource;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.MobSpawnType;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.entity.item.FallingBlockEntity;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.BaseEntityBlock;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.FallingBlock;
import net.minecraft.world.level.block.RenderShape;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.IntegerProperty;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import org.jetbrains.annotations.Nullable;
public abstract class RoyalEggBlock extends BaseEntityBlock {
public static final IntegerProperty HATCH = BlockStateProperties.HATCH;
private static final int MAX_HATCH = 2;
private static final int FALL_DELAY = 2;
private static final VoxelShape SHAPE = Shapes.or(
Block.box(3.0D, 0.0D, 3.0D, 13.0D, 13.0D, 13.0D),
Block.box(2.0D, 1.0D, 2.0D, 14.0D, 11.0D, 14.0D),
Block.box(4.0D, 13.0D, 4.0D, 12.0D, 14.0D, 12.0D),
Block.box(5.0D, 14.0D, 5.0D, 11.0D, 15.0D, 11.0D),
Block.box(6.0D, 15.0D, 6.0D, 10.0D, 16.0D, 10.0D));
public RoyalEggBlock(Properties properties) {
super(properties);
this.registerDefaultState(this.stateDefinition.any().setValue(HATCH, 0));
}
public abstract EntityType<? extends RoyalMountEntity> mountType();
@Override
public void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
builder.add(HATCH);
}
@Override
public RenderShape getRenderShape(BlockState state) {
return RenderShape.MODEL;
}
@Override
public VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
return SHAPE;
}
@Nullable
@Override
public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
return new RoyalEggBlockEntity(pos, state);
}
@Override
public void setPlacedBy(Level level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) {
super.setPlacedBy(level, pos, state, placer, stack);
if (!level.isClientSide && placer instanceof Player player
&& level.getBlockEntity(pos) instanceof RoyalEggBlockEntity egg) {
egg.setPlacerUuid(player.getUUID());
}
}
@Override
public void attack(BlockState state, Level level, BlockPos pos, Player player) {
if (level instanceof ServerLevel serverLevel) {
this.teleport(state, serverLevel, pos);
}
}
private void teleport(BlockState state, ServerLevel level, BlockPos pos) {
RandomSource random = level.random;
for (int attempt = 0; attempt < 32; attempt++) {
BlockPos target = pos.offset(
random.nextInt(16) - random.nextInt(16),
random.nextInt(8) - random.nextInt(8),
random.nextInt(16) - random.nextInt(16));
if (level.getBlockState(target).isAir() && target.getY() > level.getMinBuildHeight()) {
RoyalEggBlockEntity source = level.getBlockEntity(pos) instanceof RoyalEggBlockEntity be ? be : null;
UUID placer = source == null ? null : source.getPlacerUuid();
int hatch = state.getValue(HATCH);
level.setBlock(pos, Blocks.AIR.defaultBlockState(), 2);
level.setBlock(target, state.setValue(HATCH, Math.max(0, hatch - 1)), 2);
if (level.getBlockEntity(target) instanceof RoyalEggBlockEntity moved) {
moved.setPlacerUuid(placer);
}
level.levelEvent(2003, pos, 0);
level.playSound(null, pos, SoundEvents.ENDER_DRAGON_FLAP, SoundSource.BLOCKS, 0.6F, 1.4F);
return;
}
}
}
@Override
public void onPlace(BlockState state, Level level, BlockPos pos, BlockState oldState, boolean movedByPiston) {
level.scheduleTick(pos, this, FALL_DELAY);
}
@Override
public BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor level, BlockPos pos, BlockPos neighborPos) {
level.scheduleTick(pos, this, FALL_DELAY);
return super.updateShape(state, direction, neighborState, level, pos, neighborPos);
}
@Override
public void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
if (!FallingBlock.isFree(level.getBlockState(pos.below())) || pos.getY() < level.getMinBuildHeight()) {
return;
}
CompoundTag blockData = level.getBlockEntity(pos) instanceof RoyalEggBlockEntity egg
? egg.saveWithoutMetadata()
: null;
FallingBlockEntity falling = FallingBlockEntity.fall(level, pos, state);
if (blockData != null && !blockData.isEmpty()) {
falling.blockData = blockData;
}
}
@Override
public boolean isRandomlyTicking(BlockState state) {
return true;
}
@Override
public void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
if (random.nextInt(Math.max(1, AntarchySettings.royalEggHatchChance())) != 0) {
return;
}
int hatch = state.getValue(HATCH);
if (hatch < MAX_HATCH) {
level.setBlock(pos, state.setValue(HATCH, hatch + 1), 2);
level.playSound(null, pos, SoundEvents.TURTLE_EGG_CRACK, SoundSource.BLOCKS, 0.7F, 0.9F + random.nextFloat() * 0.2F);
return;
}
this.hatch(level, pos);
}
private void hatch(ServerLevel level, BlockPos pos) {
UUID placer = level.getBlockEntity(pos) instanceof RoyalEggBlockEntity be ? be.getPlacerUuid() : null;
level.playSound(null, pos, SoundEvents.TURTLE_EGG_HATCH, SoundSource.BLOCKS, 0.9F, 1.0F);
level.setBlock(pos, Blocks.AIR.defaultBlockState(), 3);
RoyalMountEntity mount = this.mountType().create(level);
if (mount == null) {
return;
}
mount.moveTo(pos.getX() + 0.5D, pos.getY(), pos.getZ() + 0.5D, level.random.nextFloat() * 360.0F, 0.0F);
mount.setBaby(true);
mount.finalizeSpawn(level, level.getCurrentDifficultyAt(pos), MobSpawnType.BREEDING, null, null);
if (placer != null) {
mount.setTame(true);
mount.setOwnerUUID(placer);
if (level.getPlayerByUUID(placer) instanceof ServerPlayer owner) {
mount.tame(owner);
}
}
level.addFreshEntity(mount);
level.sendParticles(ParticleTypes.HAPPY_VILLAGER, pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D, 24, 0.4D, 0.4D, 0.4D, 0.0D);
}
}
@@ -0,0 +1,139 @@
package com.craisinlord.antarchy.content.block;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.tags.BlockTags;
import net.minecraft.util.RandomSource;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.LeavesBlock;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.level.block.state.properties.IntegerProperty;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.material.Fluids;
public class RoyalLeavesBlock extends LeavesBlock {
public static final IntegerProperty ROYAL_DISTANCE = IntegerProperty.create("royal_distance", 1, 17);
public static final BooleanProperty PERSISTENT = LeavesBlock.PERSISTENT;
public static final BooleanProperty WATERLOGGED = LeavesBlock.WATERLOGGED;
public static final int ROYAL_MAX_DISTANCE = 16;
public static final int ROYAL_DECAY_DISTANCE = ROYAL_MAX_DISTANCE + 1;
public RoyalLeavesBlock(BlockBehaviour.Properties properties) {
super(properties);
this.registerDefaultState(this.stateDefinition.any()
.setValue(DISTANCE, DECAY_DISTANCE)
.setValue(ROYAL_DISTANCE, ROYAL_DECAY_DISTANCE)
.setValue(PERSISTENT, false)
.setValue(WATERLOGGED, false));
}
@Override
public BlockState getStateForPlacement(BlockPlaceContext context) {
FluidState fluidState = context.getLevel().getFluidState(context.getClickedPos());
BlockState state = this.defaultBlockState()
.setValue(PERSISTENT, true)
.setValue(WATERLOGGED, fluidState.getType() == Fluids.WATER);
return recalculateDistance(state, context.getLevel(), context.getClickedPos());
}
@Override
public void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
builder.add(LeavesBlock.DISTANCE, ROYAL_DISTANCE, PERSISTENT, WATERLOGGED);
}
@Override
public boolean isRandomlyTicking(BlockState state) {
return decaying(state);
}
@Override
public boolean decaying(BlockState state) {
return !state.getValue(PERSISTENT) && state.getValue(ROYAL_DISTANCE) > ROYAL_MAX_DISTANCE;
}
@Override
public void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
if (decaying(state)) {
dropResources(state, level, pos);
level.removeBlock(pos, false);
}
}
@Override
public void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
level.setBlock(pos, recalculateDistance(state, level, pos), Block.UPDATE_ALL);
}
@Override
public BlockState updateShape(
BlockState state,
Direction direction,
BlockState neighborState,
LevelAccessor level,
BlockPos pos,
BlockPos neighborPos
) {
if (state.getValue(WATERLOGGED)) {
level.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(level));
}
int neighborDistance = Math.min(ROYAL_DECAY_DISTANCE, getDistanceAt(neighborState) + 1);
int currentDistance = state.getValue(ROYAL_DISTANCE);
if (neighborDistance != 1 || currentDistance != neighborDistance) {
level.scheduleTick(pos, this, 1);
}
return state;
}
private static BlockState recalculateDistance(BlockState state, LevelReader level, BlockPos pos) {
int bestDistance = ROYAL_DECAY_DISTANCE;
BlockPos.MutableBlockPos cursor = new BlockPos.MutableBlockPos();
for (Direction direction : Direction.values()) {
cursor.setWithOffset(pos, direction);
bestDistance = Math.min(bestDistance, getDistanceAt(level.getBlockState(cursor)) + 1);
if (bestDistance == 1) {
break;
}
}
return setRoyalDistance(state, Math.min(ROYAL_DECAY_DISTANCE, bestDistance));
}
private static BlockState setRoyalDistance(BlockState state, int distance) {
int royalDistance = Math.max(1, Math.min(ROYAL_DECAY_DISTANCE, distance));
int vanillaDistance = Math.min(LeavesBlock.DECAY_DISTANCE, royalDistance);
return state
.setValue(ROYAL_DISTANCE, royalDistance)
.setValue(LeavesBlock.DISTANCE, vanillaDistance);
}
private static int getDistanceAt(BlockState state) {
if (state.is(BlockTags.LOGS)) {
return 0;
}
if (state.hasProperty(ROYAL_DISTANCE)) {
return state.getValue(ROYAL_DISTANCE);
}
if (state.hasProperty(LeavesBlock.DISTANCE)) {
return state.getValue(LeavesBlock.DISTANCE);
}
return ROYAL_DECAY_DISTANCE;
}
public static BlockState setRoyalDistanceForWorldgen(BlockState state, int distance) {
return setRoyalDistance(state, distance);
}
}
@@ -0,0 +1,152 @@
package com.craisinlord.antarchy.content.block;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.advancement.AntarchyAdvancements;
import com.craisinlord.antarchy.content.worldgen.royal.RoyalTreeGrowers;
import java.util.LinkedHashMap;
import java.util.Map;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Holder;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.tags.BlockTags;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.BonemealableBlock;
import net.minecraft.world.level.block.BushBlock;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.feature.ConfiguredFeature;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.VoxelShape;
public class RoyalSaplingBlock extends BushBlock implements BonemealableBlock {
private static final ResourceLocation LET_IT_GROW_ADVANCEMENT = new ResourceLocation(Antarchy.MODID, "let_it_grow");
private static final VoxelShape SHAPE = Block.box(2.0D, 0.0D, 2.0D, 14.0D, 12.0D, 14.0D);
public RoyalSaplingBlock(BlockBehaviour.Properties properties) {
super(properties);
}
@Override
public VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
return SHAPE;
}
@Override
public boolean mayPlaceOn(BlockState state, BlockGetter level, BlockPos pos) {
return state.is(BlockTags.DIRT);
}
@Override
public boolean canSurvive(BlockState state, LevelReader level, BlockPos pos) {
return level.getBlockState(pos.below()).is(BlockTags.DIRT);
}
@Override
public BlockState updateShape(
BlockState state,
Direction direction,
BlockState neighborState,
LevelAccessor level,
BlockPos pos,
BlockPos neighborPos
) {
return state.canSurvive(level, pos)
? super.updateShape(state, direction, neighborState, level, pos, neighborPos)
: Blocks.AIR.defaultBlockState();
}
@Override
public void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
if (level.getMaxLocalRawBrightness(pos.above()) >= 9 && random.nextInt(7) == 0) {
this.growRoyalTree(level, pos, random);
}
}
@Override
public boolean isValidBonemealTarget(LevelReader level, BlockPos pos, BlockState state, boolean isClientSide) {
return true;
}
@Override
public boolean isBonemealSuccess(Level level, RandomSource random, BlockPos pos, BlockState state) {
return random.nextFloat() < 0.45F;
}
@Override
public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) {
this.growRoyalTree(level, pos, random);
}
private void growRoyalTree(ServerLevel level, BlockPos pos, RandomSource random) {
Map<BlockPos, BlockState> footprint = this.findFootprint(level, pos);
if (footprint == null) {
return;
}
BlockPos origin = footprint.keySet().iterator().next();
ResourceKey<ConfiguredFeature<?, ?>> variant = switch (random.nextInt(3)) {
case 0 -> RoyalTreeGrowers.ROYAL_TREE_SMALL;
case 1 -> RoyalTreeGrowers.ROYAL_TREE_MEDIUM;
default -> RoyalTreeGrowers.ROYAL_TREE_LARGE;
};
Holder.Reference<ConfiguredFeature<?, ?>> feature = level.registryAccess()
.lookupOrThrow(Registries.CONFIGURED_FEATURE)
.getOrThrow(variant);
for (BlockPos saplingPos : footprint.keySet()) {
level.removeBlock(saplingPos, false);
}
if (feature.value().place(level, level.getChunkSource().getGenerator(), random, origin)) {
for (ServerPlayer player : level.getEntitiesOfClass(ServerPlayer.class, new AABB(origin).inflate(8.0D))) {
AntarchyAdvancements.award(player, LET_IT_GROW_ADVANCEMENT);
}
return;
}
for (Map.Entry<BlockPos, BlockState> entry : footprint.entrySet()) {
level.setBlock(entry.getKey(), entry.getValue(), Block.UPDATE_ALL);
}
}
private Map<BlockPos, BlockState> findFootprint(ServerLevel level, BlockPos pos) {
for (int offsetX = 0; offsetX < 2; offsetX++) {
for (int offsetZ = 0; offsetZ < 2; offsetZ++) {
BlockPos origin = pos.offset(-offsetX, 0, -offsetZ);
Map<BlockPos, BlockState> blocks = new LinkedHashMap<>();
boolean matches = true;
for (int x = 0; x < 2 && matches; x++) {
for (int z = 0; z < 2; z++) {
BlockPos checkPos = origin.offset(x, 0, z);
BlockState checkState = level.getBlockState(checkPos);
if (!checkState.is(this)) {
matches = false;
break;
}
blocks.put(checkPos, checkState);
}
}
if (matches) {
return blocks;
}
}
}
return null;
}
}
@@ -0,0 +1,173 @@
package com.craisinlord.antarchy.content.block;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.RandomSource;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.BonemealableBlock;
import net.minecraft.world.level.block.BushBlock;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.level.block.state.properties.EnumProperty;
import net.minecraft.world.level.block.state.properties.IntegerProperty;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.VoxelShape;
public class SimpleDirectionalVineBlock extends BushBlock implements BonemealableBlock {
public static final EnumProperty<Direction> GROWTH_DIRECTION = EnumProperty.create("growth_direction", Direction.class, Direction.UP, Direction.DOWN);
public static final BooleanProperty TOP_CAP = BooleanProperty.create("top_cap");
public static final BooleanProperty BOTTOM_CAP = BooleanProperty.create("bottom_cap");
public static final IntegerProperty DISTANCE = IntegerProperty.create("distance", 0, 15);
public static final int MAX_DISTANCE = 6;
public static final VoxelShape SHAPE = Block.box(4.0D, 0.0D, 4.0D, 12.0D, 16.0D, 12.0D);
public SimpleDirectionalVineBlock(BlockBehaviour.Properties properties) {
super(properties);
this.registerDefaultState(this.stateDefinition.any()
.setValue(GROWTH_DIRECTION, Direction.DOWN)
.setValue(TOP_CAP, false)
.setValue(BOTTOM_CAP, true)
.setValue(DISTANCE, 0));
}
@Override
public VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
return SHAPE;
}
@Override
public BlockState getStateForPlacement(BlockPlaceContext context) {
BlockGetter level = context.getLevel();
BlockPos pos = context.getClickedPos();
Direction growthDirection = resolveGrowthDirection(level, pos);
return computeState(this.defaultBlockState().setValue(GROWTH_DIRECTION, growthDirection).setValue(DISTANCE, 0), level, pos);
}
private Direction resolveGrowthDirection(BlockGetter level, BlockPos pos) {
BlockState belowState = level.getBlockState(pos.below());
if (belowState.is(this) && belowState.getValue(GROWTH_DIRECTION) == Direction.UP) {
return Direction.UP;
}
BlockState aboveState = level.getBlockState(pos.above());
if (aboveState.is(this) && aboveState.getValue(GROWTH_DIRECTION) == Direction.DOWN) {
return Direction.DOWN;
}
return hasSturdyAnchor(level, pos, Direction.UP) ? Direction.DOWN : Direction.UP;
}
private static boolean hasSturdyAnchor(BlockGetter level, BlockPos pos, Direction towardAnchor) {
BlockPos anchorPos = pos.relative(towardAnchor);
BlockState anchorState = level.getBlockState(anchorPos);
return anchorState.isFaceSturdy(level, anchorPos, towardAnchor.getOpposite());
}
@Override
public boolean mayPlaceOn(BlockState state, BlockGetter level, BlockPos pos) {
return state.is(this) || state.isFaceSturdy(level, pos, Direction.DOWN) || state.isFaceSturdy(level, pos, Direction.UP);
}
@Override
public boolean canSurvive(BlockState state, LevelReader level, BlockPos pos) {
Direction anchorDirection = state.getValue(GROWTH_DIRECTION).getOpposite();
BlockPos anchorPos = pos.relative(anchorDirection);
BlockState anchorState = level.getBlockState(anchorPos);
if (anchorState.is(this)) {
return true;
}
return anchorState.isFaceSturdy(level, anchorPos, anchorDirection.getOpposite());
}
@Override
public BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor level, BlockPos pos, BlockPos neighborPos) {
if (!state.canSurvive(level, pos)) {
return net.minecraft.world.level.block.Blocks.AIR.defaultBlockState();
}
if (direction == Direction.UP || direction == Direction.DOWN) {
return computeState(state, level, pos);
}
return state;
}
public static BlockState computeState(BlockState state, BlockGetter level, BlockPos pos) {
boolean aboveIsSame = level.getBlockState(pos.above()).is(state.getBlock());
boolean belowIsSame = level.getBlockState(pos.below()).is(state.getBlock());
Direction growthDirection = state.getValue(GROWTH_DIRECTION);
boolean topCap = growthDirection == Direction.UP && !aboveIsSame;
boolean bottomCap = growthDirection == Direction.DOWN && !belowIsSame;
return state.setValue(TOP_CAP, topCap).setValue(BOTTOM_CAP, bottomCap);
}
public int maxDistance() {
return MAX_DISTANCE;
}
@Override
public boolean isRandomlyTicking(BlockState state) {
return isTip(state) && state.getValue(DISTANCE) < maxDistance();
}
@Override
public void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
if (random.nextInt(10) == 0) {
grow(level, pos, state);
}
}
@Override
public boolean isValidBonemealTarget(LevelReader level, BlockPos pos, BlockState state, boolean isClientSide) {
return isTip(state) && state.getValue(DISTANCE) < maxDistance() && level.getBlockState(pos.relative(state.getValue(GROWTH_DIRECTION))).isAir();
}
@Override
public boolean isBonemealSuccess(Level level, RandomSource random, BlockPos pos, BlockState state) {
return true;
}
@Override
public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) {
grow(level, pos, state);
}
private void grow(ServerLevel level, BlockPos pos, BlockState state) {
Direction direction = state.getValue(GROWTH_DIRECTION);
BlockPos targetPos = pos.relative(direction);
if (!level.getBlockState(targetPos).isAir()) {
return;
}
int nextDistance = Math.min(state.getValue(DISTANCE) + 1, maxDistance());
BlockState grownState = this.defaultBlockState()
.setValue(GROWTH_DIRECTION, direction)
.setValue(DISTANCE, nextDistance);
level.setBlock(targetPos, grownState, Block.UPDATE_ALL);
BlockState posState = level.getBlockState(pos);
if (posState.is(this)) {
level.setBlock(pos, computeState(posState, level, pos), Block.UPDATE_CLIENTS);
}
BlockState targetState = level.getBlockState(targetPos);
if (targetState.is(this)) {
level.setBlock(targetPos, computeState(targetState, level, targetPos), Block.UPDATE_CLIENTS);
}
}
public static boolean isTip(BlockState state) {
return state.getValue(GROWTH_DIRECTION) == Direction.DOWN ? state.getValue(BOTTOM_CAP) : state.getValue(TOP_CAP);
}
@Override
public void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
builder.add(GROWTH_DIRECTION, TOP_CAP, BOTTOM_CAP, DISTANCE);
}
}
@@ -0,0 +1,11 @@
package com.craisinlord.antarchy.content.block;
import net.minecraft.world.level.block.BushBlock;
import net.minecraft.world.level.block.state.BlockBehaviour;
public class SpiralingVinesBlock extends SimpleDirectionalVineBlock {
public SpiralingVinesBlock(BlockBehaviour.Properties properties) {
super(properties);
}
}
@@ -0,0 +1,45 @@
package com.craisinlord.antarchy.content.block;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.RotatedPillarBlock;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.VoxelShape;
public class TruffaloLogBlock extends RotatedPillarBlock {
private static final VoxelShape SHAPE_Y = Block.box(4.0D, 0.0D, 4.0D, 12.0D, 16.0D, 12.0D);
private static final VoxelShape SHAPE_X = Block.box(0.0D, 4.0D, 4.0D, 16.0D, 12.0D, 12.0D);
private static final VoxelShape SHAPE_Z = Block.box(4.0D, 4.0D, 0.0D, 12.0D, 12.0D, 16.0D);
public TruffaloLogBlock(BlockBehaviour.Properties properties) {
super(properties);
}
@Override
public VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
return shapeFor(state);
}
@Override
public VoxelShape getCollisionShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
return shapeFor(state);
}
@Override
public VoxelShape getOcclusionShape(BlockState state, BlockGetter level, BlockPos pos) {
return shapeFor(state);
}
private static VoxelShape shapeFor(BlockState state) {
return switch (state.getValue(AXIS)) {
case X -> SHAPE_X;
case Z -> SHAPE_Z;
default -> SHAPE_Y;
};
}
}
@@ -0,0 +1,110 @@
package com.craisinlord.antarchy.content.block;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.advancement.AntarchyAdvancements;
import com.craisinlord.antarchy.content.worldgen.truffalo.TruffaloTreeGrowers;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Holder;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.tags.TagKey;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.BonemealableBlock;
import net.minecraft.world.level.block.BushBlock;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.feature.ConfiguredFeature;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.VoxelShape;
public class TruffaloSaplingBlock extends BushBlock implements BonemealableBlock {
private static final ResourceLocation LET_IT_GROW_ADVANCEMENT = new ResourceLocation(Antarchy.MODID, "let_it_grow");
private static final TagKey<Block> SUPPORTS = TagKey.create(
Registries.BLOCK,
new ResourceLocation(Antarchy.MODID, "truffalo_sapling_supports")
);
private static final VoxelShape SHAPE = Block.box(2.0D, 4.0D, 2.0D, 14.0D, 16.0D, 14.0D);
public TruffaloSaplingBlock(BlockBehaviour.Properties properties) {
super(properties);
}
@Override
public VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
return SHAPE;
}
@Override
public boolean mayPlaceOn(BlockState state, BlockGetter level, BlockPos pos) {
return state.is(SUPPORTS);
}
@Override
public boolean canSurvive(BlockState state, LevelReader level, BlockPos pos) {
BlockPos supportPos = pos.below();
BlockState supportState = level.getBlockState(supportPos);
return supportState.is(SUPPORTS) && supportState.isFaceSturdy(level, supportPos, Direction.UP);
}
@Override
public void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
if (random.nextInt(7) == 0) {
this.growConfiguredTree(level, pos, state, random);
}
}
@Override
public BlockState updateShape(
BlockState state,
Direction direction,
BlockState neighborState,
LevelAccessor level,
BlockPos pos,
BlockPos neighborPos
) {
return state.canSurvive(level, pos)
? super.updateShape(state, direction, neighborState, level, pos, neighborPos)
: Blocks.AIR.defaultBlockState();
}
@Override
public boolean isValidBonemealTarget(LevelReader level, BlockPos pos, BlockState state, boolean isClientSide) {
return true;
}
@Override
public boolean isBonemealSuccess(Level level, RandomSource random, BlockPos pos, BlockState state) {
return random.nextFloat() < 0.45F;
}
@Override
public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) {
this.growConfiguredTree(level, pos, state, random);
}
private void growConfiguredTree(ServerLevel level, BlockPos pos, BlockState state, RandomSource random) {
Holder.Reference<ConfiguredFeature<?, ?>> feature = level.registryAccess()
.lookupOrThrow(Registries.CONFIGURED_FEATURE)
.getOrThrow(TruffaloTreeGrowers.TRUFFALO_TREE);
level.removeBlock(pos, false);
if (feature.value().place(level, level.getChunkSource().getGenerator(), random, pos)) {
for (ServerPlayer player : level.getEntitiesOfClass(ServerPlayer.class, new AABB(pos).inflate(8.0D))) {
AntarchyAdvancements.award(player, LET_IT_GROW_ADVANCEMENT);
}
return;
}
level.setBlock(pos, state, Block.UPDATE_ALL);
}
}
@@ -0,0 +1,26 @@
package com.craisinlord.antarchy.content.block;
import net.minecraft.core.BlockPos;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ShearsItem;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
public class TruffaloTuftBlock extends Block {
private static final float SHEARS_DESTROY_SPEED = 10.0F;
public TruffaloTuftBlock(BlockBehaviour.Properties properties) {
super(properties);
}
@Override
public float getDestroyProgress(BlockState state, Player player, BlockGetter level, BlockPos pos) {
if (player.getMainHandItem().getItem() instanceof ShearsItem) {
return SHEARS_DESTROY_SPEED;
}
return super.getDestroyProgress(state, player, level, pos);
}
}
@@ -0,0 +1,158 @@
package com.craisinlord.antarchy.content.block;
import com.craisinlord.antarchy.content.entity.UpwardFallingBlockEntity;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.RandomSource;
import net.minecraft.world.entity.item.FallingBlockEntity;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.PointedDripstoneBlock;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.DripstoneThickness;
import net.minecraft.world.level.material.Fluids;
public class TyphoniteSpikeBlock extends PointedDripstoneBlock {
@SuppressWarnings("rawtypes")
public TyphoniteSpikeBlock(BlockBehaviour.Properties properties) {
super(properties);
}
@Override
public BlockState getStateForPlacement(BlockPlaceContext context) {
LevelAccessor level = context.getLevel();
BlockPos pos = context.getClickedPos();
Direction direction = calculateTipDirection(level, pos, context.getNearestLookingVerticalDirection().getOpposite());
if (direction == null) {
return null;
}
boolean mergeTips = !context.isSecondaryUseActive();
DripstoneThickness thickness = calculateDripstoneThickness(level, pos, direction, mergeTips);
if (thickness == null) {
return null;
}
return this.defaultBlockState()
.setValue(TIP_DIRECTION, direction)
.setValue(THICKNESS, thickness)
.setValue(WATERLOGGED, level.getFluidState(pos).getType() == Fluids.WATER);
}
@Override
public BlockState updateShape(
BlockState state,
Direction direction,
BlockState neighborState,
LevelAccessor level,
BlockPos pos,
BlockPos neighborPos
) {
if (state.getValue(WATERLOGGED)) {
level.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(level));
}
if (direction != Direction.UP && direction != Direction.DOWN) {
return state;
}
Direction tipDirection = state.getValue(TIP_DIRECTION);
if (tipDirection == Direction.DOWN && level.getBlockTicks().hasScheduledTick(pos, this)) {
return state;
}
if (direction == tipDirection.getOpposite() && !this.canSurvive(state, level, pos)) {
if (tipDirection == Direction.DOWN) {
level.scheduleTick(pos, this, 2);
} else {
level.scheduleTick(pos, this, 1);
}
return state;
}
boolean mergeTips = state.getValue(THICKNESS) == DripstoneThickness.TIP_MERGE;
return state.setValue(THICKNESS, calculateDripstoneThickness(level, pos, tipDirection, mergeTips));
}
@Override
public boolean canSurvive(BlockState state, LevelReader level, BlockPos pos) {
return isValidPointedDripstonePlacement(level, pos, state.getValue(TIP_DIRECTION));
}
@Override
public void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
if (this.canSurvive(state, level, pos)) {
return;
}
if (state.getValue(TIP_DIRECTION) == Direction.DOWN) {
BlockPos.MutableBlockPos root = pos.mutable();
while (isTyphoniteSpikeWithDirection(level.getBlockState(root.above()), Direction.DOWN)) {
root.move(Direction.UP);
}
BlockPos.MutableBlockPos cursor = root.mutable();
while (isTyphoniteSpikeWithDirection(level.getBlockState(cursor), Direction.DOWN)) {
BlockState fallingState = level.getBlockState(cursor);
if (isTyphoniteSpikeWithDirection(fallingState, Direction.DOWN)) {
FallingBlockEntity.fall(level, cursor, fallingState);
}
cursor.move(Direction.DOWN);
}
return;
}
BlockPos.MutableBlockPos cursor = pos.mutable();
while (isTyphoniteSpikeWithDirection(level.getBlockState(cursor), Direction.UP)) {
BlockState spikeState = level.getBlockState(cursor);
UpwardFallingBlockEntity.fallUp(level, cursor, spikeState);
cursor.move(Direction.UP);
}
}
private static Direction calculateTipDirection(LevelReader level, BlockPos pos, Direction preferredDirection) {
if (isValidPointedDripstonePlacement(level, pos, preferredDirection)) {
return preferredDirection;
}
Direction opposite = preferredDirection.getOpposite();
return isValidPointedDripstonePlacement(level, pos, opposite) ? opposite : null;
}
private static DripstoneThickness calculateDripstoneThickness(LevelReader level, BlockPos pos, Direction direction, boolean mergeTips) {
Direction opposite = direction.getOpposite();
BlockState forwardState = level.getBlockState(pos.relative(direction));
if (isTyphoniteSpikeWithDirection(forwardState, opposite)) {
return !mergeTips && forwardState.getValue(THICKNESS) != DripstoneThickness.TIP_MERGE
? DripstoneThickness.TIP
: DripstoneThickness.TIP_MERGE;
}
if (!isTyphoniteSpikeWithDirection(forwardState, direction)) {
return DripstoneThickness.TIP;
}
DripstoneThickness forwardThickness = forwardState.getValue(THICKNESS);
if (forwardThickness == DripstoneThickness.TIP || forwardThickness == DripstoneThickness.TIP_MERGE) {
return DripstoneThickness.MIDDLE;
}
return DripstoneThickness.BASE;
}
private static boolean isValidPointedDripstonePlacement(LevelReader level, BlockPos pos, Direction direction) {
BlockPos supportPos = pos.relative(direction.getOpposite());
BlockState supportState = level.getBlockState(supportPos);
return supportState.isFaceSturdy(level, supportPos, direction) || isTyphoniteSpikeWithDirection(supportState, direction);
}
private static boolean isTyphoniteSpikeWithDirection(BlockState state, Direction direction) {
return state.getBlock() instanceof TyphoniteSpikeBlock && state.getValue(TIP_DIRECTION) == direction;
}
}
@@ -0,0 +1,110 @@
package com.craisinlord.antarchy.content.block;
import com.craisinlord.antarchy.content.block.entity.VortexLensBlockEntity;
import java.util.function.Supplier;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.BaseEntityBlock;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.RenderShape;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityTicker;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.level.block.state.properties.DirectionProperty;
import org.jetbrains.annotations.Nullable;
public class VortexLensBlock extends BaseEntityBlock {
public static final DirectionProperty FACING = BlockStateProperties.FACING;
public static final BooleanProperty POWERED = BlockStateProperties.POWERED;
public static final BooleanProperty PUSHING = BooleanProperty.create("pushing");
private final Supplier<? extends BlockEntityType<VortexLensBlockEntity>> blockEntityTypeSupplier;
public VortexLensBlock(Supplier<? extends BlockEntityType<VortexLensBlockEntity>> blockEntityTypeSupplier,
BlockBehaviour.Properties properties) {
super(properties);
this.blockEntityTypeSupplier = blockEntityTypeSupplier;
this.registerDefaultState(this.stateDefinition.any()
.setValue(FACING, Direction.UP).setValue(POWERED, false).setValue(PUSHING, false));
}
@Override
public void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
builder.add(FACING, POWERED, PUSHING);
}
@Override
public BlockState getStateForPlacement(BlockPlaceContext context) {
Level level = context.getLevel();
BlockPos pos = context.getClickedPos();
boolean bluestone = BluestoneSignalHelper.getBestNeighborSignal(level, pos) > 0;
return this.defaultBlockState()
.setValue(FACING, context.getClickedFace())
.setValue(POWERED, hasAnySignal(level, pos))
.setValue(PUSHING, bluestone);
}
@Override
public void neighborChanged(BlockState state, Level level, BlockPos pos, Block neighborBlock, BlockPos neighborPos,
boolean movedByPiston) {
if (!level.isClientSide && level.getBlockEntity(pos) instanceof VortexLensBlockEntity lens) {
lens.refreshImmediately();
}
}
@Override
public RenderShape getRenderShape(BlockState state) {
return RenderShape.MODEL;
}
@Nullable
@Override
public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
return new VortexLensBlockEntity(pos, state, this.blockEntityTypeSupplier);
}
@Nullable
@Override
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level, BlockState state,
BlockEntityType<T> blockEntityType) {
if (level.isClientSide) {
return null;
}
return createTickerHelper(blockEntityType, this.blockEntityTypeSupplier.get(), (tickLevel, tickPos, tickState, blockEntity) -> {
if (tickLevel instanceof net.minecraft.server.level.ServerLevel serverLevel) {
VortexLensBlockEntity.serverTick(serverLevel, tickPos, tickState, blockEntity);
}
});
}
@Override
public void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean movedByPiston) {
if (!state.is(newState.getBlock()) && level.getBlockEntity(pos) instanceof VortexLensBlockEntity lens) {
lens.removeActiveVortex();
}
super.onRemove(state, level, pos, newState, movedByPiston);
}
@Override
public BlockState rotate(BlockState state, Rotation rotation) {
return state.setValue(FACING, rotation.rotate(state.getValue(FACING)));
}
@Override
public BlockState mirror(BlockState state, Mirror mirror) {
return state.rotate(mirror.getRotation(state.getValue(FACING)));
}
private static boolean hasAnySignal(Level level, BlockPos pos) {
return level.getBestNeighborSignal(pos) > 0 || BluestoneSignalHelper.getBestNeighborSignal(level, pos) > 0;
}
}
@@ -0,0 +1,79 @@
package com.craisinlord.antarchy.content.block;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.AntarchyTags;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.BushBlock;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.DirectionProperty;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.VoxelShape;
public class WhirlflowerBlock extends BushBlock {
public static final DirectionProperty VERTICAL_DIRECTION = BlockStateProperties.VERTICAL_DIRECTION;
private static final VoxelShape UP_SHAPE = Block.box(3.0D, 0.0D, 3.0D, 13.0D, 13.0D, 13.0D);
private static final VoxelShape DOWN_SHAPE = Block.box(3.0D, 3.0D, 3.0D, 13.0D, 16.0D, 13.0D);
private static final ResourceLocation VEINED_TYPHONITE_ID = new ResourceLocation(Antarchy.MODID, "veined_typhonite");
public WhirlflowerBlock(BlockBehaviour.Properties properties) {
super(properties);
this.registerDefaultState(this.stateDefinition.any().setValue(VERTICAL_DIRECTION, Direction.UP));
}
@Override
public VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
return state.getValue(VERTICAL_DIRECTION) == Direction.DOWN ? DOWN_SHAPE : UP_SHAPE;
}
@Override
public BlockState getStateForPlacement(BlockPlaceContext context) {
return this.defaultBlockState().setValue(
VERTICAL_DIRECTION,
context.getClickedFace() == Direction.DOWN ? Direction.DOWN : Direction.UP
);
}
@Override
public boolean mayPlaceOn(BlockState state, BlockGetter level, BlockPos pos) {
return state.is(AntarchyTags.Blocks.NYXIUM)
|| BuiltInRegistries.BLOCK.getKey(state.getBlock()).equals(VEINED_TYPHONITE_ID);
}
@Override
public boolean canSurvive(BlockState state, LevelReader level, BlockPos pos) {
Direction growthDirection = state.getValue(VERTICAL_DIRECTION);
BlockPos supportPos = pos.relative(growthDirection.getOpposite());
return this.mayPlaceOn(level.getBlockState(supportPos), level, supportPos);
}
@Override
public BlockState updateShape(
BlockState state,
Direction direction,
BlockState neighborState,
LevelAccessor level,
BlockPos pos,
BlockPos neighborPos
) {
return state.canSurvive(level, pos)
? super.updateShape(state, direction, neighborState, level, pos, neighborPos)
: Blocks.AIR.defaultBlockState();
}
@Override
public void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
builder.add(VERTICAL_DIRECTION);
}
}
@@ -0,0 +1,187 @@
package com.craisinlord.antarchy.content.block.entity;
import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.entity.portal.DimensionalTearEntity;
import java.util.function.Supplier;
import net.minecraft.core.BlockPos;
import net.minecraft.core.HolderLookup;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.Mth;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
public class DimensionalTearMarkerBlockEntity extends BlockEntity {
private static final int MAX_ATTEMPT_TICKS = 80;
private static final int MIN_LINK_RANGE = 50;
private static final int MAX_LINK_RANGE = 500;
private static final int PARTNER_ORIGIN_ATTEMPTS = 8;
private static final int PARTNER_SEARCH_ATTEMPTS = 32;
private static final int LOCAL_SEARCH_ATTEMPTS = 12;
private static final int SEARCH_ATTEMPTS_PER_TICK = 1;
private static final int MIN_VERTICAL_SEPARATION = 24;
private static final int MIN_PARTNER_VERTICAL_OFFSET = 48;
private static final int MAX_PARTNER_VERTICAL_OFFSET = 96;
private static final int NATURAL_TEAR_LOCAL_RADIUS = 512;
private static final int NATURAL_TEAR_LOCAL_CAP = 6;
private int attemptTicks;
private int localAttempts;
private int partnerOriginAttempts;
private int partnerSearchAttempts;
@Nullable
private BlockPos firstPocket;
@Nullable
private BlockPos partnerOrigin;
public DimensionalTearMarkerBlockEntity(
BlockPos pos,
BlockState blockState,
Supplier<? extends BlockEntityType<DimensionalTearMarkerBlockEntity>> blockEntityTypeSupplier
) {
super(blockEntityTypeSupplier.get(), pos, blockState);
}
public static void serverTick(ServerLevel level, BlockPos pos, BlockState state, DimensionalTearMarkerBlockEntity blockEntity) {
blockEntity.attemptTicks++;
boolean spawned = blockEntity.trySpawnLinkedPair(level, pos);
if (spawned || blockEntity.attemptTicks >= MAX_ATTEMPT_TICKS) {
level.removeBlock(pos, false);
}
}
private boolean trySpawnLinkedPair(ServerLevel level, BlockPos origin) {
RandomSource random = level.getRandom();
for (int i = 0; i < SEARCH_ATTEMPTS_PER_TICK; i++) {
if (firstPocket == null) {
if (localAttempts >= LOCAL_SEARCH_ATTEMPTS) {
return false;
}
localAttempts++;
BlockPos candidate = randomCandidate(origin, random);
if (isUsablePocket(level, candidate)) {
firstPocket = candidate;
}
continue;
}
if (partnerOrigin == null && partnerOriginAttempts == 0
&& countNearbyTears(level, origin) >= NATURAL_TEAR_LOCAL_CAP) {
return false;
}
if (partnerOrigin == null || partnerSearchAttempts >= PARTNER_SEARCH_ATTEMPTS) {
if (partnerOriginAttempts >= PARTNER_ORIGIN_ATTEMPTS) {
return false;
}
partnerOrigin = linkedOrigin(firstPocket, random);
partnerOriginAttempts++;
partnerSearchAttempts = 0;
}
partnerSearchAttempts++;
BlockPos candidate = randomCandidate(partnerOrigin, random);
if (Math.abs(candidate.getY() - firstPocket.getY()) < MIN_VERTICAL_SEPARATION
|| !isUsablePocket(level, candidate)) {
continue;
}
return spawnPair(level, random, firstPocket, candidate);
}
return false;
}
private boolean spawnPair(ServerLevel level, RandomSource random, BlockPos first, BlockPos second) {
int lifetime = Math.max(1200, AntarchySettings.dimensionalTearLifetimeTicks());
float yawA = random.nextFloat() * 360.0F;
float yawB = Mth.wrapDegrees(yawA + 140.0F + random.nextFloat() * 80.0F);
DimensionalTearEntity tearA = DimensionalTearEntity.create(level, center(first), yawA, lifetime);
DimensionalTearEntity tearB = DimensionalTearEntity.create(level, center(second), yawB, lifetime);
tearA.linkTo(tearB);
tearB.linkTo(tearA);
level.addFreshEntity(tearA);
level.addFreshEntity(tearB);
return true;
}
private static BlockPos randomCandidate(BlockPos origin, RandomSource random) {
return origin.offset(
Mth.nextInt(random, -16, 16),
Mth.nextInt(random, -24, 24),
Mth.nextInt(random, -16, 16)
);
}
private static BlockPos linkedOrigin(BlockPos first, RandomSource random) {
double angle = random.nextDouble() * Math.PI * 2.0D;
int distance = Mth.nextInt(random, MIN_LINK_RANGE, MAX_LINK_RANGE);
int dx = Mth.floor(Math.cos(angle) * distance);
int dz = Mth.floor(Math.sin(angle) * distance);
int verticalOffset = Mth.nextInt(random, MIN_PARTNER_VERTICAL_OFFSET, MAX_PARTNER_VERTICAL_OFFSET);
int dy = random.nextBoolean() ? verticalOffset : -verticalOffset;
return first.offset(dx, dy, dz);
}
private static int countNearbyTears(ServerLevel level, BlockPos origin) {
AABB area = new AABB(origin).inflate(NATURAL_TEAR_LOCAL_RADIUS);
return level.getEntitiesOfClass(DimensionalTearEntity.class, area).size();
}
private static boolean isUsablePocket(ServerLevel level, BlockPos pos) {
int minY = level.getMinBuildHeight() + 8;
int maxY = level.getMaxBuildHeight() - 8;
if (pos.getY() < minY || pos.getY() > maxY) {
return false;
}
for (int dx = -2; dx <= 2; dx++) {
for (int dy = -2; dy <= 3; dy++) {
for (int dz = -4; dz <= 4; dz++) {
BlockPos checkPos = pos.offset(dx, dy, dz);
if (!level.isLoaded(checkPos)) {
return false;
}
if (!level.getBlockState(checkPos).getCollisionShape(level, checkPos).isEmpty()) {
return false;
}
}
}
}
return true;
}
private static Vec3 center(BlockPos pos) {
return new Vec3(pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D);
}
@Override
public void saveAdditional(CompoundTag tag) {
super.saveAdditional(tag);
tag.putInt("attempt_ticks", this.attemptTicks);
tag.putInt("local_attempts", this.localAttempts);
tag.putInt("partner_origin_attempts", this.partnerOriginAttempts);
tag.putInt("partner_search_attempts", this.partnerSearchAttempts);
if (this.firstPocket != null) {
tag.putLong("first_pocket", this.firstPocket.asLong());
}
if (this.partnerOrigin != null) {
tag.putLong("partner_origin", this.partnerOrigin.asLong());
}
}
@Override
public void load(CompoundTag tag) {
super.load(tag);
this.attemptTicks = tag.getInt("attempt_ticks");
this.localAttempts = tag.getInt("local_attempts");
this.partnerOriginAttempts = tag.getInt("partner_origin_attempts");
this.partnerSearchAttempts = tag.getInt("partner_search_attempts");
this.firstPocket = tag.contains("first_pocket") ? BlockPos.of(tag.getLong("first_pocket")) : null;
this.partnerOrigin = tag.contains("partner_origin") ? BlockPos.of(tag.getLong("partner_origin")) : null;
}
}
@@ -0,0 +1,148 @@
package com.craisinlord.antarchy.content.block.entity;
import com.craisinlord.antarchy.content.portalgun.PortalGunPortalEntity;
import java.util.UUID;
import java.util.function.Supplier;
import net.minecraft.core.BlockPos;
import net.minecraft.core.HolderLookup;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.ClientGamePacketListener;
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
public class PortalGunPortalBaseBlockEntity extends BlockEntity {
private UUID ownerId;
private UUID portalId;
private PortalGunPortalEntity.PortalSide side = PortalGunPortalEntity.PortalSide.BLUE;
private BlockPos masterPos = BlockPos.ZERO;
private UUID linkedPortalId;
private int pairTime;
public PortalGunPortalBaseBlockEntity(BlockPos pos, BlockState state, Supplier<? extends BlockEntityType<PortalGunPortalBaseBlockEntity>> typeSupplier) {
super(typeSupplier.get(), pos, state);
}
public void configure(UUID ownerId, UUID portalId, PortalGunPortalEntity.PortalSide side, BlockPos masterPos) {
this.ownerId = ownerId;
this.portalId = portalId;
this.side = side;
this.masterPos = masterPos.immutable();
this.setChanged();
}
public UUID getOwnerId() {
return this.ownerId;
}
public UUID getPortalId() {
return this.portalId;
}
public PortalGunPortalEntity.PortalSide getSide() {
return this.side;
}
public BlockPos getMasterPos() {
return this.masterPos;
}
public UUID getLinkedPortalId() {
return this.linkedPortalId;
}
public int getPairTime() {
return this.pairTime;
}
public void updatePair(UUID linkedPortalId, int pairTime) {
this.linkedPortalId = linkedPortalId;
this.pairTime = pairTime;
this.setChanged();
}
public boolean matches(UUID ownerId, UUID portalId, PortalGunPortalEntity.PortalSide side) {
return ownerId != null && portalId != null && ownerId.equals(this.ownerId) && portalId.equals(this.portalId) && this.side == side;
}
public void onBroken() {
if (!(this.level instanceof ServerLevel serverLevel) || this.portalId == null) {
return;
}
if (serverLevel.getEntity(this.portalId) instanceof PortalGunPortalEntity portal && !portal.isRemoved()) {
portal.discard();
}
}
public static void serverTick(ServerLevel level, BlockPos pos, BlockState state, PortalGunPortalBaseBlockEntity blockEntity) {
if (((level.getGameTime() + pos.asLong()) & 15L) != 0L) {
return;
}
if (!(level.getBlockEntity(blockEntity.masterPos) instanceof PortalGunPortalMasterBlockEntity master)
|| !master.matches(blockEntity.ownerId, blockEntity.portalId, blockEntity.side)
|| !pos.equals(master.getBasePos())) {
level.removeBlock(pos, false);
return;
}
if (!(level.getEntity(blockEntity.portalId) instanceof PortalGunPortalEntity portal)
|| portal.isRemoved()
|| !pos.equals(portal.getBasePos())
|| blockEntity.ownerId == null
|| !blockEntity.ownerId.equals(portal.getOwnerId())
|| blockEntity.side != portal.getPortalSide()) {
level.removeBlock(pos, false);
return;
}
UUID linkedPortalId = portal.getLinkedPortalId();
if ((linkedPortalId == null && blockEntity.linkedPortalId != null)
|| (linkedPortalId != null && !linkedPortalId.equals(blockEntity.linkedPortalId))
|| blockEntity.pairTime != portal.getPairTime()) {
blockEntity.linkedPortalId = linkedPortalId;
blockEntity.pairTime = portal.getPairTime();
blockEntity.setChanged();
}
}
@Override
public void saveAdditional(CompoundTag tag) {
super.saveAdditional(tag);
if (this.ownerId != null) {
tag.putUUID("OwnerId", this.ownerId);
}
if (this.portalId != null) {
tag.putUUID("PortalId", this.portalId);
}
if (this.linkedPortalId != null) {
tag.putUUID("LinkedPortalId", this.linkedPortalId);
}
tag.putInt("Side", this.side.ordinal());
tag.putInt("MasterX", this.masterPos.getX());
tag.putInt("MasterY", this.masterPos.getY());
tag.putInt("MasterZ", this.masterPos.getZ());
tag.putInt("PairTime", this.pairTime);
}
@Override
public void load(CompoundTag tag) {
super.load(tag);
this.ownerId = tag.hasUUID("OwnerId") ? tag.getUUID("OwnerId") : null;
this.portalId = tag.hasUUID("PortalId") ? tag.getUUID("PortalId") : null;
this.linkedPortalId = tag.hasUUID("LinkedPortalId") ? tag.getUUID("LinkedPortalId") : null;
this.side = tag.getInt("Side") == PortalGunPortalEntity.PortalSide.ORANGE.ordinal() ? PortalGunPortalEntity.PortalSide.ORANGE : PortalGunPortalEntity.PortalSide.BLUE;
this.masterPos = new BlockPos(tag.getInt("MasterX"), tag.getInt("MasterY"), tag.getInt("MasterZ"));
this.pairTime = tag.getInt("PairTime");
}
@Override
public CompoundTag getUpdateTag() {
return this.saveWithoutMetadata();
}
@Override
public Packet<ClientGamePacketListener> getUpdatePacket() {
return ClientboundBlockEntityDataPacket.create(this);
}
}
@@ -0,0 +1,300 @@
package com.craisinlord.antarchy.content.block.entity;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.portalgun.PortalGunPortalEntity;
import com.craisinlord.antarchy.content.portalgun.PortalGunPlacement;
import com.craisinlord.antarchy.content.portalgun.PortalGunSavedData;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.function.Supplier;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.HolderLookup;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.Tag;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.ClientGamePacketListener;
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.level.block.entity.BlockEntityTicker;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3;
public class PortalGunPortalMasterBlockEntity extends BlockEntity {
private UUID ownerId;
private UUID portalId;
private PortalGunPortalEntity.PortalSide side = PortalGunPortalEntity.PortalSide.BLUE;
private Direction facing = Direction.NORTH;
private Direction upAxis = Direction.UP;
private BlockPos basePos = BlockPos.ZERO;
private BlockPos[] portalSpots = new BlockPos[] {BlockPos.ZERO, BlockPos.ZERO};
private Set<BlockPos> compensatedSpots = Set.of();
private UUID linkedPortalId;
private int pairTime;
public PortalGunPortalMasterBlockEntity(BlockPos pos, BlockState state, Supplier<? extends BlockEntityType<PortalGunPortalMasterBlockEntity>> typeSupplier) {
super(typeSupplier.get(), pos, state);
}
public void configure(
UUID ownerId,
UUID portalId,
PortalGunPortalEntity.PortalSide side,
Direction facing,
Direction upAxis,
BlockPos basePos,
BlockPos[] portalSpots,
Set<BlockPos> compensatedSpots,
int pairTime
) {
this.ownerId = ownerId;
this.portalId = portalId;
this.side = side;
this.facing = facing;
this.upAxis = upAxis;
this.basePos = basePos.immutable();
this.portalSpots = new BlockPos[] {portalSpots[0].immutable(), portalSpots[1].immutable()};
this.compensatedSpots = new HashSet<>(compensatedSpots);
this.pairTime = pairTime;
this.setChanged();
}
public UUID getPortalId() {
return this.portalId;
}
public UUID getOwnerId() {
return this.ownerId;
}
public PortalGunPortalEntity.PortalSide getSide() {
return this.side;
}
public Direction getFacing() {
return this.facing;
}
public Direction getUpAxis() {
return this.upAxis;
}
public BlockPos getBasePos() {
return this.basePos;
}
public BlockPos[] getPortalSpots() {
return new BlockPos[] {this.portalSpots[0], this.portalSpots[1]};
}
public Set<BlockPos> getCompensatedSpots() {
return Set.copyOf(this.compensatedSpots);
}
public int getPairTime() {
return this.pairTime;
}
public UUID getLinkedPortalId() {
return this.linkedPortalId;
}
public void updatePair(UUID linkedPortalId, int pairTime) {
this.linkedPortalId = linkedPortalId;
this.pairTime = pairTime;
this.setChanged();
}
public boolean matches(UUID ownerId, UUID portalId, PortalGunPortalEntity.PortalSide side) {
return ownerId != null && portalId != null && ownerId.equals(this.ownerId) && portalId.equals(this.portalId) && this.side == side;
}
public void onBroken() {
if (!(this.level instanceof ServerLevel serverLevel) || this.portalId == null) {
return;
}
if (serverLevel.getEntity(this.portalId) instanceof PortalGunPortalEntity portal && !portal.isRemoved()) {
portal.discard();
}
}
public static void serverTick(ServerLevel level, BlockPos pos, BlockState state, PortalGunPortalMasterBlockEntity blockEntity) {
if (((level.getGameTime() + pos.asLong()) & 15L) != 0L) {
return;
}
if (!blockEntity.hasValidFootprint(level, pos)) {
level.removeBlock(pos, false);
return;
}
PortalGunPortalEntity portal = blockEntity.ensurePortalEntity(level);
if (portal == null
|| portal.isRemoved()
|| !pos.equals(portal.getMasterPos())
|| blockEntity.ownerId == null
|| !blockEntity.ownerId.equals(portal.getOwnerId())
|| blockEntity.side != portal.getPortalSide()) {
level.removeBlock(pos, false);
return;
}
blockEntity.ensureLinkedPortal(level, portal);
UUID linkedPortalId = portal.getLinkedPortalId();
if ((linkedPortalId == null && blockEntity.linkedPortalId != null)
|| (linkedPortalId != null && !linkedPortalId.equals(blockEntity.linkedPortalId))
|| blockEntity.pairTime != portal.getPairTime()) {
blockEntity.linkedPortalId = linkedPortalId;
blockEntity.pairTime = portal.getPairTime();
blockEntity.setChanged();
}
}
private boolean hasValidFootprint(ServerLevel level, BlockPos pos) {
if (this.ownerId == null || this.portalId == null || !pos.equals(this.worldPosition)) {
return false;
}
if (!(level.getBlockEntity(this.basePos) instanceof PortalGunPortalBaseBlockEntity base)
|| !base.matches(this.ownerId, this.portalId, this.side)
|| !pos.equals(base.getMasterPos())) {
return false;
}
List<BlockPos> supportPositions = new ArrayList<>(2);
for (BlockPos portalSpot : this.portalSpots) {
if (portalSpot == null || BlockPos.ZERO.equals(portalSpot)) {
return false;
}
supportPositions.add(portalSpot.relative(this.facing.getOpposite()));
}
for (BlockPos supportPos : supportPositions) {
BlockState supportState = level.getBlockState(supportPos);
if (!supportState.isFaceSturdy(level, supportPos, this.facing)) {
return false;
}
}
return true;
}
private PortalGunPortalEntity ensurePortalEntity(ServerLevel level) {
if (this.portalId == null || this.ownerId == null) {
return null;
}
if (level.getEntity(this.portalId) instanceof PortalGunPortalEntity portal && !portal.isRemoved()) {
return portal;
}
EntityType<?> rawType = BuiltInRegistries.ENTITY_TYPE.get(new ResourceLocation(Antarchy.MODID, "portal_gun_portal"));
if (!(rawType instanceof EntityType<?>)) {
return null;
}
PortalGunPlacement placement = PortalGunPlacement.fromStored(this.facing, this.upAxis, this.worldPosition, this.basePos, this.compensatedSpots);
@SuppressWarnings("unchecked")
PortalGunPortalEntity restored = new PortalGunPortalEntity((EntityType<? extends PortalGunPortalEntity>) rawType, level);
restored.setUUID(this.portalId);
restored.configure(this.ownerId, this.side, placement);
restored.restorePair(this.linkedPortalId, this.pairTime);
Vec3 center = placement.center();
restored.moveTo(center.x, center.y, center.z, placement.yaw(), 0.0F);
level.addFreshEntity(restored);
PortalGunSavedData.setPortal(level.getServer(), this.ownerId, this.side, restored.getUUID());
return restored;
}
private void ensureLinkedPortal(ServerLevel level, PortalGunPortalEntity portal) {
if (this.ownerId == null) {
return;
}
PortalGunPortalEntity counterpart = PortalGunSavedData.findLoadedPortal(
level.getServer(),
this.ownerId,
this.side == PortalGunPortalEntity.PortalSide.BLUE ? PortalGunPortalEntity.PortalSide.ORANGE : PortalGunPortalEntity.PortalSide.BLUE
);
if (counterpart == null || counterpart == portal || counterpart.isRemoved()) {
return;
}
if (!counterpart.getUUID().equals(portal.getLinkedPortalId()) || !portal.getUUID().equals(counterpart.getLinkedPortalId())) {
portal.linkTo(counterpart);
counterpart.linkTo(portal);
}
}
@Override
public void saveAdditional(CompoundTag tag) {
super.saveAdditional(tag);
if (this.ownerId != null) {
tag.putUUID("OwnerId", this.ownerId);
}
if (this.portalId != null) {
tag.putUUID("PortalId", this.portalId);
}
if (this.linkedPortalId != null) {
tag.putUUID("LinkedPortalId", this.linkedPortalId);
}
tag.putInt("Side", this.side.ordinal());
tag.putInt("Facing", this.facing.get3DDataValue());
tag.putInt("UpAxis", this.upAxis.get3DDataValue());
tag.putInt("BaseX", this.basePos.getX());
tag.putInt("BaseY", this.basePos.getY());
tag.putInt("BaseZ", this.basePos.getZ());
tag.putInt("PairTime", this.pairTime);
ListTag spots = new ListTag();
for (BlockPos portalSpot : this.portalSpots) {
CompoundTag spot = new CompoundTag();
spot.putInt("X", portalSpot.getX());
spot.putInt("Y", portalSpot.getY());
spot.putInt("Z", portalSpot.getZ());
spots.add(spot);
}
tag.put("PortalSpots", spots);
ListTag compensated = new ListTag();
for (BlockPos compensatedSpot : this.compensatedSpots) {
CompoundTag spot = new CompoundTag();
spot.putInt("X", compensatedSpot.getX());
spot.putInt("Y", compensatedSpot.getY());
spot.putInt("Z", compensatedSpot.getZ());
compensated.add(spot);
}
tag.put("CompensatedSpots", compensated);
}
@Override
public void load(CompoundTag tag) {
super.load(tag);
this.ownerId = tag.hasUUID("OwnerId") ? tag.getUUID("OwnerId") : null;
this.portalId = tag.hasUUID("PortalId") ? tag.getUUID("PortalId") : null;
this.linkedPortalId = tag.hasUUID("LinkedPortalId") ? tag.getUUID("LinkedPortalId") : null;
this.side = tag.getInt("Side") == PortalGunPortalEntity.PortalSide.ORANGE.ordinal() ? PortalGunPortalEntity.PortalSide.ORANGE : PortalGunPortalEntity.PortalSide.BLUE;
this.facing = Direction.from3DDataValue(tag.getInt("Facing"));
this.upAxis = Direction.from3DDataValue(tag.getInt("UpAxis"));
this.basePos = new BlockPos(tag.getInt("BaseX"), tag.getInt("BaseY"), tag.getInt("BaseZ"));
this.pairTime = tag.getInt("PairTime");
ListTag spots = tag.getList("PortalSpots", Tag.TAG_COMPOUND);
for (int i = 0; i < Math.min(2, spots.size()); i++) {
CompoundTag spot = spots.getCompound(i);
this.portalSpots[i] = new BlockPos(spot.getInt("X"), spot.getInt("Y"), spot.getInt("Z"));
}
ListTag compensated = tag.getList("CompensatedSpots", Tag.TAG_COMPOUND);
Set<BlockPos> compensatedSpots = new HashSet<>();
for (int i = 0; i < compensated.size(); i++) {
CompoundTag spot = compensated.getCompound(i);
compensatedSpots.add(new BlockPos(spot.getInt("X"), spot.getInt("Y"), spot.getInt("Z")));
}
this.compensatedSpots = compensatedSpots;
}
@Override
public CompoundTag getUpdateTag() {
return this.saveWithoutMetadata();
}
@Override
public Packet<ClientGamePacketListener> getUpdatePacket() {
return ClientboundBlockEntityDataPacket.create(this);
}
}
@@ -0,0 +1,42 @@
package com.craisinlord.antarchy.content.block.entity;
import com.craisinlord.antarchy.content.AntarchyObjects;
import java.util.UUID;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import org.jetbrains.annotations.Nullable;
public class RoyalEggBlockEntity extends BlockEntity {
@Nullable
private UUID placerUuid;
public RoyalEggBlockEntity(BlockPos pos, BlockState state) {
super(AntarchyObjects.ROYAL_EGG_BLOCK_ENTITY.get(), pos, state);
}
@Nullable
public UUID getPlacerUuid() {
return this.placerUuid;
}
public void setPlacerUuid(@Nullable UUID placerUuid) {
this.placerUuid = placerUuid;
this.setChanged();
}
@Override
public void load(CompoundTag tag) {
super.load(tag);
this.placerUuid = tag.hasUUID("Placer") ? tag.getUUID("Placer") : null;
}
@Override
public void saveAdditional(CompoundTag tag) {
super.saveAdditional(tag);
if (this.placerUuid != null) {
tag.putUUID("Placer", this.placerUuid);
}
}
}
@@ -0,0 +1,158 @@
package com.craisinlord.antarchy.content.block.entity;
import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.content.block.BluestoneSignalHelper;
import com.craisinlord.antarchy.content.block.VortexLensBlock;
import com.craisinlord.antarchy.content.entity.vortex.WindVortexEntity;
import java.util.UUID;
import java.util.function.Supplier;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.HolderLookup;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
public class VortexLensBlockEntity extends BlockEntity {
private static final String ACTIVE_VORTEX_KEY = "ActiveVortex";
private static final int LENS_VORTEX_REFRESH_DURATION = 40;
@Nullable
private UUID activeVortexUuid;
private boolean refreshNow = true;
public VortexLensBlockEntity(BlockPos pos, BlockState state,
Supplier<? extends BlockEntityType<VortexLensBlockEntity>> typeSupplier) {
super(typeSupplier.get(), pos, state);
}
public static void serverTick(ServerLevel level, BlockPos pos, BlockState state, VortexLensBlockEntity blockEntity) {
blockEntity.tickLens(level, pos, state);
}
public void refreshImmediately() {
this.refreshNow = true;
}
public void removeActiveVortex() {
if (this.level instanceof ServerLevel serverLevel) {
WindVortexEntity vortex = this.getActiveVortex(serverLevel);
if (vortex != null) {
vortex.removeFromLens();
}
}
this.activeVortexUuid = null;
this.setChanged();
}
private void tickLens(ServerLevel level, BlockPos pos, BlockState state) {
if (!this.refreshNow && (level.getGameTime() & 1L) == 1L) {
return;
}
this.refreshNow = false;
int bluestoneSignal = BluestoneSignalHelper.getBestNeighborSignal(level, pos);
int redstoneSignal = level.getBestNeighborSignal(pos);
boolean pushMode = bluestoneSignal > 0;
int signal = pushMode ? bluestoneSignal : redstoneSignal;
if (signal <= 0) {
this.removeActiveVortex();
this.updateBlockState(level, pos, state, false, false);
return;
}
WindVortexEntity vortex = this.getOrCreateVortex(level, pos, state);
if (vortex == null) {
return;
}
Direction facing = state.getValue(VortexLensBlock.FACING);
double signalScale = Mth.clamp(signal / 15.0D, 0.0D, 1.0D);
double radius = Mth.lerp(signalScale, AntarchySettings.vortexLensMinRadius(), AntarchySettings.vortexLensMaxRadius());
double height = Mth.lerp(signalScale, AntarchySettings.vortexLensMinHeight(), AntarchySettings.vortexLensMaxHeight());
Vec3 axis = Vec3.atLowerCornerOf(facing.getNormal());
Vec3 origin = Vec3.atCenterOf(pos).add(axis.scale(0.65D));
WindVortexEntity.VortexMode mode = pushMode ? WindVortexEntity.VortexMode.LENS_PUSH : WindVortexEntity.VortexMode.LENS_PULL;
double strength = pushMode ? AntarchySettings.vortexLensPushStrength() : AntarchySettings.vortexLensPullStrength();
vortex.setPos(origin.x, origin.y, origin.z);
vortex.setDeltaMovement(Vec3.ZERO);
vortex.setAxis(facing);
vortex.setMode(mode);
vortex.setVortexSize((float) height, (float) radius);
vortex.setVortexDurationTicks(LENS_VORTEX_REFRESH_DURATION);
vortex.resetVortexAge();
vortex.setVortexStrengths(strength, AntarchySettings.vortexLensLaunchStrength());
vortex.setDamaging(false);
this.updateBlockState(level, pos, state, true, pushMode);
}
@Nullable
private WindVortexEntity getOrCreateVortex(ServerLevel level, BlockPos pos, BlockState state) {
WindVortexEntity vortex = this.getActiveVortex(level);
if (vortex != null) {
return vortex;
}
Direction facing = state.getValue(VortexLensBlock.FACING);
Vec3 axis = Vec3.atLowerCornerOf(facing.getNormal());
Vec3 origin = Vec3.atCenterOf(pos).add(axis.scale(0.65D));
vortex = WindVortexEntity.create(level, AntarchyObjects.WIND_VORTEX.get(), origin, Vec3.ZERO, null, false);
vortex.setAxis(facing);
vortex.setMode(WindVortexEntity.VortexMode.LENS_PULL);
vortex.setVortexDurationTicks(LENS_VORTEX_REFRESH_DURATION);
vortex.resetVortexAge();
level.addFreshEntity(vortex);
this.activeVortexUuid = vortex.getUUID();
this.setChanged();
return vortex;
}
@Nullable
private WindVortexEntity getActiveVortex(ServerLevel level) {
if (this.activeVortexUuid == null) {
return null;
}
Entity entity = level.getEntity(this.activeVortexUuid);
if (entity instanceof WindVortexEntity vortex && vortex.isAlive()) {
return vortex;
}
this.activeVortexUuid = null;
this.setChanged();
return null;
}
private void updateBlockState(ServerLevel level, BlockPos pos, BlockState state, boolean powered, boolean pushing) {
if (!(state.getBlock() instanceof VortexLensBlock)) {
return;
}
if (state.getValue(VortexLensBlock.POWERED) != powered || state.getValue(VortexLensBlock.PUSHING) != pushing) {
level.setBlock(pos,
state.setValue(VortexLensBlock.POWERED, powered).setValue(VortexLensBlock.PUSHING, pushing),
Block.UPDATE_CLIENTS);
}
}
@Override
public void saveAdditional(CompoundTag tag) {
super.saveAdditional(tag);
if (this.activeVortexUuid != null) {
tag.putUUID(ACTIVE_VORTEX_KEY, this.activeVortexUuid);
}
}
@Override
public void load(CompoundTag tag) {
super.load(tag);
this.activeVortexUuid = tag.hasUUID(ACTIVE_VORTEX_KEY) ? tag.getUUID(ACTIVE_VORTEX_KEY) : null;
}
}
@@ -0,0 +1,146 @@
package com.craisinlord.antarchy.content.client;
import com.craisinlord.antarchy.content.time.TimeDilationFieldSnapshot;
import com.craisinlord.antarchy.content.time.TimeDilationFieldSnapshotSampler;
import com.craisinlord.antarchy.content.time.TimeDilationMath;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.util.Mth;
public final class ClientTimeDilationTicker {
private static volatile List<TimeDilationFieldSnapshot> activeFields = List.of();
private static ClientLevel activeLevel;
private static final Map<ItemEntity, ItemSpinState> ITEM_SPIN_STATES = new WeakHashMap<>();
private static final Map<LivingEntity, AttackAnimationState> ATTACK_ANIMATION_STATES = new WeakHashMap<>();
private static final Map<Entity, AnimationClockState> ANIMATION_CLOCKS = new WeakHashMap<>();
private ClientTimeDilationTicker() {
}
public static void tick(ClientLevel level) {
if (activeLevel != level) {
activeLevel = level;
activeFields = List.of();
ITEM_SPIN_STATES.clear();
ATTACK_ANIMATION_STATES.clear();
ANIMATION_CLOCKS.clear();
com.craisinlord.antarchy.content.time.TimeDilationApi.clearSyncedClientRates();
}
}
public static float dilateAnimationTime(Entity entity, float vanillaTime) {
double rate = com.craisinlord.antarchy.content.time.TimeDilationApi.getRate(entity);
if (rate >= TimeDilationMath.NORMAL_RATE) {
ANIMATION_CLOCKS.remove(entity);
return vanillaTime;
}
AnimationClockState state = ANIMATION_CLOCKS.computeIfAbsent(entity,
ignored -> new AnimationClockState(vanillaTime));
float delta = vanillaTime - state.lastVanillaTime;
if (delta >= 0.0F) {
state.temporalTime += delta * (float) rate;
}
state.lastVanillaTime = vanillaTime;
return state.temporalTime;
}
public static float dilateAttackAnimation(LivingEntity entity, float partialTick, float vanillaAnimation) {
double rate = com.craisinlord.antarchy.content.time.TimeDilationApi.getRate(entity);
if (rate >= TimeDilationMath.NORMAL_RATE) {
ATTACK_ANIMATION_STATES.remove(entity);
return vanillaAnimation;
}
AttackAnimationState state = ATTACK_ANIMATION_STATES.computeIfAbsent(
entity, ignored -> new AttackAnimationState(entity.tickCount, vanillaAnimation)
);
if (state.lastTick != entity.tickCount) {
state.previous = state.current;
state.current += (vanillaAnimation - state.current) * (float) rate;
state.lastTick = entity.tickCount;
}
return state.previous + (state.current - state.previous) * partialTick;
}
public static float dilateRotation(LivingEntity entity, float previous, float current, float partialTick) {
double rate = com.craisinlord.antarchy.content.time.TimeDilationApi.getRate(entity);
if (rate >= TimeDilationMath.NORMAL_RATE) {
return Mth.rotLerp(partialTick, previous, current);
}
return previous + Mth.wrapDegrees(current - previous) * partialTick * (float) rate;
}
public static float dilatePitch(LivingEntity entity, float previous, float current, float partialTick) {
double rate = com.craisinlord.antarchy.content.time.TimeDilationApi.getRate(entity);
if (rate >= TimeDilationMath.NORMAL_RATE) {
return Mth.lerp(partialTick, previous, current);
}
return previous + (current - previous) * partialTick * (float) rate;
}
public static float dilateWalkAmount(LivingEntity entity, float amount) {
double rate = com.craisinlord.antarchy.content.time.TimeDilationApi.getRate(entity);
return rate >= TimeDilationMath.NORMAL_RATE ? amount : amount * (float) rate;
}
public static float dilateItemSpin(ItemEntity item, float vanillaSpin) {
double rate = com.craisinlord.antarchy.content.time.TimeDilationApi.getRate(item);
if (rate >= TimeDilationMath.NORMAL_RATE) {
ITEM_SPIN_STATES.remove(item);
return vanillaSpin;
}
ItemSpinState state = ITEM_SPIN_STATES.computeIfAbsent(item, ignored -> new ItemSpinState(vanillaSpin));
float rawDelta = vanillaSpin - state.lastVanillaSpin;
state.lastVanillaSpin = vanillaSpin;
state.temporalSpin += rawDelta * (float) rate;
return state.temporalSpin;
}
private static final class ItemSpinState {
private float lastVanillaSpin;
private float temporalSpin;
private ItemSpinState(float initialSpin) {
this.lastVanillaSpin = initialSpin;
this.temporalSpin = initialSpin;
}
}
private static final class AttackAnimationState {
private int lastTick;
private float previous;
private float current;
private AttackAnimationState(int tick, float initial) {
this.lastTick = tick;
this.previous = initial;
this.current = initial;
}
}
private static final class AnimationClockState {
private float lastVanillaTime;
private float temporalTime;
private AnimationClockState(float initialTime) {
this.lastVanillaTime = initialTime;
this.temporalTime = initialTime;
}
}
public static double rateAt(double x, double y, double z) {
List<TimeDilationFieldSnapshot> fields = activeFields;
if (fields.isEmpty()) {
return TimeDilationMath.NORMAL_RATE;
}
return TimeDilationFieldSnapshotSampler.sample(fields, x, y, z);
}
public static void applyFields(List<TimeDilationFieldSnapshot> fields) {
activeFields = List.copyOf(fields);
}
}
@@ -0,0 +1,62 @@
package com.craisinlord.antarchy.content.client;
import com.craisinlord.antarchy.content.effect.RoyalEffectHooks;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Iterator;
import java.util.Map;
import java.util.WeakHashMap;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.effect.MobEffect;
public final class ContractionAfterimages {
public static final int MAX_SAMPLES = 10;
public static final int SAMPLE_LIFETIME_TICKS = 13;
private static final Map<LivingEntity, Deque<Sample>> HISTORY = new WeakHashMap<>();
private ContractionAfterimages() {}
public static void tick(ClientLevel level) {
MobEffect contracted = RoyalEffectHooks.contractedHolder();
int now = (int) (level.getGameTime() & 0x7fffffffL);
Iterator<Map.Entry<LivingEntity, Deque<Sample>>> iterator = HISTORY.entrySet().iterator();
while (iterator.hasNext()) {
LivingEntity tracked = iterator.next().getKey();
if (tracked == null || tracked.isRemoved() || !tracked.isAlive() || contracted == null
|| !tracked.hasEffect(contracted)) {
iterator.remove();
}
}
if (contracted == null) return;
for (Entity entity : level.entitiesForRendering()) {
if (!(entity instanceof LivingEntity living) || living.isInvisible() || !living.hasEffect(contracted)) continue;
Deque<Sample> samples = HISTORY.computeIfAbsent(living, ignored -> new ArrayDeque<>());
samples.addFirst(new Sample(living, now));
while (samples.size() > MAX_SAMPLES) samples.removeLast();
}
}
public static Deque<Sample> samples(LivingEntity entity) { return HISTORY.get(entity); }
public static float fade(Sample sample, int now) {
return Math.max(0.0F, 1.0F - (now - sample.recordedTick) / (float) SAMPLE_LIFETIME_TICKS);
}
public static void clear() { HISTORY.clear(); }
public static final class Sample {
public final double x, y, z;
public final float bodyYaw, headYaw, xRot, limbPos, limbSpeed, ageInTicks;
public final int recordedTick;
Sample(LivingEntity entity, int recordedTick) {
this.x = entity.getX(); this.y = entity.getY(); this.z = entity.getZ();
this.bodyYaw = entity.yBodyRot; this.headYaw = entity.getYHeadRot(); this.xRot = entity.getXRot();
this.limbPos = entity.walkAnimation.position();
this.limbSpeed = Math.min(entity.walkAnimation.speed(), 1.0F);
this.ageInTicks = entity.tickCount; this.recordedTick = recordedTick;
}
}
}
@@ -0,0 +1,294 @@
package com.craisinlord.antarchy.content.client;
import com.craisinlord.antarchy.content.entity.HoverboardEntity;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.BufferBuilder;
import com.mojang.blaze3d.vertex.BufferUploader;
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
import com.mojang.blaze3d.vertex.Tesselator;
import com.mojang.blaze3d.vertex.VertexFormat;
import net.minecraft.client.Camera;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.item.DyeColor;
import net.minecraft.world.phys.Vec3;
import org.joml.Matrix4f;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
public final class HoverboardTrailSystem {
private static final int MAX_TRAIL_POINTS = 18;
private static final int TRAIL_LIFETIME_TICKS = 12;
private static final float CORE_HALF_WIDTH = 0.0225f;
private static final float GLOW_HALF_WIDTH = 0.055f;
private static final double MIN_DISTANCE_BETWEEN_POINTS = 0.04D;
private static final double LATERAL_OFFSET = 0.5275D;
private static final double REAR_OFFSET = 0.77D;
private static final double HEIGHT_OFFSET = 0.14D;
private static final float[] DEFAULT_COLOR = rgb(0xC9A0FF);
private static final Map<Integer, BoardTrails> TRAILS = new HashMap<>();
private HoverboardTrailSystem() {
}
private static final class TrailPoint {
final Vec3 pos;
int age;
TrailPoint(Vec3 pos) {
this.pos = pos;
}
float progress() {
return (float) this.age / (float) TRAIL_LIFETIME_TICKS;
}
boolean expired() {
return this.age >= TRAIL_LIFETIME_TICKS;
}
}
private static final class BoardTrails {
final ArrayDeque<TrailPoint> left = new ArrayDeque<>();
final ArrayDeque<TrailPoint> right = new ArrayDeque<>();
float[] color = DEFAULT_COLOR;
}
public static void tick() {
Minecraft mc = Minecraft.getInstance();
if (mc.level == null) {
TRAILS.clear();
return;
}
if (mc.isPaused()) {
return;
}
Set<Integer> activeIds = new HashSet<>();
for (Entity entity : mc.level.entitiesForRendering()) {
if (!(entity instanceof HoverboardEntity hoverboard)) {
continue;
}
int id = hoverboard.getId();
activeIds.add(id);
BoardTrails boardTrails = TRAILS.computeIfAbsent(id, ignored -> new BoardTrails());
boardTrails.color = colorFor(hoverboard.getColor());
if (hoverboard.isVehicle() && hoverboard.isTrailActive()) {
TrailAnchors anchors = trailAnchors(hoverboard);
addPoint(boardTrails.left, anchors.left());
addPoint(boardTrails.right, anchors.right());
}
age(boardTrails.left);
age(boardTrails.right);
if (boardTrails.left.isEmpty() && boardTrails.right.isEmpty() && !hoverboard.isTrailActive()) {
TRAILS.remove(id);
}
}
TRAILS.keySet().removeIf(id -> !activeIds.contains(id));
}
public static void render(Camera camera, Matrix4f mat) {
if (TRAILS.isEmpty()) {
return;
}
Minecraft mc = Minecraft.getInstance();
if (mc.level == null) {
return;
}
Vec3 camPos = camera.getPosition();
List<TrailSnapshot> snapshots = new ArrayList<>(TRAILS.size() * 2);
for (BoardTrails boardTrails : TRAILS.values()) {
if (boardTrails.left.size() >= 2) {
snapshots.add(new TrailSnapshot(new ArrayList<>(boardTrails.left), boardTrails.color));
}
if (boardTrails.right.size() >= 2) {
snapshots.add(new TrailSnapshot(new ArrayList<>(boardTrails.right), boardTrails.color));
}
}
if (snapshots.isEmpty()) {
return;
}
RenderSystem.enableBlend();
RenderSystem.defaultBlendFunc();
RenderSystem.depthMask(false);
RenderSystem.disableCull();
RenderSystem.setShader(GameRenderer::getPositionColorShader);
Tesselator tesselator = Tesselator.getInstance();
BufferBuilder glowBuf = tesselator.getBuilder();
glowBuf.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR);
for (TrailSnapshot snapshot : snapshots) {
buildPass(glowBuf, mat, snapshot.points(), snapshot.color(), camPos, true);
}
BufferBuilder.RenderedBuffer glowMesh = glowBuf.end();
if (glowMesh != null) {
BufferUploader.drawWithShader(glowMesh);
}
BufferBuilder coreBuf = tesselator.getBuilder();
coreBuf.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR);
for (TrailSnapshot snapshot : snapshots) {
buildPass(coreBuf, mat, snapshot.points(), snapshot.color(), camPos, false);
}
BufferBuilder.RenderedBuffer coreMesh = coreBuf.end();
if (coreMesh != null) {
BufferUploader.drawWithShader(coreMesh);
}
RenderSystem.enableCull();
RenderSystem.depthMask(true);
RenderSystem.disableBlend();
}
private static void addPoint(ArrayDeque<TrailPoint> points, Vec3 pos) {
if (points.isEmpty()) {
points.addFirst(new TrailPoint(pos));
return;
}
if (points.peekFirst().pos.distanceToSqr(pos) < MIN_DISTANCE_BETWEEN_POINTS * MIN_DISTANCE_BETWEEN_POINTS) {
return;
}
points.addFirst(new TrailPoint(pos));
while (points.size() > MAX_TRAIL_POINTS) {
points.pollLast();
}
}
private static void age(ArrayDeque<TrailPoint> points) {
Iterator<TrailPoint> iterator = points.iterator();
while (iterator.hasNext()) {
TrailPoint point = iterator.next();
point.age++;
if (point.expired()) {
iterator.remove();
}
}
}
private static TrailAnchors trailAnchors(HoverboardEntity hoverboard) {
float yawRad = hoverboard.getYRot() * Mth.DEG_TO_RAD;
Vec3 forward = new Vec3(-Mth.sin(yawRad), 0.0D, Mth.cos(yawRad));
if (forward.lengthSqr() < 1.0E-6D) {
forward = new Vec3(0.0D, 0.0D, 1.0D);
}
forward = forward.normalize();
Vec3 right = new Vec3(forward.z, 0.0D, -forward.x);
Vec3 center = hoverboard.position().add(0.0D, HEIGHT_OFFSET, 0.0D).subtract(forward.scale(REAR_OFFSET));
return new TrailAnchors(center.subtract(right.scale(LATERAL_OFFSET)), center.add(right.scale(LATERAL_OFFSET)));
}
private static void buildPass(BufferBuilder buf, Matrix4f mat, List<TrailPoint> points, float[] color, Vec3 camPos, boolean glow) {
int n = points.size();
for (int i = 0; i < n - 1; i++) {
TrailPoint newer = points.get(i);
TrailPoint older = points.get(i + 1);
float alphaNearer = (glow ? 0.34f : 0.78f) * indexAlpha(i, n) * (1.0f - newer.progress());
float alphaFarther = (glow ? 0.34f : 0.78f) * indexAlpha(i + 1, n) * (1.0f - older.progress());
if (alphaNearer < 0.005f && alphaFarther < 0.005f) {
continue;
}
Vec3 right = cameraFacingRight(newer.pos, older.pos, camPos);
if (right == null) {
continue;
}
right = right.scale(glow ? GLOW_HALF_WIDTH : CORE_HALF_WIDTH);
Vec3 na = newer.pos.subtract(camPos);
Vec3 nb = older.pos.subtract(camPos);
buf.vertex(mat, (float) (na.x + right.x), (float) (na.y + right.y), (float) (na.z + right.z)).color(color[0], color[1], color[2], alphaNearer).endVertex();
buf.vertex(mat, (float) (na.x - right.x), (float) (na.y - right.y), (float) (na.z - right.z)).color(color[0], color[1], color[2], alphaNearer).endVertex();
buf.vertex(mat, (float) (nb.x - right.x), (float) (nb.y - right.y), (float) (nb.z - right.z)).color(color[0], color[1], color[2], alphaFarther).endVertex();
buf.vertex(mat, (float) (nb.x + right.x), (float) (nb.y + right.y), (float) (nb.z + right.z)).color(color[0], color[1], color[2], alphaFarther).endVertex();
}
}
private static Vec3 cameraFacingRight(Vec3 a, Vec3 b, Vec3 camPos) {
Vec3 segDir = b.subtract(a);
if (segDir.lengthSqr() < 1.0E-12D) {
return null;
}
segDir = segDir.normalize();
Vec3 mid = a.add(b).scale(0.5D);
Vec3 toCamera = camPos.subtract(mid);
if (toCamera.lengthSqr() < 1.0E-12D) {
return null;
}
toCamera = toCamera.normalize();
Vec3 right = segDir.cross(toCamera);
if (right.lengthSqr() < 1.0E-10D) {
right = segDir.cross(new Vec3(0.0D, 1.0D, 0.0D));
if (right.lengthSqr() < 1.0E-10D) {
return null;
}
}
return right.normalize();
}
private static float indexAlpha(int i, int n) {
if (n <= 1) {
return 1.0f;
}
return 1.0f - (float) i / (float) (n - 1);
}
private static float[] colorFor(DyeColor color) {
if (color == null) {
return DEFAULT_COLOR;
}
return switch (color) {
case PURPLE -> rgb(0x00FF3C);
case WHITE -> rgb(0xFF1919);
case ORANGE -> rgb(0x33CCFF);
case LIGHT_BLUE -> rgb(0xFF8A00);
case YELLOW -> rgb(0xB026FF);
case PINK -> rgb(0x66E0FF);
case LIME -> rgb(0xFF00E6);
case GRAY -> rgb(0xFF9F4D);
case LIGHT_GRAY -> rgb(0x9BFF1A);
case BLUE -> rgb(0x2ED9A0);
case BROWN -> rgb(0x66C6FF);
case GREEN -> rgb(0xFF3B1F);
case RED -> rgb(0xFF7A00);
case BLACK -> rgb(0xFFFFFF);
case MAGENTA -> rgb(0x00E5FF);
case CYAN -> rgb(0xFF3DDB);
};
}
private static float[] rgb(int color) {
return new float[] {
((color >> 16) & 255) / 255.0f,
((color >> 8) & 255) / 255.0f,
(color & 255) / 255.0f
};
}
private record TrailAnchors(Vec3 left, Vec3 right) {
}
private record TrailSnapshot(List<TrailPoint> points, float[] color) {
}
}
@@ -0,0 +1,101 @@
package com.craisinlord.antarchy.content.client;
import com.craisinlord.antarchy.content.portalgun.PortalGunPortalEntity;
import com.craisinlord.antarchy.content.portalgun.PortalGunTransformUtil;
import java.util.ArrayDeque;
import java.util.Deque;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.phys.Vec3;
public final class PortalGunEntityTransformationStack {
private final Entity entity;
private final Deque<EntityTransformation> stack = new ArrayDeque<>();
public PortalGunEntityTransformationStack(Entity entity) {
this.entity = entity;
}
public void push() {
this.stack.push(EntityTransformation.capture(this.entity));
}
public void pop() {
if (!this.stack.isEmpty()) {
this.stack.pop().restore(this.entity);
}
}
public Vec3 moveEntity(PortalGunPortalEntity sourcePortal, PortalGunPortalEntity destinationPortal, float partialTick) {
Vec3 interpolated = new Vec3(
net.minecraft.util.Mth.lerp(partialTick, this.entity.xo, this.entity.getX()),
net.minecraft.util.Mth.lerp(partialTick, this.entity.yo, this.entity.getY()),
net.minecraft.util.Mth.lerp(partialTick, this.entity.zo, this.entity.getZ())
);
Vec3 previous = new Vec3(this.entity.xo, this.entity.yo, this.entity.zo);
Vec3 transformed = destinationPortal.position().add(PortalGunTransformUtil.transformPosition(sourcePortal, destinationPortal, interpolated.subtract(sourcePortal.position())));
Vec3 transformedPrevious = destinationPortal.position().add(PortalGunTransformUtil.transformPosition(sourcePortal, destinationPortal, previous.subtract(sourcePortal.position())));
Vec3 look = PortalGunTransformUtil.transformVector(sourcePortal, destinationPortal, this.entity.getLookAngle()).normalize();
Vec3 velocity = PortalGunTransformUtil.transformVector(sourcePortal, destinationPortal, this.entity.getDeltaMovement());
float yaw = PortalGunTransformUtil.yawFromLook(look);
float pitch = PortalGunTransformUtil.pitchFromLook(look);
this.entity.setPos(transformed.x, transformed.y, transformed.z);
this.entity.xo = transformedPrevious.x;
this.entity.yo = transformedPrevious.y;
this.entity.zo = transformedPrevious.z;
this.entity.setYRot(yaw);
this.entity.setXRot(pitch);
this.entity.yRotO = yaw;
this.entity.xRotO = pitch;
this.entity.setDeltaMovement(velocity);
if (this.entity instanceof LivingEntity living) {
living.setYHeadRot(yaw);
living.setYBodyRot(yaw);
}
return transformed;
}
public void reset() {
while (!this.stack.isEmpty()) {
this.pop();
}
}
private record EntityTransformation(
double x,
double y,
double z,
double xo,
double yo,
double zo,
float yRot,
float xRot,
float yRotO,
float xRotO,
float yHeadRot,
float yBodyRot,
Vec3 deltaMovement
) {
private static EntityTransformation capture(Entity entity) {
float yHeadRot = entity instanceof LivingEntity living ? living.getYHeadRot() : entity.getYRot();
float yBodyRot = entity instanceof LivingEntity living ? living.yBodyRot : entity.getYRot();
return new EntityTransformation(entity.getX(), entity.getY(), entity.getZ(), entity.xo, entity.yo, entity.zo, entity.getYRot(), entity.getXRot(), entity.yRotO, entity.xRotO, yHeadRot, yBodyRot, entity.getDeltaMovement());
}
private void restore(Entity entity) {
entity.setPos(this.x, this.y, this.z);
entity.xo = this.xo;
entity.yo = this.yo;
entity.zo = this.zo;
entity.setYRot(this.yRot);
entity.setXRot(this.xRot);
entity.yRotO = this.yRotO;
entity.xRotO = this.xRotO;
entity.setDeltaMovement(this.deltaMovement);
if (entity instanceof LivingEntity living) {
living.setYHeadRot(this.yHeadRot);
living.setYBodyRot(this.yBodyRot);
}
}
}
}
@@ -0,0 +1,139 @@
package com.craisinlord.antarchy.content.client;
import com.craisinlord.antarchy.content.portalgun.PortalGunWorldPortalShape;
import java.util.ArrayDeque;
import java.util.Deque;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import org.joml.Matrix4f;
public final class PortalGunPortalRenderState {
private static final ThreadLocal<Deque<PortalGunWorldPortalShape>> ACTIVE_DESTINATION_SHAPES = ThreadLocal.withInitial(ArrayDeque::new);
private static final ThreadLocal<Deque<PortalGunWorldPortalShape>> ACTIVE_SOURCE_SHAPES = ThreadLocal.withInitial(ArrayDeque::new);
private static final ThreadLocal<Deque<PortalRenderContext>> ACTIVE_CONTEXTS = ThreadLocal.withInitial(ArrayDeque::new);
private static final ThreadLocal<Integer> RENDER_ALL_DEPTH = ThreadLocal.withInitial(() -> 0);
private PortalGunPortalRenderState() {
}
public static void pushPortalView(PortalGunWorldPortalShape sourceShape, PortalGunWorldPortalShape destinationShape, boolean renderAll) {
ACTIVE_SOURCE_SHAPES.get().push(sourceShape);
ACTIVE_DESTINATION_SHAPES.get().push(destinationShape);
if (renderAll) {
RENDER_ALL_DEPTH.set(RENDER_ALL_DEPTH.get() + 1);
}
}
public static void pushPortalView(PortalRenderContext context) {
ACTIVE_CONTEXTS.get().push(context);
pushPortalView(context.sourceShape(), context.destinationShape(), context.renderAll());
}
public static void popPortalView(boolean renderAll) {
Deque<PortalGunWorldPortalShape> sourceShapes = ACTIVE_SOURCE_SHAPES.get();
if (!sourceShapes.isEmpty()) {
sourceShapes.pop();
}
if (sourceShapes.isEmpty()) {
ACTIVE_SOURCE_SHAPES.remove();
}
Deque<PortalGunWorldPortalShape> destinationShapes = ACTIVE_DESTINATION_SHAPES.get();
if (!destinationShapes.isEmpty()) {
destinationShapes.pop();
}
if (destinationShapes.isEmpty()) {
ACTIVE_DESTINATION_SHAPES.remove();
}
if (renderAll) {
int depth = Math.max(0, RENDER_ALL_DEPTH.get() - 1);
if (depth == 0) {
RENDER_ALL_DEPTH.remove();
} else {
RENDER_ALL_DEPTH.set(depth);
}
}
Deque<PortalRenderContext> contexts = ACTIVE_CONTEXTS.get();
if (!contexts.isEmpty()) {
contexts.pop();
}
if (contexts.isEmpty()) {
ACTIVE_CONTEXTS.remove();
}
}
public static void pushDestinationShape(PortalGunWorldPortalShape shape) {
ACTIVE_DESTINATION_SHAPES.get().push(shape);
}
public static void popDestinationShape() {
Deque<PortalGunWorldPortalShape> shapes = ACTIVE_DESTINATION_SHAPES.get();
if (!shapes.isEmpty()) {
shapes.pop();
}
if (shapes.isEmpty()) {
ACTIVE_DESTINATION_SHAPES.remove();
}
}
public static PortalGunWorldPortalShape getDestinationShape() {
Deque<PortalGunWorldPortalShape> shapes = ACTIVE_DESTINATION_SHAPES.get();
return shapes.isEmpty() ? null : shapes.peek();
}
public static PortalGunWorldPortalShape getSourceShape() {
Deque<PortalGunWorldPortalShape> shapes = ACTIVE_SOURCE_SHAPES.get();
return shapes.isEmpty() ? null : shapes.peek();
}
public static PortalRenderContext getContext() {
Deque<PortalRenderContext> contexts = ACTIVE_CONTEXTS.get();
return contexts.isEmpty() ? null : contexts.peek();
}
public static int getRenderLevel() {
return ACTIVE_CONTEXTS.get().size();
}
public static boolean renderAll() {
return RENDER_ALL_DEPTH.get() > 0;
}
public static boolean shouldRenderBounds(AABB bounds) {
if (renderAll()) {
return true;
}
PortalRenderContext context = getContext();
if (context == null) {
PortalGunWorldPortalShape destinationShape = getDestinationShape();
PortalGunWorldPortalShape sourceShape = getSourceShape();
return destinationShape == null || shouldRenderBounds(bounds, sourceShape, destinationShape, false);
}
return shouldRenderBounds(bounds, context.sourceShape(), context.destinationShape(), context.renderAll());
}
private static boolean shouldRenderBounds(AABB bounds, PortalGunWorldPortalShape sourceShape, PortalGunWorldPortalShape destinationShape, boolean renderAll) {
if (renderAll) {
return true;
}
if (!destinationShape.intersectsFront(bounds, 0.125D)) {
return false;
}
if (!destinationShape.intersectsPortalColumn(bounds, 1.5D, 1.5D, destinationShape.halfDepth() + 96.0D)) {
return false;
}
return sourceShape == null || !sourceShape.intersectsFront(bounds, -0.02D);
}
public record PortalRenderContext(
PortalGunWorldPortalShape sourceShape,
PortalGunWorldPortalShape destinationShape,
Vec3 cameraPos,
Vec3 look,
Vec3 up,
Matrix4f viewMatrix,
Matrix4f projectionMatrix,
int renderLevel,
boolean renderAll
) {
}
}
@@ -0,0 +1,123 @@
package com.craisinlord.antarchy.content.client;
import com.craisinlord.antarchy.content.portalgun.PortalGunPortalEntity;
import com.craisinlord.antarchy.content.portalgun.PortalGunTransformUtil;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import net.minecraft.client.Minecraft;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.phys.Vec3;
public final class PortalGunRollClientState {
private static final int TELEPORT_COOLDOWN_TICKS = 40;
private static final float ROLL_DECAY = 0.85F;
private static final float ROLL_EPSILON = 0.05F;
private static final Map<UUID, Long> TELEPORT_COOLDOWNS = new HashMap<>();
private static float previousRollDegrees;
private static float rollDegrees;
private PortalGunRollClientState() {
}
public static float getRollDegrees() {
return rollDegrees;
}
public static float getRollDegrees(float partialTick) {
return previousRollDegrees + (rollDegrees - previousRollDegrees) * partialTick;
}
public static boolean isEntityInsidePortal(Minecraft minecraft, Entity entity) {
if (minecraft.level == null || entity == null) {
return false;
}
for (Entity renderEntity : minecraft.level.entitiesForRendering()) {
if (renderEntity instanceof PortalGunPortalEntity portal && portal.isAlive() && portal.intersectsEntityBounds(entity)) {
return true;
}
}
return false;
}
public static boolean isCameraInsidePortal(Minecraft minecraft) {
Entity cameraEntity = minecraft.getCameraEntity();
return cameraEntity != null && isEntityInsidePortal(minecraft, cameraEntity);
}
public static void clear() {
previousRollDegrees = 0.0F;
rollDegrees = 0.0F;
TELEPORT_COOLDOWNS.clear();
}
public static void tick(Minecraft minecraft) {
if (minecraft.level == null || minecraft.player == null) {
clear();
return;
}
Entity cameraEntity = minecraft.getCameraEntity();
if (cameraEntity == null) {
return;
}
previousRollDegrees = rollDegrees;
rollDegrees *= ROLL_DECAY;
if (Math.abs(rollDegrees) < ROLL_EPSILON) {
rollDegrees = 0.0F;
}
long gameTime = minecraft.level.getGameTime();
TELEPORT_COOLDOWNS.entrySet().removeIf(entry -> entry.getValue() <= gameTime);
for (Entity entity : minecraft.level.entitiesForRendering()) {
if (!(entity instanceof PortalGunPortalEntity portal) || !portal.isAlive()) {
continue;
}
PortalGunPortalEntity linkedPortal = portal.getLinkedPortal();
if (linkedPortal == null || !linkedPortal.isAlive()) {
continue;
}
if (!portal.shouldRenderFront(cameraEntity.position())) {
continue;
}
if (!canTeleport(cameraEntity, gameTime)) {
continue;
}
Vec3 crossingProbe = portal.resolveCrossingProbe(cameraEntity);
if (crossingProbe == null) {
continue;
}
applyPredictedTeleport(cameraEntity, portal, linkedPortal, crossingProbe, gameTime);
break;
}
}
private static boolean canTeleport(Entity entity, long gameTime) {
if (!entity.isAlive() || entity instanceof PortalGunPortalEntity || entity.isPassenger() || entity.isVehicle()) {
return false;
}
long cooldownUntil = TELEPORT_COOLDOWNS.getOrDefault(entity.getUUID(), 0L);
return cooldownUntil <= gameTime;
}
private static void applyPredictedTeleport(Entity entity, PortalGunPortalEntity sourcePortal, PortalGunPortalEntity destinationPortal, Vec3 currentProbe, long gameTime) {
Vec3 transformedLook = PortalGunTransformUtil.transformVector(sourcePortal, destinationPortal, entity.getLookAngle()).normalize();
Vec3 transformedUp = PortalGunTransformUtil.transformVector(sourcePortal, destinationPortal, PortalGunTransformUtil.upVectorFromLookAndRoll(entity.getLookAngle(), rollDegrees)).normalize();
Vec3 relativeProbe = currentProbe.subtract(sourcePortal.position());
Vec3 transformedProbe = PortalGunTransformUtil.transformPosition(sourcePortal, destinationPortal, relativeProbe);
Vec3 transformedEntityOffset = PortalGunTransformUtil.transformVector(sourcePortal, destinationPortal, entity.position().subtract(currentProbe));
Vec3 destinationNormal = destinationPortal.getNormalVec().normalize();
double exitClearance = Math.max(0.35D, entity.getBbWidth() * 0.5D + 0.16D);
Vec3 exitProbe = destinationPortal.position().add(transformedProbe).add(destinationNormal.scale(exitClearance - transformedProbe.dot(destinationNormal)));
Vec3 exitPos = exitProbe.add(transformedEntityOffset);
Vec3 transformedMotion = PortalGunTransformUtil.transformVector(sourcePortal, destinationPortal, entity.getDeltaMovement());
double outward = transformedMotion.dot(destinationNormal);
if (outward < 0.42D) {
transformedMotion = transformedMotion.add(destinationNormal.scale(0.42D - outward));
}
rollDegrees = PortalGunTransformUtil.rollFromOrientation(transformedLook, transformedUp);
entity.teleportTo(exitPos.x, exitPos.y, exitPos.z);
entity.setDeltaMovement(transformedMotion);
entity.setYRot(PortalGunTransformUtil.yawFromLook(transformedLook));
entity.setXRot(PortalGunTransformUtil.pitchFromLook(transformedLook));
TELEPORT_COOLDOWNS.put(entity.getUUID(), gameTime + TELEPORT_COOLDOWN_TICKS);
}
}
@@ -0,0 +1,27 @@
package com.craisinlord.antarchy.content.client;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public final class WormHookTetherClientState {
private static final Map<Integer, Integer> TETHERS = new ConcurrentHashMap<>();
private WormHookTetherClientState() {
}
public static void update(int playerId, int hookId) {
if (hookId < 0) {
TETHERS.remove(playerId);
} else {
TETHERS.put(playerId, hookId);
}
}
public static Map<Integer, Integer> snapshot() {
return Map.copyOf(TETHERS);
}
public static void clear() {
TETHERS.clear();
}
}
@@ -0,0 +1,32 @@
package com.craisinlord.antarchy.content.client.model;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.entity.HoverboardEntity;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.DyeColor;
import software.bernie.geckolib.model.GeoModel;
public class HoverboardModel extends GeoModel<HoverboardEntity> {
private static final ResourceLocation MODEL = new ResourceLocation(Antarchy.MODID, "geo/hoverboard.geo.json");
private static final ResourceLocation ANIMATION = new ResourceLocation(Antarchy.MODID, "animations/hoverboard.animation.json");
private static final ResourceLocation BASE_TEXTURE = new ResourceLocation(Antarchy.MODID, "textures/entity/hoverboard/hoverboard.png");
@Override
public ResourceLocation getModelResource(HoverboardEntity animatable) {
return MODEL;
}
@Override
public ResourceLocation getTextureResource(HoverboardEntity animatable) {
DyeColor color = animatable.getColor();
if (color == null) {
return BASE_TEXTURE;
}
return new ResourceLocation(Antarchy.MODID, "textures/entity/hoverboard/hoverboard_" + color.getName() + ".png");
}
@Override
public ResourceLocation getAnimationResource(HoverboardEntity animatable) {
return ANIMATION;
}
}
@@ -0,0 +1,23 @@
package com.craisinlord.antarchy.content.client.model;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.entity.ManticoreEntity;
import net.minecraft.resources.ResourceLocation;
import software.bernie.geckolib.model.GeoModel;
public class ManticoreModel extends GeoModel<ManticoreEntity> {
@Override
public ResourceLocation getModelResource(ManticoreEntity animatable) {
return new ResourceLocation(Antarchy.MODID, "geo/manticore.geo.json");
}
@Override
public ResourceLocation getTextureResource(ManticoreEntity animatable) {
return new ResourceLocation(Antarchy.MODID, "textures/entity/manticore.png");
}
@Override
public ResourceLocation getAnimationResource(ManticoreEntity animatable) {
return new ResourceLocation(Antarchy.MODID, "animations/manticore.animation.json");
}
}
@@ -0,0 +1,25 @@
package com.craisinlord.antarchy.content.client.model;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.portalgun.PortalGunBlackHoleEntity;
import net.minecraft.resources.ResourceLocation;
import software.bernie.geckolib.model.GeoModel;
public class PortalGunBlackHoleModel extends GeoModel<PortalGunBlackHoleEntity> {
private static final ResourceLocation TEXTURE = new ResourceLocation(Antarchy.MODID, "textures/vfx/portal_gun_black_hole.png");
@Override
public ResourceLocation getModelResource(PortalGunBlackHoleEntity animatable) {
return new ResourceLocation(Antarchy.MODID, "geo/portal_gun_portal.geo.json");
}
@Override
public ResourceLocation getTextureResource(PortalGunBlackHoleEntity animatable) {
return TEXTURE;
}
@Override
public ResourceLocation getAnimationResource(PortalGunBlackHoleEntity animatable) {
return new ResourceLocation(Antarchy.MODID, "animations/portal_gun_portal.animation.json");
}
}
@@ -0,0 +1,26 @@
package com.craisinlord.antarchy.content.client.model;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.portalgun.PortalGunPortalEntity;
import net.minecraft.resources.ResourceLocation;
import software.bernie.geckolib.model.GeoModel;
public class PortalGunPortalModel extends GeoModel<PortalGunPortalEntity> {
private static final ResourceLocation BLUE_TEXTURE = new ResourceLocation(Antarchy.MODID, "textures/vfx/portal_gun_portal_blue.png");
private static final ResourceLocation ORANGE_TEXTURE = new ResourceLocation(Antarchy.MODID, "textures/vfx/portal_gun_portal_orange.png");
@Override
public ResourceLocation getModelResource(PortalGunPortalEntity animatable) {
return new ResourceLocation(Antarchy.MODID, "geo/portal_gun_portal.geo.json");
}
@Override
public ResourceLocation getTextureResource(PortalGunPortalEntity animatable) {
return animatable.getPortalSide() == PortalGunPortalEntity.PortalSide.BLUE ? BLUE_TEXTURE : ORANGE_TEXTURE;
}
@Override
public ResourceLocation getAnimationResource(PortalGunPortalEntity animatable) {
return new ResourceLocation(Antarchy.MODID, "animations/portal_gun_portal.animation.json");
}
}
@@ -0,0 +1,23 @@
package com.craisinlord.antarchy.content.client.model;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.entity.royal.RoyalBlackHoleEntity;
import net.minecraft.resources.ResourceLocation;
import software.bernie.geckolib.model.GeoModel;
public class RoyalBlackHoleModel<T extends RoyalBlackHoleEntity> extends GeoModel<T> {
@Override
public ResourceLocation getModelResource(T animatable) {
return new ResourceLocation(Antarchy.MODID, "geo/black_hole.geo.json");
}
@Override
public ResourceLocation getTextureResource(T animatable) {
return new ResourceLocation(Antarchy.MODID, "textures/entity/queen/black_hole.png");
}
@Override
public ResourceLocation getAnimationResource(T animatable) {
return new ResourceLocation(Antarchy.MODID, "animations/black_hole.animation.json");
}
}
@@ -0,0 +1,24 @@
package com.craisinlord.antarchy.content.client.model;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.entity.royal.RoyalBossEntity;
import net.minecraft.resources.ResourceLocation;
import software.bernie.geckolib.model.GeoModel;
public class RoyalBossModel extends GeoModel<RoyalBossEntity> {
@Override
public ResourceLocation getModelResource(RoyalBossEntity animatable) {
return new ResourceLocation(Antarchy.MODID, "geo/" + animatable.geoNameForRender() + ".geo.json");
}
@Override
public ResourceLocation getTextureResource(RoyalBossEntity animatable) {
String name = animatable.geoNameForRender();
return new ResourceLocation(Antarchy.MODID, "textures/entity/" + name + "/" + name + ".png");
}
@Override
public ResourceLocation getAnimationResource(RoyalBossEntity animatable) {
return new ResourceLocation(Antarchy.MODID, "animations/" + animatable.geoNameForRender() + ".animation.json");
}
}
@@ -0,0 +1,26 @@
package com.craisinlord.antarchy.content.client.model;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.entity.royal.RoyalElementalProjectileEntity;
import net.minecraft.resources.ResourceLocation;
import software.bernie.geckolib.model.GeoModel;
public class RoyalElementalProjectileModel extends GeoModel<RoyalElementalProjectileEntity> {
@Override
public ResourceLocation getModelResource(RoyalElementalProjectileEntity animatable) {
return new ResourceLocation(Antarchy.MODID,
"geo/" + (animatable.isIceball() ? "ice_ball" : "fire_ball") + ".geo.json");
}
@Override
public ResourceLocation getTextureResource(RoyalElementalProjectileEntity animatable) {
return new ResourceLocation(Antarchy.MODID,
"textures/entity/king/" + (animatable.isIceball() ? "ice_ball" : "fire_ball") + ".png");
}
@Override
public ResourceLocation getAnimationResource(RoyalElementalProjectileEntity animatable) {
return new ResourceLocation(Antarchy.MODID,
"animations/" + (animatable.isIceball() ? "ice_ball" : "fire_ball") + ".animation.json");
}
}
@@ -0,0 +1,23 @@
package com.craisinlord.antarchy.content.client.model;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.entity.royal.RoyalIceSpikeEntity;
import net.minecraft.resources.ResourceLocation;
import software.bernie.geckolib.model.GeoModel;
public class RoyalIceSpikeModel extends GeoModel<RoyalIceSpikeEntity> {
@Override
public ResourceLocation getModelResource(RoyalIceSpikeEntity animatable) {
return new ResourceLocation(Antarchy.MODID, "geo/ice_spike.geo.json");
}
@Override
public ResourceLocation getTextureResource(RoyalIceSpikeEntity animatable) {
return new ResourceLocation(Antarchy.MODID, "textures/entity/king/ice_spike.png");
}
@Override
public ResourceLocation getAnimationResource(RoyalIceSpikeEntity animatable) {
return new ResourceLocation(Antarchy.MODID, "animations/ice_spike.animation.json");
}
}
@@ -0,0 +1,23 @@
package com.craisinlord.antarchy.content.client.model;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.entity.royal.RoyalMountEntity;
import net.minecraft.resources.ResourceLocation;
import software.bernie.geckolib.model.GeoModel;
public class RoyalMountModel extends GeoModel<RoyalMountEntity> {
@Override
public ResourceLocation getModelResource(RoyalMountEntity animatable) {
return new ResourceLocation(Antarchy.MODID, "geo/" + animatable.geoNameForRender() + ".geo.json");
}
@Override
public ResourceLocation getTextureResource(RoyalMountEntity animatable) {
return new ResourceLocation(Antarchy.MODID, "textures/entity/" + animatable.geoNameForRender() + ".png");
}
@Override
public ResourceLocation getAnimationResource(RoyalMountEntity animatable) {
return new ResourceLocation(Antarchy.MODID, "animations/" + animatable.geoNameForRender() + ".animation.json");
}
}
@@ -0,0 +1,23 @@
package com.craisinlord.antarchy.content.client.model;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.entity.vortex.VortexEntity;
import net.minecraft.resources.ResourceLocation;
import software.bernie.geckolib.model.GeoModel;
public class VortexModel extends GeoModel<VortexEntity> {
@Override
public ResourceLocation getModelResource(VortexEntity animatable) {
return new ResourceLocation(Antarchy.MODID, "geo/vortex.geo.json");
}
@Override
public ResourceLocation getTextureResource(VortexEntity animatable) {
return new ResourceLocation(Antarchy.MODID, "textures/entity/vortex.png");
}
@Override
public ResourceLocation getAnimationResource(VortexEntity animatable) {
return new ResourceLocation(Antarchy.MODID, "animations/vortex.animation.json");
}
}
@@ -0,0 +1,27 @@
package com.craisinlord.antarchy.content.client.model;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.entity.WormEntity;
import net.minecraft.resources.ResourceLocation;
import software.bernie.geckolib.model.GeoModel;
public class WormModel extends GeoModel<WormEntity> {
private static final ResourceLocation MODEL = new ResourceLocation(Antarchy.MODID, "geo/worm_large.geo.json");
private static final ResourceLocation TEXTURE = new ResourceLocation(Antarchy.MODID, "textures/entity/worm/worm_large.png");
private static final ResourceLocation ANIMATION = new ResourceLocation(Antarchy.MODID, "animations/worm_large.animation.json");
@Override
public ResourceLocation getModelResource(WormEntity animatable) {
return MODEL;
}
@Override
public ResourceLocation getTextureResource(WormEntity animatable) {
return TEXTURE;
}
@Override
public ResourceLocation getAnimationResource(WormEntity animatable) {
return ANIMATION;
}
}
@@ -0,0 +1,13 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.content.client.model.HoverboardModel;
import com.craisinlord.antarchy.content.entity.HoverboardEntity;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import software.bernie.geckolib.renderer.GeoEntityRenderer;
public class HoverboardRenderer extends GeoEntityRenderer<HoverboardEntity> {
public HoverboardRenderer(EntityRendererProvider.Context context) {
super(context, new HoverboardModel());
this.shadowRadius = 0.5F;
}
}
@@ -0,0 +1,13 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.content.client.model.ManticoreModel;
import com.craisinlord.antarchy.content.entity.ManticoreEntity;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import software.bernie.geckolib.renderer.GeoEntityRenderer;
public class ManticoreRenderer extends GeoEntityRenderer<ManticoreEntity> {
public ManticoreRenderer(EntityRendererProvider.Context context) {
super(context, new ManticoreModel());
this.shadowRadius = 0.9F;
}
}
@@ -0,0 +1,48 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.content.item.ManticoreWingsItem;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import net.minecraft.client.Minecraft;
import net.minecraft.client.model.ElytraModel;
import net.minecraft.client.model.PlayerModel;
import net.minecraft.client.model.geom.ModelLayers;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.entity.ItemRenderer;
import net.minecraft.client.renderer.entity.RenderLayerParent;
import net.minecraft.client.renderer.entity.layers.RenderLayer;
import net.minecraft.client.renderer.texture.OverlayTexture;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.item.ItemStack;
public final class ManticoreWingsLayer extends RenderLayer<net.minecraft.client.player.AbstractClientPlayer, PlayerModel<net.minecraft.client.player.AbstractClientPlayer>> {
private static final ResourceLocation TEXTURE = new ResourceLocation("antarchy", "textures/models/armor/manticore_wings.png");
private final ElytraModel<net.minecraft.client.player.AbstractClientPlayer> model;
public ManticoreWingsLayer(RenderLayerParent<net.minecraft.client.player.AbstractClientPlayer, PlayerModel<net.minecraft.client.player.AbstractClientPlayer>> renderer) {
super(renderer);
this.model = new ElytraModel<>(Minecraft.getInstance().getEntityModels().bakeLayer(ModelLayers.ELYTRA));
}
@Override
public void render(PoseStack poseStack, MultiBufferSource buffer, int packedLight,
net.minecraft.client.player.AbstractClientPlayer player, float limbSwing,
float limbSwingAmount, float partialTick, float ageInTicks,
float netHeadYaw, float headPitch) {
if (!ManticoreWingsItem.isWearingManticoreWings(player) || player.isInvisible()) {
return;
}
ItemStack chestStack = player.getItemBySlot(EquipmentSlot.CHEST);
poseStack.pushPose();
this.getParentModel().copyPropertiesTo(this.model);
if (player.isCrouching()) {
poseStack.translate(0.0F, 0.0F, 0.125F);
}
this.model.setupAnim(player, limbSwing, limbSwingAmount, ageInTicks, netHeadYaw, headPitch);
VertexConsumer consumer = ItemRenderer.getFoilBufferDirect(buffer, RenderType.armorCutoutNoCull(TEXTURE), false, chestStack.hasFoil());
this.model.renderToBuffer(poseStack, consumer, packedLight, OverlayTexture.NO_OVERLAY, 1.0F, 1.0F, 1.0F, 1.0F);
poseStack.popPose();
}
}
@@ -0,0 +1,28 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.content.client.model.PortalGunBlackHoleModel;
import com.craisinlord.antarchy.content.portalgun.PortalGunBlackHoleEntity;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.Nullable;
import software.bernie.geckolib.renderer.GeoEntityRenderer;
public class PortalGunBlackHoleRenderer extends GeoEntityRenderer<PortalGunBlackHoleEntity> {
public PortalGunBlackHoleRenderer(EntityRendererProvider.Context context) {
super(context, new PortalGunBlackHoleModel());
this.shadowRadius = 0.0F;
}
@Override
public void render(PortalGunBlackHoleEntity entity, float entityYaw, float partialTick, PoseStack poseStack, MultiBufferSource bufferSource, int packedLight) {
super.render(entity, entityYaw, partialTick, poseStack, bufferSource, 0xF000F0);
}
@Override
public @Nullable RenderType getRenderType(PortalGunBlackHoleEntity animatable, ResourceLocation texture, @Nullable MultiBufferSource bufferSource, float partialTick) {
return RenderType.entityTranslucent(this.getGeoModel().getTextureResource(animatable));
}
}
@@ -0,0 +1,65 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.content.client.model.PortalGunPortalModel;
import com.craisinlord.antarchy.content.portalgun.PortalGunPortalEntity;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.LightTexture;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.client.renderer.texture.OverlayTexture;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
import org.joml.Matrix4f;
import software.bernie.geckolib.renderer.GeoEntityRenderer;
public class PortalGunPortalRenderer extends GeoEntityRenderer<PortalGunPortalEntity> {
public PortalGunPortalRenderer(EntityRendererProvider.Context context) {
super(context, new PortalGunPortalModel());
this.shadowRadius = 0.0F;
}
@Override
public void render(PortalGunPortalEntity entity, float entityYaw, float partialTick, PoseStack poseStack, MultiBufferSource bufferSource, int packedLight) {
poseStack.pushPose();
VertexConsumer consumer = bufferSource.getBuffer(RenderType.entityTranslucent(this.getGeoModel().getTextureResource(entity)));
Matrix4f pose = poseStack.last().pose();
putPortalVertex(consumer, pose, entity, -0.5F, -1.0F, 0.0F, 1.0F, false);
putPortalVertex(consumer, pose, entity, -0.5F, 1.0F, 0.0F, 0.0F, false);
putPortalVertex(consumer, pose, entity, 0.5F, 1.0F, 1.0F, 0.0F, false);
putPortalVertex(consumer, pose, entity, 0.5F, -1.0F, 1.0F, 1.0F, false);
VertexConsumer edge = bufferSource.getBuffer(RenderType.entityTranslucent(this.getGeoModel().getTextureResource(entity)));
putPortalVertex(edge, pose, entity, -0.56F, -1.07F, 0.0F, 1.0F, true);
putPortalVertex(edge, pose, entity, -0.56F, 1.07F, 0.0F, 0.0F, true);
putPortalVertex(edge, pose, entity, 0.56F, 1.07F, 1.0F, 0.0F, true);
putPortalVertex(edge, pose, entity, 0.56F, -1.07F, 1.0F, 1.0F, true);
poseStack.popPose();
}
private static void putPortalVertex(VertexConsumer consumer, Matrix4f pose, PortalGunPortalEntity entity, float horizontal, float vertical, float u, float v, boolean channelTint) {
Vec3 position = entity.getWidthVec().normalize().scale(horizontal)
.add(entity.getUpVec().normalize().scale(vertical))
.add(entity.getNormalVec().normalize().scale(0.03125D));
int red = channelTint ? entity.getChannelRed() : 255;
int green = channelTint ? entity.getChannelGreen() : 255;
int blue = channelTint ? entity.getChannelBlue() : 255;
int alpha = channelTint ? 150 : 210;
consumer.vertex(pose, (float) position.x, (float) position.y, (float) position.z)
.color(red, green, blue, alpha)
.uv(u, v)
.overlayCoords(OverlayTexture.NO_OVERLAY)
.uv2(LightTexture.FULL_BRIGHT)
.normal((float) entity.getNormalVec().x, (float) entity.getNormalVec().y, (float) entity.getNormalVec().z)
.endVertex();
}
protected void applyRotations(PortalGunPortalEntity animatable, PoseStack poseStack, float ageInTicks, float rotationYaw, float partialTick, float nativeScale) {
}
@Override
public @Nullable RenderType getRenderType(PortalGunPortalEntity animatable, ResourceLocation texture, @Nullable MultiBufferSource bufferSource, float partialTick) {
return RenderType.entityTranslucent(this.getGeoModel().getTextureResource(animatable));
}
}
@@ -0,0 +1,18 @@
package com.craisinlord.antarchy.content.client.renderer;
import net.minecraft.client.Camera;
import org.joml.Matrix4f;
/** 1.20 hook for the optional recursive portal framebuffer path. */
public final class PortalGunPortalViewRenderer {
private PortalGunPortalViewRenderer() {
}
public static boolean isEnabled() {
return false;
}
public static void render(Camera camera, Matrix4f poseMatrix, float tickCounter) {
// Disabled on 1.20 until its LevelRenderer camera contract is ported.
}
}
@@ -0,0 +1,83 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.portalgun.PortalGunProjectileEntity;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.entity.EntityRenderer;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.client.renderer.texture.OverlayTexture;
import net.minecraft.core.BlockPos;
import net.minecraft.resources.ResourceLocation;
import org.joml.Matrix4f;
public class PortalGunProjectileRenderer extends EntityRenderer<PortalGunProjectileEntity> {
private static final ResourceLocation BLUE_TEXTURE = new ResourceLocation(Antarchy.MODID, "textures/vfx/portal_gun_portal_blue.png");
private static final ResourceLocation ORANGE_TEXTURE = new ResourceLocation(Antarchy.MODID, "textures/vfx/portal_gun_portal_orange.png");
public PortalGunProjectileRenderer(EntityRendererProvider.Context context) {
super(context);
}
@Override
public void render(PortalGunProjectileEntity entity, float entityYaw, float partialTick, PoseStack poseStack, MultiBufferSource bufferSource, int packedLight) {
poseStack.pushPose();
poseStack.mulPose(this.entityRenderDispatcher.cameraOrientation());
float age = entity.tickCount + partialTick;
float syncedDistance = entity.getSyncedDistance() + partialTick;
float pulse = 0.8F + 0.2F * (float) Math.sin(age * 0.9F);
float travelPulse = 0.82F + 0.18F * (float) Math.sin(syncedDistance * 0.55F);
float trailStretch = 1.1F + (float) Math.min(entity.getSyncedVelocity().length() * 0.42D, 1.8D);
float baseScale = entity.getPortalSide() == com.craisinlord.antarchy.content.portalgun.PortalGunPortalEntity.PortalSide.BLUE ? 0.44F : 0.48F;
ResourceLocation texture = entity.getPortalSide() == com.craisinlord.antarchy.content.portalgun.PortalGunPortalEntity.PortalSide.BLUE ? BLUE_TEXTURE : ORANGE_TEXTURE;
float red = entity.getPortalSide() == com.craisinlord.antarchy.content.portalgun.PortalGunPortalEntity.PortalSide.BLUE ? 0.55F : 1.0F;
float green = entity.getPortalSide() == com.craisinlord.antarchy.content.portalgun.PortalGunPortalEntity.PortalSide.BLUE ? 0.82F : 0.56F;
float blue = entity.getPortalSide() == com.craisinlord.antarchy.content.portalgun.PortalGunPortalEntity.PortalSide.BLUE ? 1.0F : 0.18F;
poseStack.scale(baseScale * trailStretch, baseScale * travelPulse, baseScale * travelPulse);
PoseStack.Pose pose = poseStack.last();
Matrix4f matrix = pose.pose();
VertexConsumer core = bufferSource.getBuffer(RenderType.entityTranslucent(texture));
addQuad(core, pose, matrix, red, green, blue, 0.92F, packedLight, -0.9F, -0.5F, 0.9F, 0.5F);
VertexConsumer glow = bufferSource.getBuffer(RenderType.entityTranslucentEmissive(texture));
addQuad(glow, pose, matrix, 1.0F, 1.0F, 1.0F, 0.8F * pulse, 0xF000F0, -1.15F, -0.7F, 1.15F, 0.7F);
poseStack.mulPose(com.mojang.math.Axis.ZP.rotationDegrees(90.0F));
pose = poseStack.last();
matrix = pose.pose();
core = bufferSource.getBuffer(RenderType.entityTranslucent(texture));
addQuad(core, pose, matrix, red, green, blue, 0.74F, packedLight, -0.45F, -0.95F, 0.45F, 0.95F);
glow = bufferSource.getBuffer(RenderType.entityTranslucentEmissive(texture));
addQuad(glow, pose, matrix, 1.0F, 1.0F, 1.0F, 0.52F * pulse, 0xF000F0, -0.62F, -1.15F, 0.62F, 1.15F);
poseStack.popPose();
}
@Override
protected int getBlockLightLevel(PortalGunProjectileEntity entity, BlockPos pos) {
return 15;
}
@Override
public ResourceLocation getTextureLocation(PortalGunProjectileEntity entity) {
return entity.getPortalSide() == com.craisinlord.antarchy.content.portalgun.PortalGunPortalEntity.PortalSide.BLUE ? BLUE_TEXTURE : ORANGE_TEXTURE;
}
private static void addQuad(VertexConsumer consumer, PoseStack.Pose pose, Matrix4f matrix, float red, float green, float blue, float alpha, int packedLight, float minX, float minY, float maxX, float maxY) {
addVertex(consumer, pose, matrix, minX, minY, 0.0F, 1.0F, red, green, blue, alpha, packedLight);
addVertex(consumer, pose, matrix, maxX, minY, 1.0F, 1.0F, red, green, blue, alpha, packedLight);
addVertex(consumer, pose, matrix, maxX, maxY, 1.0F, 0.0F, red, green, blue, alpha, packedLight);
addVertex(consumer, pose, matrix, minX, maxY, 0.0F, 0.0F, red, green, blue, alpha, packedLight);
}
private static void addVertex(VertexConsumer consumer, PoseStack.Pose pose, Matrix4f matrix, float x, float y, float u, float v, float red, float green, float blue, float alpha, int packedLight) {
consumer.vertex(matrix, x, y, 0.0F)
.color((int) (red * 255.0F), (int) (green * 255.0F), (int) (blue * 255.0F), (int) (alpha * 255.0F))
.uv(u, v)
.overlayCoords(OverlayTexture.NO_OVERLAY)
.uv2(packedLight)
.normal(pose.normal(), 0.0F, 0.0F, 1.0F)
.endVertex();
}
}
@@ -0,0 +1,63 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.content.item.RoyalAssailantBattleAxeItem;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import com.mojang.math.Axis;
import net.minecraft.client.Minecraft;
import net.minecraft.client.model.PlayerModel;
import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraft.client.renderer.LightTexture;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.entity.LivingEntityRenderer;
import net.minecraft.client.renderer.entity.RenderLayerParent;
import net.minecraft.client.renderer.entity.layers.RenderLayer;
import net.minecraft.world.entity.HumanoidArm;
import net.minecraft.world.item.ItemDisplayContext;
import net.minecraft.world.item.ItemStack;
public final class RoyalAssailantAxeAfterimageLayer extends RenderLayer<AbstractClientPlayer, PlayerModel<AbstractClientPlayer>> {
private static final int[] TINTS = {0x66FF3B4D, 0x4DFF263D, 0x33E8142F, 0x1FCC0D26};
public RoyalAssailantAxeAfterimageLayer(RenderLayerParent<AbstractClientPlayer, PlayerModel<AbstractClientPlayer>> renderer) {
super(renderer);
}
@Override
public void render(PoseStack poseStack, MultiBufferSource buffer, int packedLight, AbstractClientPlayer player,
float limbSwing, float limbSwingAmount, float partialTick, float ageInTicks,
float netHeadYaw, float headPitch) {
if (player.isSpectator() || player.isInvisible() || player.getAttackAnim(partialTick) <= 0.01F) return;
ItemStack stack = player.getMainHandItem();
HumanoidArm arm = player.getMainArm();
if (!(stack.getItem() instanceof RoyalAssailantBattleAxeItem)) {
stack = player.getOffhandItem();
arm = player.getMainArm() == HumanoidArm.RIGHT ? HumanoidArm.LEFT : HumanoidArm.RIGHT;
}
if (!(stack.getItem() instanceof RoyalAssailantBattleAxeItem)) return;
VertexConsumer vertexConsumer = buffer.getBuffer(RenderType.entityTranslucent(this.getTextureLocation(player)));
float swing = player.getAttackAnim(partialTick);
for (int i = TINTS.length - 1; i >= 0; i--) {
poseStack.pushPose();
poseStack.translate(0.0D, 0.0D, (i + 1) * 0.08F);
poseStack.mulPose(Axis.YP.rotation((i + 1) * 0.12F * (swing < 0.5F ? -1.0F : 1.0F)));
int tint = TINTS[i];
this.getParentModel().renderToBuffer(poseStack, vertexConsumer, LightTexture.FULL_BRIGHT,
LivingEntityRenderer.getOverlayCoords(player, 0.0F),
((tint >> 16) & 0xFF) / 255.0F, ((tint >> 8) & 0xFF) / 255.0F,
(tint & 0xFF) / 255.0F, ((tint >>> 24) & 0xFF) / 255.0F);
poseStack.popPose();
}
for (int i = TINTS.length - 1; i >= 0; i--) {
poseStack.pushPose();
this.getParentModel().translateToHand(arm, poseStack);
poseStack.mulPose(Axis.YP.rotation((i + 1) * 0.12F * (swing < 0.5F ? -1.0F : 1.0F)));
Minecraft.getInstance().getItemRenderer().renderStatic(player, stack,
arm == HumanoidArm.RIGHT ? ItemDisplayContext.THIRD_PERSON_RIGHT_HAND : ItemDisplayContext.THIRD_PERSON_LEFT_HAND,
arm == HumanoidArm.LEFT, poseStack, buffer, player.level(), packedLight,
LivingEntityRenderer.getOverlayCoords(player, 0.0F), player.getId());
poseStack.popPose();
}
}
}
@@ -0,0 +1,30 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.content.client.model.RoyalBlackHoleModel;
import com.craisinlord.antarchy.content.entity.royal.RoyalAssailantBlackHoleEntity;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.Nullable;
import software.bernie.geckolib.renderer.GeoEntityRenderer;
public final class RoyalAssailantBlackHoleRenderer extends GeoEntityRenderer<RoyalAssailantBlackHoleEntity> {
public RoyalAssailantBlackHoleRenderer(EntityRendererProvider.Context context) {
super(context, new RoyalBlackHoleModel<>());
this.shadowRadius = 0.0F;
}
@Override
public void render(RoyalAssailantBlackHoleEntity entity, float entityYaw, float partialTick,
PoseStack poseStack, MultiBufferSource bufferSource, int packedLight) {
super.render(entity, entityYaw, partialTick, poseStack, bufferSource, 0xF000F0);
}
@Override
public @Nullable RenderType getRenderType(RoyalAssailantBlackHoleEntity animatable, ResourceLocation texture,
@Nullable MultiBufferSource bufferSource, float partialTick) {
return RenderType.entityTranslucent(this.getGeoModel().getTextureResource(animatable));
}
}
@@ -0,0 +1,30 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.content.client.model.RoyalBlackHoleModel;
import com.craisinlord.antarchy.content.entity.royal.RoyalBlackHoleEntity;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.Nullable;
import software.bernie.geckolib.renderer.GeoEntityRenderer;
public class RoyalBlackHoleRenderer extends GeoEntityRenderer<RoyalBlackHoleEntity> {
public RoyalBlackHoleRenderer(EntityRendererProvider.Context context) {
super(context, new RoyalBlackHoleModel<>());
this.shadowRadius = 0.0F;
}
@Override
public void render(RoyalBlackHoleEntity entity, float entityYaw, float partialTick, PoseStack poseStack,
MultiBufferSource bufferSource, int packedLight) {
super.render(entity, entityYaw, partialTick, poseStack, bufferSource, 0xF000F0);
}
@Override
public @Nullable RenderType getRenderType(RoyalBlackHoleEntity animatable, ResourceLocation texture,
@Nullable MultiBufferSource bufferSource, float partialTick) {
return RenderType.entityTranslucent(this.getGeoModel().getTextureResource(animatable));
}
}
@@ -0,0 +1,19 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.content.entity.royal.RoyalBoltEntity;
import net.minecraft.client.renderer.entity.EntityRenderer;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.resources.ResourceLocation;
public class RoyalBoltRenderer extends EntityRenderer<RoyalBoltEntity> {
private static final ResourceLocation TEXTURE = new ResourceLocation("minecraft", "textures/particle/generic_0.png");
public RoyalBoltRenderer(EntityRendererProvider.Context context) {
super(context);
}
@Override
public ResourceLocation getTextureLocation(RoyalBoltEntity entity) {
return TEXTURE;
}
}
@@ -0,0 +1,176 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.content.client.model.RoyalBossModel;
import com.craisinlord.antarchy.content.entity.royal.RoyalBossEntity;
import com.craisinlord.antarchy.content.entity.royal.KingEntity;
import com.craisinlord.antarchy.content.entity.royal.beam.RoyalBeamElement;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.Mth;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
import software.bernie.geckolib.cache.object.BakedGeoModel;
import software.bernie.geckolib.renderer.GeoEntityRenderer;
import software.bernie.geckolib.core.object.Color;
public class RoyalBossRenderer extends GeoEntityRenderer<RoyalBossEntity> {
private static final ResourceLocation QUEEN_BEAM_OUTER = new ResourceLocation("antarchy", "textures/entity/queen/queen_beam_outer.png");
private static final ResourceLocation QUEEN_BEAM_INNER = new ResourceLocation("antarchy", "textures/entity/queen/queen_beam_inner.png");
private static final ResourceLocation QUEEN_BEAM_END = new ResourceLocation("antarchy", "textures/entity/queen/queen_beam_end_1.png");
private static final ResourceLocation KING_FIRE_BEAM_OUTER = new ResourceLocation("antarchy", "textures/entity/king/fire_beam_outer.png");
private static final ResourceLocation KING_FIRE_BEAM_INNER = new ResourceLocation("antarchy", "textures/entity/king/fire_beam_inner.png");
private static final ResourceLocation KING_FIRE_BEAM_END = new ResourceLocation("antarchy", "textures/entity/king/fire_beam_end_1.png");
private static final ResourceLocation KING_LIGHTNING_BEAM_OUTER = new ResourceLocation("antarchy", "textures/entity/king/lightning_beam_outer.png");
private static final ResourceLocation KING_LIGHTNING_BEAM_INNER = new ResourceLocation("antarchy", "textures/entity/king/lightning_beam_inner.png");
private static final ResourceLocation KING_LIGHTNING_BEAM_END = new ResourceLocation("antarchy", "textures/entity/king/lightning_beam_end_1.png");
private static final ResourceLocation KING_ICE_BEAM_OUTER = new ResourceLocation("antarchy", "textures/entity/king/ice_beam_outer.png");
private static final ResourceLocation KING_ICE_BEAM_INNER = new ResourceLocation("antarchy", "textures/entity/king/ice_beam_inner.png");
private static final ResourceLocation KING_ICE_BEAM_END = new ResourceLocation("antarchy", "textures/entity/king/ice_beam_end_1.png");
public RoyalBossRenderer(EntityRendererProvider.Context context) {
super(context, new RoyalBossModel());
this.shadowRadius = 8.0F;
}
@Override
public RenderType getRenderType(RoyalBossEntity animatable, ResourceLocation texture, @Nullable MultiBufferSource bufferSource, float partialTick) {
return RenderType.entityCutoutNoCull(texture);
}
@Override
public Color getRenderColor(RoyalBossEntity animatable, float partialTick, int packedLight) {
if (!animatable.isRoyalAccelerated()) {
return Color.ofRGBA(255, 255, 255, 255);
}
float pulse = 0.5F + 0.5F * (float) Math.sin((animatable.tickCount + partialTick) * 0.4F);
int green = 60 + Math.round(60.0F * pulse);
return Color.ofRGBA(255, green, green, 255);
}
@Override
public void preRender(PoseStack poseStack, RoyalBossEntity animatable, BakedGeoModel model, @Nullable MultiBufferSource bufferSource,
@Nullable VertexConsumer buffer, boolean isReRender, float partialTick, int packedLight, int packedOverlay,
float red, float green, float blue, float alpha) {
super.preRender(poseStack, animatable, model, bufferSource, buffer, isReRender, partialTick, packedLight, packedOverlay, red, green, blue, alpha);
poseStack.scale(RoyalBossEntity.MODEL_RENDER_SCALE, RoyalBossEntity.MODEL_RENDER_SCALE, RoyalBossEntity.MODEL_RENDER_SCALE);
this.shadowRadius = 8.0F * RoyalBossEntity.MODEL_RENDER_SCALE;
}
private static final float BEAM_OUTER_RADIUS = 0.55F;
private static final float BEAM_INNER_RADIUS = 0.32F;
private static final float BEAM_TILE_BLOCKS = 3.0F;
private static final float BEAM_SCROLL_SPEED = 0.32F;
@Override
public void render(RoyalBossEntity entity, float entityYaw, float partialTick, PoseStack poseStack,
MultiBufferSource bufferSource, int packedLight) {
super.render(entity, entityYaw, partialTick, poseStack, bufferSource, packedLight);
Vec3 end = entity.getRoyalBeamEndPosition();
if (!entity.isFiringRoyalBeam() || end == null) {
return;
}
double x = Mth.lerp(partialTick, entity.xo, entity.getX());
double y = Mth.lerp(partialTick, entity.yo, entity.getY());
double z = Mth.lerp(partialTick, entity.zo, entity.getZ());
Vec3 start = entity.getRoyalBeamShootFrom(partialTick).subtract(x, y, z);
Vec3 finish = end.subtract(x, y, z);
Vec3 axis = finish.subtract(start);
double length = axis.length();
if (length < 0.05D) {
return;
}
Vec3 dir = axis.scale(1.0D / length);
Vec3 side1 = dir.cross(new Vec3(0.0D, 1.0D, 0.0D));
if (side1.lengthSqr() < 1.0E-4D) {
side1 = dir.cross(new Vec3(1.0D, 0.0D, 0.0D));
}
side1 = side1.normalize();
Vec3 side2 = dir.cross(side1).normalize();
ResourceLocation outer = QUEEN_BEAM_OUTER;
ResourceLocation inner = QUEEN_BEAM_INNER;
ResourceLocation endTexture = QUEEN_BEAM_END;
if (entity instanceof KingEntity) {
switch (entity.getRoyalBeamElement()) {
case FIRE -> {
outer = KING_FIRE_BEAM_OUTER;
inner = KING_FIRE_BEAM_INNER;
endTexture = KING_FIRE_BEAM_END;
}
case LIGHTNING -> {
outer = KING_LIGHTNING_BEAM_OUTER;
inner = KING_LIGHTNING_BEAM_INNER;
endTexture = KING_LIGHTNING_BEAM_END;
}
case ICE -> {
outer = KING_ICE_BEAM_OUTER;
inner = KING_ICE_BEAM_INNER;
endTexture = KING_ICE_BEAM_END;
}
case GENERIC -> {
}
}
}
float time = entity.tickCount + partialTick;
float tiles = (float) (length / BEAM_TILE_BLOCKS);
float scroll = -time * BEAM_SCROLL_SPEED;
var pose = poseStack.last().pose();
VertexConsumer outerBuffer = bufferSource.getBuffer(RenderType.entityTranslucent(outer));
drawBeamPlane(outerBuffer, pose, start, finish, side1.scale(BEAM_OUTER_RADIUS), scroll, scroll + tiles);
drawBeamPlane(outerBuffer, pose, start, finish, side2.scale(BEAM_OUTER_RADIUS), scroll, scroll + tiles);
float innerScroll = -time * BEAM_SCROLL_SPEED * 1.6F;
VertexConsumer innerBuffer = bufferSource.getBuffer(RenderType.entityTranslucent(inner));
drawBeamPlane(innerBuffer, pose, start, finish, side1.scale(BEAM_INNER_RADIUS), innerScroll, innerScroll + tiles);
drawBeamPlane(innerBuffer, pose, start, finish, side2.scale(BEAM_INNER_RADIUS), innerScroll, innerScroll + tiles);
float impactSize = 1.1F + 0.35F * Mth.sin(time * 0.6F);
Vec3 camLeft = new Vec3(this.entityRenderDispatcher.camera.getLeftVector());
Vec3 camUp = new Vec3(this.entityRenderDispatcher.camera.getUpVector());
VertexConsumer endBuffer = bufferSource.getBuffer(RenderType.entityTranslucent(endTexture));
drawBillboard(endBuffer, pose, finish, camLeft.scale(impactSize), camUp.scale(impactSize));
}
private static void drawBeamPlane(VertexConsumer vertices, org.joml.Matrix4f pose, Vec3 start, Vec3 end, Vec3 half,
float startV, float endV) {
emitQuad(vertices, pose,
start.subtract(half), start.add(half), end.add(half), end.subtract(half),
0.0F, startV, 1.0F, endV);
emitQuad(vertices, pose,
start.add(half), start.subtract(half), end.subtract(half), end.add(half),
0.0F, startV, 1.0F, endV);
}
private static void drawBillboard(VertexConsumer vertices, org.joml.Matrix4f pose, Vec3 center, Vec3 left, Vec3 up) {
emitQuad(vertices, pose,
center.add(left).add(up), center.subtract(left).add(up),
center.subtract(left).subtract(up), center.add(left).subtract(up),
0.0F, 0.0F, 1.0F, 1.0F);
emitQuad(vertices, pose,
center.subtract(left).add(up), center.add(left).add(up),
center.add(left).subtract(up), center.subtract(left).subtract(up),
0.0F, 0.0F, 1.0F, 1.0F);
}
private static void emitQuad(VertexConsumer vertices, org.joml.Matrix4f pose,
Vec3 a, Vec3 b, Vec3 c, Vec3 d,
float minU, float minV, float maxU, float maxV) {
vertex(vertices, pose, a, minU, minV);
vertex(vertices, pose, b, maxU, minV);
vertex(vertices, pose, c, maxU, maxV);
vertex(vertices, pose, d, minU, maxV);
}
private static void vertex(VertexConsumer vertices, org.joml.Matrix4f pose, Vec3 pos, float u, float v) {
vertices.vertex(pose, (float) pos.x, (float) pos.y, (float) pos.z)
.color(255, 255, 255, 255).uv(u, v)
.overlayCoords(net.minecraft.client.renderer.texture.OverlayTexture.NO_OVERLAY)
.uv2(0x00F000F0)
.normal(0.0F, 1.0F, 0.0F).endVertex();
}
}
@@ -0,0 +1,37 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.content.client.model.RoyalElementalProjectileModel;
import com.craisinlord.antarchy.content.entity.royal.RoyalElementalProjectileEntity;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import com.mojang.math.Axis;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.util.Mth;
import software.bernie.geckolib.cache.object.BakedGeoModel;
import software.bernie.geckolib.renderer.GeoEntityRenderer;
public class RoyalElementalProjectileRenderer extends GeoEntityRenderer<RoyalElementalProjectileEntity> {
public RoyalElementalProjectileRenderer(EntityRendererProvider.Context context) {
super(context, new RoyalElementalProjectileModel());
this.shadowRadius = 0.0F;
}
@Override
public void preRender(PoseStack poseStack, RoyalElementalProjectileEntity animatable, BakedGeoModel model,
MultiBufferSource bufferSource, VertexConsumer buffer, boolean isReRender,
float partialTick, int packedLight, int packedOverlay, float red, float green, float blue, float alpha) {
super.preRender(poseStack, animatable, model, bufferSource, buffer, isReRender, partialTick,
packedLight, packedOverlay, red, green, blue, alpha);
float scale = animatable.isIceball() ? 0.75F : 0.36F;
poseStack.scale(scale, scale, scale);
}
protected void applyRotations(RoyalElementalProjectileEntity entity, PoseStack poseStack, float ageInTicks,
float rotationYaw, float partialTick, float nativeScale) {
float yaw = Mth.rotLerp(partialTick, entity.yRotO, entity.getYRot());
float pitch = Mth.lerp(partialTick, entity.xRotO, entity.getXRot());
poseStack.mulPose(Axis.YP.rotationDegrees(180.0F - yaw));
poseStack.mulPose(Axis.XP.rotationDegrees(-pitch));
}
}
@@ -0,0 +1,14 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.content.client.model.RoyalIceSpikeModel;
import com.craisinlord.antarchy.content.entity.royal.RoyalIceSpikeEntity;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import software.bernie.geckolib.renderer.GeoEntityRenderer;
public class RoyalIceSpikeRenderer extends GeoEntityRenderer<RoyalIceSpikeEntity> {
public RoyalIceSpikeRenderer(EntityRendererProvider.Context context) {
super(context, new RoyalIceSpikeModel());
this.shadowRadius = 0.0F;
}
}
@@ -0,0 +1,35 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.content.client.model.RoyalMountModel;
import com.craisinlord.antarchy.content.entity.royal.RoyalMountEntity;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.Nullable;
import software.bernie.geckolib.cache.object.BakedGeoModel;
import software.bernie.geckolib.renderer.GeoEntityRenderer;
public class RoyalMountRenderer extends GeoEntityRenderer<RoyalMountEntity> {
public RoyalMountRenderer(EntityRendererProvider.Context context) {
super(context, new RoyalMountModel());
this.shadowRadius = 1.1F;
}
@Override
public RenderType getRenderType(RoyalMountEntity animatable, ResourceLocation texture, @Nullable MultiBufferSource bufferSource, float partialTick) {
return RenderType.entityCutoutNoCull(texture);
}
@Override
public void preRender(PoseStack poseStack, RoyalMountEntity animatable, BakedGeoModel model, @Nullable MultiBufferSource bufferSource,
@Nullable VertexConsumer buffer, boolean isReRender, float partialTick, int packedLight, int packedOverlay,
float red, float green, float blue, float alpha) {
super.preRender(poseStack, animatable, model, bufferSource, buffer, isReRender, partialTick, packedLight, packedOverlay, red, green, blue, alpha);
float scale = animatable.getAgeScale();
poseStack.scale(scale, scale, scale);
this.shadowRadius = 1.1F * scale;
}
}
@@ -0,0 +1,60 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.content.client.ContractionAfterimages;
import com.craisinlord.antarchy.content.effect.RoyalEffectHooks;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import java.util.Deque;
import net.minecraft.client.model.EntityModel;
import net.minecraft.client.renderer.LightTexture;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.entity.LivingEntityRenderer;
import net.minecraft.client.renderer.entity.RenderLayerParent;
import net.minecraft.client.renderer.entity.layers.RenderLayer;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.LivingEntity;
import org.joml.Matrix4f;
import org.joml.Vector3f;
public final class TemporalContractionAfterimageLayer<T extends LivingEntity, M extends EntityModel<T>> extends RenderLayer<T, M> {
private static final int RGB = 0x8FE6B4;
private static final float BASE_ALPHA = 0.42F;
public TemporalContractionAfterimageLayer(RenderLayerParent<T, M> renderer) { super(renderer); }
@Override
public void render(PoseStack poseStack, MultiBufferSource buffer, int packedLight, T entity,
float limbSwing, float limbSwingAmount, float partialTick, float ageInTicks,
float netHeadYaw, float headPitch) {
if (entity.isSpectator() || entity.isInvisible() || !entity.hasEffect(RoyalEffectHooks.contractedHolder())) return;
Deque<ContractionAfterimages.Sample> samples = ContractionAfterimages.samples(entity);
if (samples == null || samples.isEmpty()) return;
double curX = Mth.lerp(partialTick, entity.xo, entity.getX());
double curY = Mth.lerp(partialTick, entity.yo, entity.getY());
double curZ = Mth.lerp(partialTick, entity.zo, entity.getZ());
int now = (int) (entity.level().getGameTime() & 0x7fffffffL);
Matrix4f inverse = new Matrix4f(poseStack.last().pose()).invert();
M model = this.getParentModel();
VertexConsumer consumer = buffer.getBuffer(RenderType.entityTranslucent(this.getTextureLocation(entity)));
int index = 0;
for (ContractionAfterimages.Sample sample : samples) {
if (index++ % 2 != 0) continue;
double dx = sample.x - curX, dy = sample.y - curY, dz = sample.z - curZ;
if (dx * dx + dy * dy + dz * dz < 0.0025D) continue;
float fade = ContractionAfterimages.fade(sample, now);
if (fade <= 0.02F) continue;
Vector3f local = inverse.transformDirection(new Vector3f((float) dx, (float) dy, (float) dz));
int color = ((int) (BASE_ALPHA * fade * 255.0F) & 0xFF) << 24 | RGB;
model.setupAnim(entity, sample.limbPos, sample.limbSpeed, sample.ageInTicks,
sample.headYaw - sample.bodyYaw, sample.xRot);
poseStack.pushPose(); poseStack.translate(local.x, local.y, local.z);
model.renderToBuffer(poseStack, consumer, LightTexture.FULL_BRIGHT,
LivingEntityRenderer.getOverlayCoords(entity, 0.0F),
((color >> 16) & 0xFF) / 255.0F, ((color >> 8) & 0xFF) / 255.0F,
(color & 0xFF) / 255.0F, ((color >>> 24) & 0xFF) / 255.0F);
poseStack.popPose();
}
model.setupAnim(entity, limbSwing, limbSwingAmount, ageInTicks, netHeadYaw, headPitch);
}
}
@@ -0,0 +1,56 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.content.client.ContractionAfterimages;
import com.craisinlord.antarchy.content.effect.RoyalEffectHooks;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import java.util.Deque;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.jetbrains.annotations.Nullable;
import software.bernie.geckolib.core.animatable.GeoAnimatable;
import software.bernie.geckolib.cache.object.BakedGeoModel;
import software.bernie.geckolib.renderer.GeoRenderer;
import software.bernie.geckolib.renderer.layer.GeoRenderLayer;
public final class TemporalContractionGeoLayer<T extends GeoAnimatable> extends GeoRenderLayer<T> {
public TemporalContractionGeoLayer(GeoRenderer<T> renderer) { super(renderer); }
@Override
public void render(PoseStack poseStack, T animatable, BakedGeoModel bakedModel, @Nullable RenderType renderType,
MultiBufferSource bufferSource, @Nullable VertexConsumer buffer, float partialTick,
int packedLight, int packedOverlay) {
if (!(animatable instanceof Entity entity) || !(entity instanceof LivingEntity living)
|| living.isInvisible() || !living.hasEffect(RoyalEffectHooks.contractedHolder())) return;
Deque<ContractionAfterimages.Sample> samples = ContractionAfterimages.samples(living);
if (samples == null || samples.isEmpty()) return;
double curX = Mth.lerp(partialTick, entity.xo, entity.getX());
double curY = Mth.lerp(partialTick, entity.yo, entity.getY());
double curZ = Mth.lerp(partialTick, entity.zo, entity.getZ());
int now = (int) (entity.level().getGameTime() & 0x7fffffffL);
Matrix4f inverse = new Matrix4f(poseStack.last().pose()).invert();
RenderType type = RenderType.entityTranslucent(this.getRenderer().getTextureLocation(animatable));
VertexConsumer consumer = bufferSource.getBuffer(type);
int index = 0;
for (ContractionAfterimages.Sample sample : samples) {
if (index++ % 2 != 0) continue;
double dx = sample.x - curX, dy = sample.y - curY, dz = sample.z - curZ;
if (dx * dx + dy * dy + dz * dz < 0.0025D) continue;
float fade = ContractionAfterimages.fade(sample, now);
if (fade <= 0.02F) continue;
Vector3f local = inverse.transformDirection(new Vector3f((float) dx, (float) dy, (float) dz));
int color = ((int) (0.42F * fade * 255.0F) & 0xFF) << 24 | 0x8FE6B4;
poseStack.pushPose(); poseStack.translate(local.x, local.y, local.z);
this.getRenderer().reRender(bakedModel, poseStack, bufferSource, animatable, type, consumer,
partialTick, 0xF000F0, packedOverlay,
((color >> 16) & 0xFF) / 255.0F, ((color >> 8) & 0xFF) / 255.0F,
(color & 0xFF) / 255.0F, ((color >>> 24) & 0xFF) / 255.0F);
poseStack.popPose();
}
}
}
@@ -0,0 +1,259 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.worldgen.thoraxis.ThoraxisUndersideManager;
import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.BufferBuilder;
import com.mojang.blaze3d.vertex.BufferUploader;
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.Tesselator;
import com.mojang.blaze3d.vertex.VertexBuffer;
import com.mojang.blaze3d.vertex.VertexFormat;
import com.mojang.blaze3d.vertex.VertexSorting;
import java.util.Random;
import net.minecraft.client.Camera;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.Mth;
import com.mojang.math.Axis;
import net.minecraft.world.level.Level;
import org.joml.Matrix4f;
public final class ThoraxisUndersideSkyRenderer {
public static final ResourceKey<Level> THORAXIS = ResourceKey.create(
Registries.DIMENSION,
new ResourceLocation(Antarchy.MODID, "thoraxis")
);
private static final ResourceLocation SUN_TEXTURE = new ResourceLocation("minecraft", "textures/environment/sun.png");
private static final ResourceLocation MOON_TEXTURE = new ResourceLocation("minecraft", "textures/environment/moon_phases.png");
private static final float SKY_RADIUS = 100.0F;
private static final float BODY_SIZE = 32.0F;
private static final float SKY_RED = 0.015F;
private static final float SKY_GREEN = 0.0F;
private static final float SKY_BLUE = 0.035F;
@org.jetbrains.annotations.Nullable
private static VertexBuffer redStarBuffer = null;
private ThoraxisUndersideSkyRenderer() {
}
public static boolean shouldRender(ClientLevel level, Camera camera) {
return level.dimension().equals(THORAXIS) && camera.getPosition().y < ThoraxisUndersideManager.GRAVITY_FLIP_Y;
}
public static void render(
Minecraft minecraft,
PoseStack poseStack,
Matrix4f projectionMatrix,
float partialTick,
Camera camera,
boolean isFoggy,
Runnable setupFog,
VertexBuffer skyBuffer
) {
setupFog.run();
if (minecraft.options.hideLightningFlash().get() || minecraft.gameRenderer.getMainCamera() != camera) {
return;
}
RenderSystem.depthMask(false);
RenderSystem.setShaderColor(SKY_RED, SKY_GREEN, SKY_BLUE, 1.0F);
skyBuffer.bind();
skyBuffer.drawWithShader(poseStack.last().pose(), projectionMatrix, GameRenderer.getPositionShader());
VertexBuffer.unbind();
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
float timeOfDay = 0.72F + partialTick * 0.0F;
renderCelestialBodies(poseStack.last().pose(), projectionMatrix, timeOfDay);
renderRedStars(poseStack.last().pose(), projectionMatrix);
RenderSystem.depthMask(true);
}
private static void renderRedStars(Matrix4f modelViewMatrix, Matrix4f projectionMatrix) {
if (redStarBuffer == null) {
redStarBuffer = buildRedStarBuffer();
}
RenderSystem.enableBlend();
RenderSystem.defaultBlendFunc();
RenderSystem.setShaderColor(0.95F, 0.95F, 0.95F, 0.95F);
redStarBuffer.bind();
redStarBuffer.drawWithShader(modelViewMatrix, projectionMatrix, GameRenderer.getPositionColorShader());
VertexBuffer.unbind();
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
RenderSystem.disableBlend();
}
private static VertexBuffer buildRedStarBuffer() {
Random random = new Random(552031L);
BufferBuilder builder = Tesselator.getInstance().getBuilder();
builder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR);
for (int i = 0; i < 1800; i++) {
double x = random.nextFloat() * 2.0F - 1.0F;
double y = random.nextFloat() * 2.0F - 1.0F;
double z = random.nextFloat() * 2.0F - 1.0F;
double size = 0.13F + random.nextFloat() * 0.12F;
double lenSq = x * x + y * y + z * z;
if (lenSq < 1.0D && lenSq > 0.01D) {
double len = 1.0D / Math.sqrt(lenSq);
x *= len;
y *= len;
z *= len;
double sx = x * 100.0D;
double sy = y * 100.0D;
double sz = z * 100.0D;
double yaw = Math.atan2(x, z);
double sinYaw = Math.sin(yaw);
double cosYaw = Math.cos(yaw);
double pitch = Math.atan2(Math.sqrt(x * x + z * z), y);
double sinPitch = Math.sin(pitch);
double cosPitch = Math.cos(pitch);
double rot = random.nextDouble() * Math.PI * 2.0D;
double sinRot = Math.sin(rot);
double cosRot = Math.cos(rot);
float red = 0.75F + random.nextFloat() * 0.25F;
float green = 0.02F + random.nextFloat() * 0.05F;
float blue = 0.04F + random.nextFloat() * 0.08F;
for (int j = 0; j < 4; j++) {
double u = (j & 2) - 1;
double v = (j + 1 & 2) - 1;
double px = u * cosRot - v * sinRot;
double py = v * cosRot + u * sinRot;
double qx = px * sinPitch;
double qy = -px * cosPitch;
double rx = qy * sinYaw - py * cosYaw;
double ry = py * sinYaw + qy * cosYaw;
builder.vertex((float) (sx + rx * size), (float) (sy + qx * size), (float) (sz + ry * size))
.color(red, green, blue, 1.0F).endVertex();
}
}
}
BufferBuilder.RenderedBuffer mesh = builder.end();
VertexBuffer buffer = new VertexBuffer(VertexBuffer.Usage.STATIC);
buffer.bind();
buffer.upload(mesh);
VertexBuffer.unbind();
return buffer;
}
private static void renderCelestialBodies(Matrix4f modelViewMatrix, Matrix4f projectionMatrix, float timeOfDay) {
float t = timeOfDay * 360.0F;
renderBodyGlow(modelViewMatrix, projectionMatrix, t, -10.0F, 0xFF1F18, 86.0F);
renderTexturedBody(modelViewMatrix, projectionMatrix, t, -10.0F, 42.0F, 0xFF1F18, SUN_TEXTURE, false, 0, t * 0.6F);
float moonOrbit = t + 180.0F;
renderBodyGlow(modelViewMatrix, projectionMatrix, moonOrbit, 24.0F, 0x8F30FF, 76.0F);
renderTexturedBody(modelViewMatrix, projectionMatrix, moonOrbit, 24.0F, 26.0F, 0x8F30FF, MOON_TEXTURE, true, 0, -t * 0.35F);
}
private static void renderBodyGlow(Matrix4f modelViewMatrix, Matrix4f projectionMatrix, float orbitDegrees, float yawDegrees, int rgb, float glowRadius) {
RenderSystem.enableBlend();
RenderSystem.defaultBlendFunc();
RenderSystem.disableCull();
RenderSystem.setShader(GameRenderer::getPositionColorShader);
RenderSystem.backupProjectionMatrix();
RenderSystem.setProjectionMatrix(projectionMatrix, VertexSorting.DISTANCE_TO_ORIGIN);
PoseStack modelViewStack = RenderSystem.getModelViewStack();
modelViewStack.pushPose();
modelViewStack.mulPoseMatrix(modelViewMatrix);
modelViewStack.mulPose(Axis.XP.rotationDegrees(orbitDegrees));
modelViewStack.mulPose(Axis.ZP.rotationDegrees(yawDegrees));
RenderSystem.applyModelViewMatrix();
float red = ((rgb >> 16) & 0xFF) / 255.0F;
float green = ((rgb >> 8) & 0xFF) / 255.0F;
float blue = (rgb & 0xFF) / 255.0F;
BufferBuilder buffer = Tesselator.getInstance().getBuilder();
buffer.begin(VertexFormat.Mode.TRIANGLE_FAN, DefaultVertexFormat.POSITION_COLOR);
buffer.vertex(0.0F, SKY_RADIUS, 0.0F).color(red, green, blue, 0.55F).endVertex();
for (int i = 0; i <= 24; i++) {
float angle = i * Mth.TWO_PI / 24.0F;
buffer.vertex(Mth.sin(angle) * glowRadius, SKY_RADIUS, Mth.cos(angle) * glowRadius).color(red, green, blue, 0.0F).endVertex();
}
BufferUploader.drawWithShader(buffer.end());
modelViewStack.popPose();
RenderSystem.applyModelViewMatrix();
RenderSystem.restoreProjectionMatrix();
RenderSystem.enableCull();
RenderSystem.disableBlend();
}
private static void renderTexturedBody(
Matrix4f modelViewMatrix,
Matrix4f projectionMatrix,
float orbitDegrees,
float yawDegrees,
float radius,
int rgb,
ResourceLocation texture,
boolean usePhaseSheet,
int phaseIndex,
float selfRotationDegrees
) {
RenderSystem.enableBlend();
RenderSystem.blendFuncSeparate(
GlStateManager.SourceFactor.ONE,
GlStateManager.DestFactor.ONE,
GlStateManager.SourceFactor.ONE,
GlStateManager.DestFactor.ZERO
);
RenderSystem.disableCull();
RenderSystem.setShaderColor(((rgb >> 16) & 0xFF) / 255.0F, ((rgb >> 8) & 0xFF) / 255.0F, (rgb & 0xFF) / 255.0F, 1.0F);
RenderSystem.setShader(GameRenderer::getPositionTexShader);
RenderSystem.setShaderTexture(0, texture);
RenderSystem.backupProjectionMatrix();
RenderSystem.setProjectionMatrix(projectionMatrix, VertexSorting.DISTANCE_TO_ORIGIN);
PoseStack modelViewStack = RenderSystem.getModelViewStack();
modelViewStack.pushPose();
modelViewStack.mulPoseMatrix(modelViewMatrix);
modelViewStack.mulPose(Axis.XP.rotationDegrees(orbitDegrees));
modelViewStack.mulPose(Axis.ZP.rotationDegrees(yawDegrees));
modelViewStack.mulPose(Axis.YP.rotationDegrees(selfRotationDegrees));
RenderSystem.applyModelViewMatrix();
float halfSize = BODY_SIZE * 0.5F * (radius / 22.0F);
float minU = 0.0F;
float maxU = 1.0F;
float minV = 0.0F;
float maxV = 1.0F;
if (usePhaseSheet) {
int phaseX = phaseIndex % 4;
int phaseY = phaseIndex / 4;
minU = phaseX / 4.0F;
maxU = (phaseX + 1) / 4.0F;
minV = phaseY / 2.0F;
maxV = (phaseY + 1) / 2.0F;
}
BufferBuilder buffer = Tesselator.getInstance().getBuilder();
buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX);
buffer.vertex(-halfSize, SKY_RADIUS, -halfSize).uv(minU, minV).endVertex();
buffer.vertex(-halfSize, SKY_RADIUS, halfSize).uv(minU, maxV).endVertex();
buffer.vertex(halfSize, SKY_RADIUS, halfSize).uv(maxU, maxV).endVertex();
buffer.vertex(halfSize, SKY_RADIUS, -halfSize).uv(maxU, minV).endVertex();
BufferUploader.drawWithShader(buffer.end());
modelViewStack.popPose();
RenderSystem.applyModelViewMatrix();
RenderSystem.restoreProjectionMatrix();
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
RenderSystem.defaultBlendFunc();
RenderSystem.enableCull();
RenderSystem.disableBlend();
}
}
@@ -0,0 +1,25 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.content.time.TimeDilationFieldEntity;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.entity.EntityRenderer;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.resources.ResourceLocation;
public class TimeDilationFieldRenderer extends EntityRenderer<TimeDilationFieldEntity> {
public TimeDilationFieldRenderer(EntityRendererProvider.Context context) {
super(context);
this.shadowRadius = 0.0F;
}
@Override
public void render(TimeDilationFieldEntity entity, float entityYaw, float partialTick, PoseStack poseStack,
MultiBufferSource bufferSource, int packedLight) {
}
@Override
public ResourceLocation getTextureLocation(TimeDilationFieldEntity entity) {
return new ResourceLocation("minecraft", "textures/misc/unknown_pack.png");
}
}
@@ -0,0 +1,19 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.content.client.model.VortexModel;
import com.craisinlord.antarchy.content.entity.vortex.VortexEntity;
import software.bernie.geckolib.renderer.GeoEntityRenderer;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
public class VortexRenderer extends GeoEntityRenderer<VortexEntity> {
public VortexRenderer(EntityRendererProvider.Context context) {
super(context, new VortexModel());
this.shadowRadius = 0.5F;
this.withScale(0.9F);
}
@Override
protected float getDeathMaxRotation(VortexEntity animatable) {
return 0.0F;
}
}
@@ -0,0 +1,115 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.content.entity.vortex.WindVortexEntity;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.entity.EntityRenderer;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.Mth;
import org.joml.Matrix4f;
public class WindVortexRenderer extends EntityRenderer<WindVortexEntity> {
private static final int RINGS = 11;
private static final float BASE_RADIUS = 0.35F;
public WindVortexRenderer(EntityRendererProvider.Context context) {
super(context);
this.shadowRadius = 0.0F;
}
@Override
public void render(WindVortexEntity entity, float entityYaw, float partialTick, PoseStack poseStack,
MultiBufferSource bufferSource, int packedLight) {
VertexConsumer consumer = bufferSource.getBuffer(RenderType.lightning());
Matrix4f pose = poseStack.last().pose();
float height = entity.getVortexHeight();
float topRadius = entity.getTopRadius();
float age = entity.tickCount + partialTick;
float fadeIn = Mth.clamp(age / 8.0F, 0.0F, 1.0F);
float alphaMultiplier = fadeIn * entity.getFadeOutProgress(partialTick);
WindVortexEntity.Basis basis = entity.basis();
boolean pull = entity.getMode() == WindVortexEntity.VortexMode.LENS_PULL;
boolean push = entity.getMode() == WindVortexEntity.VortexMode.LENS_PUSH;
float spinDirection = pull ? -1.0F : 1.0F;
for (int ring = 0; ring < RINGS; ring++) {
float progress = (ring + 0.25F) / RINGS;
float axisDistance = progress * height;
float shapeProgress = pull ? 1.0F - progress : progress;
float radius = Mth.lerp(shapeProgress, BASE_RADIUS, topRadius);
float ringSpin = spinDirection * age * (0.13F + progress * 0.045F) + ring * 0.47F;
float alpha = alphaMultiplier;
float halfSide = radius * 0.92F;
float lineThickness = Math.max(0.045F, radius * 0.12F);
int red = push ? 70 : 64;
int green = push ? 220 : 255;
int blue = push ? 255 : 56;
drawSquareRing(consumer, pose, basis, halfSide, axisDistance, lineThickness, ringSpin,
alpha, red, green, blue);
}
super.render(entity, entityYaw, partialTick, poseStack, bufferSource, packedLight);
}
private static void drawSquareRing(VertexConsumer consumer, Matrix4f pose, WindVortexEntity.Basis basis,
float halfSide, float axisDistance, float lineThickness, float rotation, float alpha,
int red, int green, int blue) {
for (int side = 0; side < 4; side++) {
float sideAngle = rotation + side * (Mth.TWO_PI / 4.0F);
addSquareSide(consumer, pose, basis, halfSide, axisDistance, lineThickness,
sideAngle, alpha, red, green, blue);
}
}
private static void addSquareSide(VertexConsumer consumer, Matrix4f pose, WindVortexEntity.Basis basis,
float halfSide, float axisDistance, float lineThickness, float sideAngle,
float alpha, int red, int green, int blue) {
org.joml.Vector3f start = squarePoint(basis, halfSide, axisDistance, sideAngle, -halfSide);
org.joml.Vector3f end = squarePoint(basis, halfSide, axisDistance, sideAngle, halfSide);
org.joml.Vector3f axisOffset = new org.joml.Vector3f(
(float) basis.axis.x, (float) basis.axis.y, (float) basis.axis.z).mul(lineThickness);
int a = Mth.clamp((int) (alpha * 255.0F), 0, 255);
addQuad(consumer, pose, start, end, axisOffset, red, green, blue, a);
}
private static org.joml.Vector3f squarePoint(WindVortexEntity.Basis basis, float halfSide,
float axisDistance, float sideAngle, float along) {
float x = Mth.cos(sideAngle) * halfSide;
float z = Mth.sin(sideAngle) * halfSide;
float tangentX = -Mth.sin(sideAngle) * along;
float tangentZ = Mth.cos(sideAngle) * along;
return new org.joml.Vector3f(
(float) (basis.axis.x * axisDistance + basis.sideA.x * (x + tangentX) + basis.sideB.x * (z + tangentZ)),
(float) (basis.axis.y * axisDistance + basis.sideA.y * (x + tangentX) + basis.sideB.y * (z + tangentZ)),
(float) (basis.axis.z * axisDistance + basis.sideA.z * (x + tangentX) + basis.sideB.z * (z + tangentZ)));
}
private static void addQuad(VertexConsumer consumer, Matrix4f pose, org.joml.Vector3f start,
org.joml.Vector3f end, org.joml.Vector3f offset, int red, int green, int blue, int alpha) {
addVertex(consumer, pose, start.x - offset.x, start.y - offset.y, start.z - offset.z, red, green, blue, alpha);
addVertex(consumer, pose, end.x - offset.x, end.y - offset.y, end.z - offset.z, red, green, blue, alpha);
addVertex(consumer, pose, end.x + offset.x, end.y + offset.y, end.z + offset.z, red, green, blue, alpha);
addVertex(consumer, pose, start.x + offset.x, start.y + offset.y, start.z + offset.z, red, green, blue, alpha);
// The lightning render type can cull one winding from some camera
// angles. Emit the opposite winding as well so every ribbon is
// visible from both sides.
addVertex(consumer, pose, start.x + offset.x, start.y + offset.y, start.z + offset.z, red, green, blue, alpha);
addVertex(consumer, pose, end.x + offset.x, end.y + offset.y, end.z + offset.z, red, green, blue, alpha);
addVertex(consumer, pose, end.x - offset.x, end.y - offset.y, end.z - offset.z, red, green, blue, alpha);
addVertex(consumer, pose, start.x - offset.x, start.y - offset.y, start.z - offset.z, red, green, blue, alpha);
}
private static void addVertex(VertexConsumer consumer, Matrix4f pose, float x, float y, float z,
int red, int green, int blue, int alpha) {
consumer.vertex(pose, x, y, z).color(red, green, blue, alpha).endVertex();
}
@Override
public ResourceLocation getTextureLocation(WindVortexEntity entity) {
return new ResourceLocation("minecraft", "textures/misc/unknown_pack.png");
}
}
@@ -0,0 +1,13 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.content.client.model.WormModel;
import com.craisinlord.antarchy.content.entity.WormEntity;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import software.bernie.geckolib.renderer.GeoEntityRenderer;
public class WormRenderer extends GeoEntityRenderer<WormEntity> {
public WormRenderer(EntityRendererProvider.Context context) {
super(context, new WormModel());
this.shadowRadius = 0.75F;
}
}
@@ -0,0 +1,32 @@
package com.craisinlord.antarchy.content.client.screen;
import com.craisinlord.antarchy.content.menu.RoyalJudgmentMenu;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.player.Inventory;
public final class RoyalJudgmentScreen extends AbstractContainerScreen<RoyalJudgmentMenu> {
private static final ResourceLocation INVENTORY_TEXTURE =
new ResourceLocation("minecraft", "textures/gui/container/inventory.png");
public RoyalJudgmentScreen(RoyalJudgmentMenu menu, Inventory inventory, Component title) {
super(menu, inventory, title);
this.imageWidth = 176;
this.imageHeight = 166;
this.inventoryLabelY = 84;
}
@Override
protected void renderBg(GuiGraphics graphics, float partialTick, int mouseX, int mouseY) {
graphics.blit(INVENTORY_TEXTURE, this.leftPos, this.topPos, 0, 0, this.imageWidth, this.imageHeight);
int gold = 0xD8E6B84A;
graphics.fill(this.leftPos - 2, this.topPos - 2, this.leftPos + this.imageWidth + 2, this.topPos, gold);
graphics.fill(this.leftPos - 2, this.topPos + this.imageHeight, this.leftPos + this.imageWidth + 2,
this.topPos + this.imageHeight + 2, gold);
graphics.fill(this.leftPos - 2, this.topPos - 2, this.leftPos, this.topPos + this.imageHeight + 2, gold);
graphics.fill(this.leftPos + this.imageWidth, this.topPos - 2, this.leftPos + this.imageWidth + 2,
this.topPos + this.imageHeight + 2, gold);
}
}
@@ -0,0 +1,77 @@
package com.craisinlord.antarchy.content.command;
import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.entity.portal.DimensionalTearEntity;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.DoubleArgumentType;
import com.mojang.brigadier.context.CommandContext;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.Mth;
import net.minecraft.world.phys.Vec3;
public final class DimensionalTearCommand {
private static final double DEFAULT_GAP = 24.0D;
private static final double VERTICAL_OFFSET = 1.0D;
private DimensionalTearCommand() {
}
public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
dispatcher.register(
Commands.literal("dimensionaltear")
.requires(source -> source.hasPermission(2))
.executes(context -> spawnPair(context, DEFAULT_GAP))
.then(Commands.literal("single")
.executes(DimensionalTearCommand::spawnSingle))
.then(Commands.argument("blocksAhead", DoubleArgumentType.doubleArg(2.0D, 512.0D))
.executes(context -> spawnPair(context, DoubleArgumentType.getDouble(context, "blocksAhead"))))
);
}
private static int lifetime() {
return Math.max(1200, AntarchySettings.dimensionalTearLifetimeTicks());
}
private static Vec3 anchorPos(ServerPlayer player) {
Vec3 forward = player.getLookAngle().normalize();
return player.position().add(forward.scale(4.0D)).add(0.0D, VERTICAL_OFFSET, 0.0D);
}
private static int spawnSingle(CommandContext<CommandSourceStack> context) throws com.mojang.brigadier.exceptions.CommandSyntaxException {
ServerPlayer player = context.getSource().getPlayerOrException();
ServerLevel level = context.getSource().getLevel();
DimensionalTearEntity tear = DimensionalTearEntity.create(level, anchorPos(player), player.getYRot(), lifetime());
level.addFreshEntity(tear);
context.getSource().sendSuccess(() -> Component.literal("Spawned an unlinked dimensional tear"), true);
return 1;
}
private static int spawnPair(CommandContext<CommandSourceStack> context, double gap) throws com.mojang.brigadier.exceptions.CommandSyntaxException {
ServerPlayer player = context.getSource().getPlayerOrException();
ServerLevel level = context.getSource().getLevel();
float yawA = player.getYRot();
Vec3 posA = anchorPos(player);
Vec3 forward = Vec3.directionFromRotation(0.0F, yawA);
Vec3 posB = posA.add(forward.scale(gap));
float yawB = Mth.wrapDegrees(yawA + 180.0F);
DimensionalTearEntity tearA = DimensionalTearEntity.create(level, posA, yawA, lifetime());
DimensionalTearEntity tearB = DimensionalTearEntity.create(level, posB, yawB, lifetime());
tearA.linkTo(tearB);
tearB.linkTo(tearA);
level.addFreshEntity(tearA);
level.addFreshEntity(tearB);
int spacing = Mth.floor(gap);
context.getSource().sendSuccess(
() -> Component.literal("Spawned a linked dimensional tear pair, " + spacing + " blocks apart"),
true
);
return 1;
}
}
@@ -0,0 +1,71 @@
package com.craisinlord.antarchy.content.command;
import com.craisinlord.antarchy.content.entity.royal.KingEntity;
import com.craisinlord.antarchy.content.entity.royal.RoyalPunishmentType;
import com.craisinlord.antarchy.content.entity.royal.decree.RoyalDecree;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.phys.AABB;
public final class RoyalCommand {
private RoyalCommand() {
}
public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
dispatcher.register(Commands.literal("royal")
.requires(source -> source.hasPermission(2))
.then(Commands.literal("decree")
.then(Commands.argument("id", StringArgumentType.word())
.executes(RoyalCommand::activateDecree)))
.then(Commands.literal("punishment")
.then(Commands.argument("id", StringArgumentType.word())
.executes(RoyalCommand::triggerPunishment))));
}
private static int activateDecree(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
ServerPlayer player = context.getSource().getPlayerOrException();
KingEntity king = nearestKing(player);
if (king == null) {
context.getSource().sendFailure(Component.literal("No King found nearby."));
return 0;
}
String id = StringArgumentType.getString(context, "id");
if (!king.debugActivateDecree(id, player)) {
context.getSource().sendFailure(Component.literal("Unknown decree: " + id));
return 0;
}
context.getSource().sendSuccess(() -> Component.literal("Activated decree " + id), true);
return 1;
}
private static int triggerPunishment(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
ServerPlayer player = context.getSource().getPlayerOrException();
KingEntity king = nearestKing(player);
if (king == null) {
context.getSource().sendFailure(Component.literal("No King found nearby."));
return 0;
}
String id = StringArgumentType.getString(context, "id");
RoyalPunishmentType type = RoyalPunishmentType.fromId(id);
if (type == null || !king.debugTriggerPunishment(type, player)) {
context.getSource().sendFailure(Component.literal("Unknown or unavailable punishment: " + id));
return 0;
}
context.getSource().sendSuccess(() -> Component.literal("Triggered punishment " + type.id()), true);
return 1;
}
private static KingEntity nearestKing(ServerPlayer player) {
AABB area = player.getBoundingBox().inflate(128.0D);
return player.serverLevel().getEntitiesOfClass(KingEntity.class, area, LivingEntity::isAlive).stream()
.min((left, right) -> Double.compare(left.distanceToSqr(player), right.distanceToSqr(player)))
.orElse(null);
}
}
@@ -0,0 +1,37 @@
package com.craisinlord.antarchy.content.dispenser;
import com.craisinlord.antarchy.content.item.EyeOfTheStormItem;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.BlockSource;
import net.minecraft.core.dispenser.DefaultDispenseItemBehavior;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.DispenserBlock;
import net.minecraft.world.phys.Vec3;
public class EyeOfTheStormDispenseBehavior extends DefaultDispenseItemBehavior {
@Override
protected ItemStack execute(BlockSource source, ItemStack stack) {
ServerLevel level = source.getLevel();
Direction direction = source.getBlockState().getValue(DispenserBlock.FACING);
BlockPos pos = source.getPos().relative(direction);
Vec3 origin = new Vec3(pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D);
Vec3 aim = new Vec3(direction.getStepX(), direction.getStepY(), direction.getStepZ());
EyeOfTheStormItem.launchStormVortex(level, origin, aim, null);
if (stack.isDamageableItem()) {
stack.setDamageValue(stack.getDamageValue() + 1);
if (stack.getDamageValue() >= stack.getMaxDamage()) {
stack.shrink(1);
}
}
return stack;
}
@Override
protected void playSound(BlockSource source) {
source.getLevel().levelEvent(1002, source.getPos(), 0);
}
}
@@ -0,0 +1,38 @@
package com.craisinlord.antarchy.content.dispenser;
import com.craisinlord.antarchy.content.entity.vortex.VortexChargeProjectileEntity;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.BlockSource;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.DispenserBlock;
import net.minecraft.world.level.block.entity.DispenserBlockEntity;
import net.minecraft.core.dispenser.DefaultDispenseItemBehavior;
public class VortexChargeDispenseBehavior extends DefaultDispenseItemBehavior {
@Override
protected ItemStack execute(BlockSource source, ItemStack stack) {
if (VortexChargeProjectileEntity.defaultItemSupplier == null) {
return super.execute(source, stack);
}
ServerLevel level = source.getLevel();
Direction direction = source.getBlockState().getValue(DispenserBlock.FACING);
BlockPos pos = source.getPos().relative(direction);
double x = pos.getX() + 0.5D;
double y = pos.getY() + 0.5D;
double z = pos.getZ() + 0.5D;
VortexChargeProjectileEntity projectile = new VortexChargeProjectileEntity(level, x, y, z);
projectile.shoot(direction.getStepX(), direction.getStepY() + 0.1D, direction.getStepZ(), 1.5F, 1.0F);
level.addFreshEntity(projectile);
stack.shrink(1);
return stack;
}
@Override
protected void playSound(BlockSource source) {
source.getLevel().levelEvent(1002, source.getPos(), 0);
}
}
@@ -0,0 +1,127 @@
package com.craisinlord.antarchy.content.effect;
import java.util.UUID;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.Mob;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.ai.attributes.Attributes;
public final class CommandedBehavior {
private CommandedBehavior() {
}
public static void tick(Mob mob) {
if (!(mob instanceof CommandedEntityAccess access)) {
return;
}
UUID commanderUuid = access.antarchy$getCommanderUuid();
if (commanderUuid == null || mob.level().getServer() == null) {
clear(mob);
return;
}
Entity commander = mob.level() instanceof ServerLevel level ? level.getEntity(commanderUuid) : null;
if (commander == null || commander.level() != mob.level() || !RoyalEffectEligibility.canApplyCommanded(mob)) {
mob.removeEffect(RoyalEffectHooks.commandedHolder());
clear(mob);
return;
}
LivingEntity target = mob.getTarget();
if (target != null && isProtectedAlly(mob, target)) {
mob.setTarget(null);
target = null;
}
if (target != null) {
tryPassiveCommandedAttack(mob, target);
}
if (mob.tickCount % 10 != 0 || target != null) {
return;
}
LivingEntity candidate = commander instanceof Mob commanderMob ? commanderMob.getTarget() : null;
if (!isValidTarget(mob, commander, candidate)) {
candidate = commander instanceof net.minecraft.world.entity.player.Player player ? player.getLastHurtByMob() : null;
}
if (!isValidTarget(mob, commander, candidate)) {
candidate = mob.getLastHurtByMob();
}
if (!isValidTarget(mob, commander, candidate)) {
candidate = commander instanceof net.minecraft.world.entity.player.Player player ? player.getLastHurtMob() : null;
}
if (!isValidTarget(mob, commander, candidate)) {
candidate = mob.getLastHurtByMob();
}
if (!isValidTarget(mob, commander, candidate)) {
candidate = mob.level().getEntitiesOfClass(LivingEntity.class, mob.getBoundingBox().inflate(16.0D),
entity -> isValidTarget(mob, commander, entity)).stream()
.min((a, b) -> {
int distance = Double.compare(mob.distanceToSqr(a), mob.distanceToSqr(b));
return distance != 0 ? distance : Integer.compare(a.getId(), b.getId());
}).orElse(null);
}
if (candidate != null) {
mob.setTarget(candidate);
access.antarchy$setCommandedTargetOwned(true);
}
}
public static boolean isProtectedAlly(Mob mob, Entity entity) {
if (!(mob instanceof CommandedEntityAccess access) || entity == null) {
return false;
}
UUID commanderUuid = access.antarchy$getCommanderUuid();
if (commanderUuid == null) {
return false;
}
if (entity.getUUID().equals(commanderUuid)) {
return true;
}
if (entity instanceof Mob other && other instanceof CommandedEntityAccess otherAccess) {
return commanderUuid.equals(otherAccess.antarchy$getCommanderUuid());
}
if (entity instanceof LivingEntity living && living.isAlliedTo(mob)) {
return true;
}
if (entity instanceof LivingEntity living && commanderEntity(mob) instanceof LivingEntity livingCommander) {
return living.isAlliedTo(livingCommander);
}
return false;
}
private static boolean isValidTarget(Mob mob, Entity commander, LivingEntity target) {
return target != null && target.isAlive() && target != mob && target != commander
&& !isProtectedAlly(mob, target) && !target.isAlliedTo(commander);
}
private static void tryPassiveCommandedAttack(Mob mob, LivingEntity target) {
if (mob.getAttribute(Attributes.ATTACK_DAMAGE) != null
&& mob.getAttributeValue(Attributes.ATTACK_DAMAGE) > 0.0D) {
return;
}
mob.getNavigation().moveTo(target, 1.0D);
if (mob.tickCount % 20 != 0 || mob.distanceToSqr(target) > 4.0D) {
return;
}
if (target.hurt(mob.damageSources().mobAttack(mob), 1.0F)) {
mob.swing(net.minecraft.world.InteractionHand.MAIN_HAND);
}
}
private static Entity commanderEntity(Mob mob) {
if (!(mob instanceof CommandedEntityAccess access) || mob.level().getServer() == null) {
return null;
}
UUID uuid = access.antarchy$getCommanderUuid();
return uuid == null || !(mob.level() instanceof ServerLevel level) ? null : level.getEntity(uuid);
}
public static void clear(Mob mob) {
if (!(mob instanceof CommandedEntityAccess access)) {
return;
}
if (access.antarchy$isCommandedTargetOwned()) {
mob.setTarget(null);
}
access.antarchy$setCommandedTargetOwned(false);
access.antarchy$setCommanderUuid(null);
}
}
@@ -0,0 +1,10 @@
package com.craisinlord.antarchy.content.effect;
import java.util.UUID;
public interface CommandedEntityAccess {
UUID antarchy$getCommanderUuid();
void antarchy$setCommanderUuid(UUID uuid);
boolean antarchy$isCommandedTargetOwned();
void antarchy$setCommandedTargetOwned(boolean owned);
}
@@ -0,0 +1,24 @@
package com.craisinlord.antarchy.content.effect;
import net.minecraft.world.effect.MobEffect;
import net.minecraft.world.effect.MobEffectCategory;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.Mob;
public final class CommandedMobEffect extends MobEffect {
public CommandedMobEffect() {
super(MobEffectCategory.HARMFUL, 0xD4AF37);
}
@Override
public void applyEffectTick(LivingEntity entity, int amplifier) {
if (!entity.level().isClientSide() && entity instanceof Mob mob) {
CommandedBehavior.tick(mob);
}
}
@Override
public boolean isDurationEffectTick(int duration, int amplifier) {
return true;
}
}
@@ -0,0 +1,28 @@
package com.craisinlord.antarchy.content.effect;
import net.minecraft.world.effect.MobEffect;
import net.minecraft.world.effect.MobEffectCategory;
import net.minecraft.world.entity.LivingEntity;
/** Accelerates the affected entity's local time rate. */
public final class ContractedMobEffect extends MobEffect {
public static final double RATE = 2.0D;
public static final double STRONG_RATE = 3.0D;
public ContractedMobEffect() {
super(MobEffectCategory.BENEFICIAL, 0xD5A63A);
}
@Override
public void applyEffectTick(LivingEntity entity, int amplifier) {
}
@Override
public boolean isDurationEffectTick(int duration, int amplifier) {
return true;
}
public static double rateForAmplifier(int amplifier) {
return amplifier > 0 ? STRONG_RATE : RATE;
}
}
@@ -0,0 +1,27 @@
package com.craisinlord.antarchy.content.effect;
import net.minecraft.world.effect.MobEffect;
import net.minecraft.world.effect.MobEffectCategory;
import net.minecraft.world.entity.LivingEntity;
public final class DilatedMobEffect extends MobEffect {
public static final double RATE = 0.35D;
public static final double STRONG_RATE = 0.2D;
public DilatedMobEffect() {
super(MobEffectCategory.HARMFUL, 0x6C4AB6);
}
@Override
public void applyEffectTick(LivingEntity entity, int amplifier) {
}
@Override
public boolean isDurationEffectTick(int duration, int amplifier) {
return true;
}
public static double rateForAmplifier(int amplifier) {
return amplifier > 0 ? STRONG_RATE : RATE;
}
}
@@ -0,0 +1,58 @@
package com.craisinlord.antarchy.content.effect;
import com.craisinlord.antarchy.content.AntarchyTags;
import com.craisinlord.antarchy.content.item.RoyalAssailantArmorItem;
import com.craisinlord.antarchy.content.item.RoyalGuardianArmorItem;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.player.Player;
import com.craisinlord.antarchy.content.entity.royal.RoyalBossEntity;
public final class RoyalEffectEligibility {
private RoyalEffectEligibility() {
}
public static boolean isCommanded(MobEffectInstance instance) {
return RoyalEffectHooks.commandedHolder() != null && instance.getEffect() == RoyalEffectHooks.commandedHolder();
}
public static boolean isDilated(MobEffectInstance instance) {
return RoyalEffectHooks.dilatedHolder() != null && instance.getEffect() == RoyalEffectHooks.dilatedHolder();
}
public static boolean canApplyCommanded(LivingEntity entity) {
return entity instanceof Mob mob
&& !(entity instanceof Player)
&& !entity.getType().is(AntarchyTags.Entities.COMMANDED_BLACKLIST)
&& !(mob.isNoAi())
&& !(entity.getItemBySlot(net.minecraft.world.entity.EquipmentSlot.HEAD).getItem() instanceof RoyalGuardianArmorItem)
&& !(entity.getItemBySlot(net.minecraft.world.entity.EquipmentSlot.CHEST).getItem() instanceof RoyalGuardianArmorItem)
&& !(entity.getItemBySlot(net.minecraft.world.entity.EquipmentSlot.LEGS).getItem() instanceof RoyalGuardianArmorItem)
&& !(entity.getItemBySlot(net.minecraft.world.entity.EquipmentSlot.FEET).getItem() instanceof RoyalGuardianArmorItem);
}
public static boolean canApplyDilated(LivingEntity entity) {
return !entity.getType().is(AntarchyTags.Entities.DILATED_BLACKLIST)
&& !entity.getType().is(AntarchyTags.Entities.TIME_DILATION_IMMUNE)
&& !(entity.getItemBySlot(net.minecraft.world.entity.EquipmentSlot.HEAD).getItem() instanceof RoyalAssailantArmorItem)
&& !(entity.getItemBySlot(net.minecraft.world.entity.EquipmentSlot.CHEST).getItem() instanceof RoyalAssailantArmorItem)
&& !(entity.getItemBySlot(net.minecraft.world.entity.EquipmentSlot.LEGS).getItem() instanceof RoyalAssailantArmorItem)
&& !(entity.getItemBySlot(net.minecraft.world.entity.EquipmentSlot.FEET).getItem() instanceof RoyalAssailantArmorItem);
}
public static boolean canApply(LivingEntity entity, MobEffectInstance instance) {
if (isCommanded(instance)) {
return canApplyCommanded(entity);
}
if (isDilated(instance)) {
return canApplyDilated(entity);
}
return true;
}
public static boolean canUseCommandSource(Entity source) {
return source instanceof Player || source instanceof RoyalBossEntity;
}
}
@@ -0,0 +1,32 @@
package com.craisinlord.antarchy.content.effect;
import com.craisinlord.antarchy.Antarchy;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.effect.MobEffect;
public final class RoyalEffectHooks {
private static final ResourceKey<MobEffect> COMMANDED_KEY = ResourceKey.create(Registries.MOB_EFFECT,
new ResourceLocation(Antarchy.MODID, "commanded"));
private static final ResourceKey<MobEffect> DILATED_KEY = ResourceKey.create(Registries.MOB_EFFECT,
new ResourceLocation(Antarchy.MODID, "dilated"));
private static final ResourceKey<MobEffect> CONTRACTED_KEY = ResourceKey.create(Registries.MOB_EFFECT,
new ResourceLocation(Antarchy.MODID, "contracted"));
private RoyalEffectHooks() {
}
public static MobEffect commandedHolder() {
return BuiltInRegistries.MOB_EFFECT.get(COMMANDED_KEY);
}
public static MobEffect dilatedHolder() {
return BuiltInRegistries.MOB_EFFECT.get(DILATED_KEY);
}
public static MobEffect contractedHolder() {
return BuiltInRegistries.MOB_EFFECT.get(CONTRACTED_KEY);
}
}
@@ -0,0 +1,22 @@
package com.craisinlord.antarchy.content.effect;
import com.craisinlord.antarchy.content.AntarchyTags;
import net.minecraft.world.entity.LivingEntity;
/**
* Version-neutral eligibility rules for the Dilated effect.
*
* The 1.21 implementation also checks Royal armor. Those items do not exist
* in the 1.20 source set yet, so the shared 1.20 rule set intentionally
* retains the entity-tag checks here and can be extended when Royal content
* is ported.
*/
public final class TimeDilationEligibility {
private TimeDilationEligibility() {
}
public static boolean canApply(LivingEntity entity) {
return !entity.getType().is(AntarchyTags.Entities.TIME_DILATION_IMMUNE)
&& !entity.getType().is(AntarchyTags.Entities.DILATED_BLACKLIST);
}
}
@@ -0,0 +1,297 @@
package com.craisinlord.antarchy.content.entity;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityDirection;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityRotationUtil;
import com.craisinlord.antarchy.mixins.LivingEntityJumpingAccessor;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.util.Mth;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.MoverType;
import net.minecraft.world.entity.PathfinderMob;
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.DyeColor;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
import software.bernie.geckolib.animatable.GeoEntity;
import software.bernie.geckolib.core.animatable.instance.AnimatableInstanceCache;
import software.bernie.geckolib.core.animation.AnimatableManager;
import software.bernie.geckolib.core.animation.AnimationController;
import software.bernie.geckolib.core.animation.AnimationState;
import software.bernie.geckolib.core.object.PlayState;
import software.bernie.geckolib.core.animation.RawAnimation;
import software.bernie.geckolib.util.GeckoLibUtil;
public class HoverboardEntity extends PathfinderMob implements GeoEntity {
private static final EntityDataAccessor<Integer> COLOR = SynchedEntityData.defineId(HoverboardEntity.class, EntityDataSerializers.INT);
private static final EntityDataAccessor<Boolean> TRAIL_ACTIVE = SynchedEntityData.defineId(HoverboardEntity.class, EntityDataSerializers.BOOLEAN);
private static final RawAnimation IDLE_ANIM = RawAnimation.begin().thenLoop("idle");
private static final RawAnimation MOVE_ANIM = RawAnimation.begin().thenLoop("move");
private static final double MAX_HOVER_HEIGHT = 5.0D;
private static final double ASCEND_SPEED = 0.2D;
private static final double SETTLE_DESCEND_SPEED = 0.08D;
private static final double FAST_DESCEND_SPEED = 0.9D;
private static final double FAST_DESCEND_RAMP = 0.2D;
private static final double DAMPING = 0.955D;
private static final double RIDER_Y_OFFSET = 0.65D;
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
public HoverboardEntity(EntityType<? extends HoverboardEntity> entityType, Level level) {
super(entityType, level);
this.setNoGravity(true);
this.setPersistenceRequired();
}
public static AttributeSupplier.Builder createAttributes() {
return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 6.0D);
}
@Override
public boolean hurt(DamageSource source, float amount) {
if (this.level().isClientSide || !this.isAlive()) {
return false;
}
if (this.isInvulnerableTo(source)) {
return false;
}
this.ejectPassengers();
this.playSound(SoundEvents.WOOD_BREAK, 1.0F, 1.0F);
this.spawnAtLocation(this.getDropStack());
this.discard();
return true;
}
@Override
protected void registerGoals() {
}
@Override
protected void defineSynchedData() {
super.defineSynchedData();
this.entityData.define(COLOR, -1);
this.entityData.define(TRAIL_ACTIVE, false);
}
@Nullable
public DyeColor getColor() {
int id = this.entityData.get(COLOR);
return id < 0 ? null : DyeColor.byId(id);
}
public void setColor(@Nullable DyeColor color) {
this.entityData.set(COLOR, color == null ? -1 : color.getId());
}
public boolean isTrailActive() {
return this.entityData.get(TRAIL_ACTIVE);
}
private void setTrailActive(boolean active) {
this.entityData.set(TRAIL_ACTIVE, active);
}
@Override
public void addAdditionalSaveData(CompoundTag tag) {
super.addAdditionalSaveData(tag);
if (this.getColor() != null) {
tag.putString("Color", this.getColor().getName());
}
}
@Override
public void readAdditionalSaveData(CompoundTag tag) {
super.readAdditionalSaveData(tag);
this.setColor(tag.contains("Color") ? DyeColor.byName(tag.getString("Color"), null) : null);
}
@Override
protected InteractionResult mobInteract(Player player, InteractionHand hand) {
if (!this.isVehicle() && !player.isSecondaryUseActive()) {
if (!this.level().isClientSide) {
player.startRiding(this);
}
return InteractionResult.sidedSuccess(this.level().isClientSide);
}
return super.mobInteract(player, hand);
}
@Override
@Nullable
public LivingEntity getControllingPassenger() {
Entity passenger = this.getFirstPassenger();
return passenger instanceof Player player ? player : null;
}
@Override
public boolean canAddPassenger(Entity passenger) {
return this.getPassengers().isEmpty();
}
@Override
protected void positionRider(Entity passenger, MoveFunction moveFunction) {
if (!this.hasPassenger(passenger)) {
return;
}
Vec3 riderOffset = AntarchyGravityRotationUtil.vecPlayerToWorld(
0.0D,
RIDER_Y_OFFSET,
0.0D,
AntarchyGravityApi.getGravityDirection(this)
);
moveFunction.accept(passenger, this.getX() + riderOffset.x, this.getY() + riderOffset.y, this.getZ() + riderOffset.z);
}
@Override
public void travel(Vec3 travelVector) {
if (this.getControllingPassenger() instanceof Player rider) {
this.setYRot(rider.getYRot());
this.yRotO = this.getYRot();
this.yBodyRot = this.getYRot();
this.yHeadRot = this.getYRot();
this.setXRot(0.0F);
Vec3 look = Vec3.directionFromRotation(0.0F, this.getYRot());
float forward = rider.zza;
this.setTrailActive(forward > 0.0F);
boolean holdingJump = ((LivingEntityJumpingAccessor) rider).antarchy$isJumping();
double verticalMotion = this.resolveVerticalMotion(holdingJump);
double acceleration = AntarchySettings.hoverboardAcceleration();
double maxSpeed = AntarchySettings.hoverboardMaxSpeed();
Vec3 currentHorizontal = new Vec3(this.getDeltaMovement().x, 0.0D, this.getDeltaMovement().z);
Vec3 horizontalMotion = currentHorizontal.scale(DAMPING).add(look.scale(forward * acceleration));
double horizontalSpeed = horizontalMotion.horizontalDistance();
if (horizontalSpeed > maxSpeed) {
double scale = maxSpeed / horizontalSpeed;
horizontalMotion = new Vec3(horizontalMotion.x * scale, 0.0D, horizontalMotion.z * scale);
}
this.setDeltaMovement(horizontalMotion.x, verticalMotion, horizontalMotion.z);
this.move(MoverType.SELF, this.getDeltaMovement());
return;
}
this.setTrailActive(false);
double verticalMotion = this.resolveVerticalMotion(false);
Vec3 horizontalMotion = new Vec3(this.getDeltaMovement().x, 0.0D, this.getDeltaMovement().z).scale(DAMPING);
this.setDeltaMovement(horizontalMotion.x, verticalMotion, horizontalMotion.z);
this.move(MoverType.SELF, this.getDeltaMovement());
}
private double resolveVerticalMotion(boolean holdingJump) {
AntarchyGravityDirection gravityDirection = AntarchyGravityApi.getGravityDirection(this);
double supportHeight = this.findSupportHeight(gravityDirection);
if (!gravityDirection.isInverted()) {
double maxY = supportHeight + MAX_HOVER_HEIGHT;
if (holdingJump) {
return this.getY() >= maxY ? 0.0D : ASCEND_SPEED;
}
if (this.getY() <= supportHeight) {
return 0.0D;
}
double heightAboveBand = this.getY() - maxY;
if (heightAboveBand > 0.0D) {
return -Mth.clamp(SETTLE_DESCEND_SPEED + heightAboveBand * FAST_DESCEND_RAMP, SETTLE_DESCEND_SPEED, FAST_DESCEND_SPEED);
}
return -SETTLE_DESCEND_SPEED;
}
double minY = supportHeight - MAX_HOVER_HEIGHT;
if (holdingJump) {
return this.getY() <= minY ? 0.0D : -ASCEND_SPEED;
}
if (this.getY() >= supportHeight) {
return 0.0D;
}
double depthBelowBand = minY - this.getY();
if (depthBelowBand > 0.0D) {
return Mth.clamp(SETTLE_DESCEND_SPEED + depthBelowBand * FAST_DESCEND_RAMP, SETTLE_DESCEND_SPEED, FAST_DESCEND_SPEED);
}
return SETTLE_DESCEND_SPEED;
}
private double findSupportHeight(AntarchyGravityDirection gravityDirection) {
BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(Mth.floor(this.getX()), Mth.floor(this.getY()), Mth.floor(this.getZ()));
if (!gravityDirection.isInverted()) {
int minY = this.level().getMinBuildHeight();
while (pos.getY() > minY && this.level().getBlockState(pos).isAir()) {
pos.move(Direction.DOWN);
}
return pos.getY() + 1.0D;
}
int maxY = this.level().getMaxBuildHeight() - 1;
while (pos.getY() < maxY && this.level().getBlockState(pos).isAir()) {
pos.move(Direction.UP);
}
return this.level().getBlockState(pos).isAir() ? this.getY() : pos.getY();
}
@Override
public boolean causeFallDamage(float fallDistance, float multiplier, DamageSource source) {
return false;
}
@Override
public boolean removeWhenFarAway(double distanceToClosestPlayer) {
return false;
}
@Override
public void die(DamageSource damageSource) {
super.die(damageSource);
if (!this.level().isClientSide) {
this.spawnAtLocation(this.getDropStack());
}
}
public ItemStack getDropStack() {
String path = this.getColor() == null ? "hoverboard" : "hoverboard_" + this.getColor().getName();
Item item = BuiltInRegistries.ITEM.get(new ResourceLocation(Antarchy.MODID, path));
return new ItemStack(item);
}
@Override
public ItemStack getPickResult() {
return this.getDropStack();
}
@Override
public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
controllers.add(new AnimationController<>(this, "movement", 0, this::movementController));
}
private PlayState movementController(AnimationState<HoverboardEntity> state) {
return state.setAndContinue(this.getDeltaMovement().horizontalDistanceSqr() > 0.0025D ? MOVE_ANIM : IDLE_ANIM);
}
@Override
public AnimatableInstanceCache getAnimatableInstanceCache() {
return this.geoCache;
}
}
@@ -0,0 +1,508 @@
package com.craisinlord.antarchy.content.entity;
import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.content.entity.royal.QueenEntity;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityDirection;
import com.craisinlord.antarchy.content.worldgen.thoraxis.ThoraxisUndersideManager;
import java.util.List;
import java.util.UUID;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.RandomSource;
import net.minecraft.world.Difficulty;
import net.minecraft.world.DifficultyInstance;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.MobSpawnType;
import net.minecraft.world.entity.SpawnGroupData;
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.ai.control.FlyingMoveControl;
import net.minecraft.world.entity.ai.goal.FloatGoal;
import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal;
import net.minecraft.world.entity.ai.goal.MeleeAttackGoal;
import net.minecraft.world.entity.ai.goal.RandomLookAroundGoal;
import net.minecraft.world.entity.ai.goal.WaterAvoidingRandomStrollGoal;
import net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal;
import net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal;
import net.minecraft.world.entity.ai.navigation.FlyingPathNavigation;
import net.minecraft.world.entity.ai.navigation.PathNavigation;
import net.minecraft.world.entity.monster.Monster;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.ServerLevelAccessor;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
import software.bernie.geckolib.animatable.GeoEntity;
import software.bernie.geckolib.core.animatable.instance.AnimatableInstanceCache;
import software.bernie.geckolib.core.animation.AnimatableManager;
import software.bernie.geckolib.core.animation.AnimationController;
import software.bernie.geckolib.core.animation.AnimationState;
import software.bernie.geckolib.core.object.PlayState;
import software.bernie.geckolib.core.animation.RawAnimation;
import software.bernie.geckolib.util.GeckoLibUtil;
public class ManticoreEntity extends Monster implements GeoEntity {
private static final String SUMMONER_KEY = "QueenSummoner";
private static final String PLAYER_SUMMONER_KEY = "PlayerSummoner";
private static final String PLAYER_SUMMON_LIFETIME_KEY = "PlayerSummonLifetime";
private static final int LOST_SUMMONER_GRACE_TICKS = 200;
private static final int TAKEOFF_COOLDOWN_TICKS = 40;
private static final int STING_COOLDOWN_TICKS = 60;
private static final float STING_CHANCE = 0.35F;
private static final int SWARM_RETARGET_INTERVAL = 40;
private static final RawAnimation IDLE_ANIM = RawAnimation.begin().thenLoop("idle");
private static final RawAnimation WALK_ANIM = RawAnimation.begin().thenLoop("walk");
private static final RawAnimation FLY_ANIM = RawAnimation.begin().thenLoop("fly");
private static final RawAnimation BITE_ANIM = RawAnimation.begin().thenPlay("bite");
private static final RawAnimation FLY_BITE_ANIM = RawAnimation.begin().thenPlay("fly_bite");
private static final RawAnimation STING_ANIM = RawAnimation.begin().thenPlay("sting");
private static final RawAnimation FLY_START_ANIM = RawAnimation.begin().thenPlay("fly_start");
private static final RawAnimation FLY_LAND_ANIM = RawAnimation.begin().thenPlay("fly_land");
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
@Nullable
private UUID summonerId;
@Nullable
private UUID playerSummonerId;
private int playerSummonLifetime;
private int playerSummonAge;
private int lostSummonerTicks;
private int takeoffCooldown;
private int stingCooldown;
private boolean wasFlyingLastTick;
public ManticoreEntity(EntityType<? extends ManticoreEntity> type, Level level) {
super(type, level);
this.moveControl = new FlyingMoveControl(this, 10, true);
this.xpReward = 12;
}
@Override
public boolean fireImmune() {
return true;
}
public static AttributeSupplier.Builder createAttributes() {
return Mob.createMobAttributes()
.add(Attributes.MAX_HEALTH, AntarchySettings.manticoreHealth())
.add(Attributes.MOVEMENT_SPEED, 0.3D)
.add(Attributes.FLYING_SPEED, 0.6D)
.add(Attributes.ATTACK_DAMAGE, AntarchySettings.manticoreAttackDamage())
.add(Attributes.FOLLOW_RANGE, 28.0D)
.add(Attributes.KNOCKBACK_RESISTANCE, 0.2D);
}
public static boolean canSpawn(EntityType<ManticoreEntity> type, ServerLevelAccessor level, MobSpawnType spawnReason, BlockPos pos, RandomSource random) {
if (spawnReason == MobSpawnType.SPAWN_EGG || spawnReason == MobSpawnType.SPAWNER
|| spawnReason == MobSpawnType.COMMAND || spawnReason == MobSpawnType.MOB_SUMMONED) {
return true;
}
if (pos.getY() >= 0) {
return false;
}
BlockState below = level.getBlockState(pos.below());
BlockState above = level.getBlockState(pos.above());
boolean floorSupport = !below.is(Blocks.BEDROCK) && below.isFaceSturdy(level, pos.below(), Direction.UP);
boolean ceilingSupport = !above.is(Blocks.BEDROCK) && above.isFaceSturdy(level, pos.above(), Direction.DOWN);
return level.getDifficulty() != Difficulty.PEACEFUL
&& (floorSupport || ceilingSupport)
&& level.isEmptyBlock(pos)
&& Monster.checkMonsterSpawnRules(type, level, spawnReason, pos, random);
}
@Override
protected PathNavigation createNavigation(Level level) {
FlyingPathNavigation navigation = new FlyingPathNavigation(this, level);
navigation.setCanOpenDoors(false);
navigation.setCanFloat(true);
navigation.setCanPassDoors(true);
return navigation;
}
@Override
protected void registerGoals() {
this.goalSelector.addGoal(0, new FloatGoal(this));
this.goalSelector.addGoal(1, new ManticoreAttackGoal(this));
this.goalSelector.addGoal(3, new WaterAvoidingRandomStrollGoal(this, 0.9D));
this.goalSelector.addGoal(4, new LookAtPlayerGoal(this, Player.class, 10.0F));
this.goalSelector.addGoal(5, new RandomLookAroundGoal(this));
this.targetSelector.addGoal(1, new HurtByTargetGoal(this));
this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, this::canTargetEntity));
}
@Override
public SpawnGroupData finalizeSpawn(ServerLevelAccessor level, DifficultyInstance difficulty, MobSpawnType spawnReason, @Nullable SpawnGroupData spawnData, @org.jetbrains.annotations.Nullable net.minecraft.nbt.CompoundTag dataTag) {
ConfiguredMobSpawnUtil.applyConfiguredHealth(this, AntarchySettings.manticoreHealth());
return super.finalizeSpawn(level, difficulty, spawnReason, spawnData, dataTag);
}
public void markQueenSummoned(UUID queenId) {
this.summonerId = queenId;
this.playerSummonerId = null;
this.lostSummonerTicks = 0;
this.setPersistenceRequired();
}
public void markPlayerSummoned(UUID playerId, int lifetimeTicks) {
this.playerSummonerId = playerId;
this.summonerId = null;
this.playerSummonLifetime = lifetimeTicks;
this.playerSummonAge = 0;
this.lostSummonerTicks = 0;
this.setPersistenceRequired();
}
public boolean isSummonedBy(UUID queenId) {
return queenId.equals(this.summonerId);
}
public boolean isQueenSummoned() {
return this.summonerId != null;
}
public boolean isPlayerSummoned() {
return this.playerSummonerId != null;
}
@Override
public boolean isAlliedTo(Entity entity) {
if (entity != null && this.playerSummonerId != null && entity.getUUID().equals(this.playerSummonerId)) {
return true;
}
return super.isAlliedTo(entity);
}
public static int countSummonedBy(ServerLevel level, UUID queenId) {
int count = 0;
for (Entity entity : level.getAllEntities()) {
if (entity instanceof ManticoreEntity manticore && manticore.isAlive() && manticore.isSummonedBy(queenId)) {
count++;
}
}
return count;
}
@Override
public boolean removeWhenFarAway(double distanceToClosestPlayer) {
return this.summonerId == null && super.removeWhenFarAway(distanceToClosestPlayer);
}
@Override
public boolean requiresCustomPersistence() {
return this.summonerId != null || this.playerSummonerId != null || super.requiresCustomPersistence();
}
@Override
public void tick() {
super.tick();
if (this.level().isClientSide) {
return;
}
this.tickQueenSummonInversionRule();
boolean flying = !this.onGround();
if (flying != this.wasFlyingLastTick) {
this.triggerAnim("controller", flying ? "fly_start" : "fly_land");
this.wasFlyingLastTick = flying;
}
if (this.takeoffCooldown > 0) {
this.takeoffCooldown--;
}
if (this.stingCooldown > 0) {
this.stingCooldown--;
}
this.tickQueenSummonCleanup();
this.tickPlayerSummon();
this.tickQueenSummonTarget();
LivingEntity target = this.getTarget();
if (target != null && target.isAlive()) {
if (this.onGround() && this.takeoffCooldown <= 0 && this.distanceToSqr(target) > 6.0D) {
this.takeoff();
}
if (!this.isPlayerSummoned() && !this.isQueenSummoned()
&& this.tickCount % SWARM_RETARGET_INTERVAL == 0) {
this.spreadSwarmTarget(target);
}
}
}
private void tickPlayerSummon() {
if (this.playerSummonerId == null || !(this.level() instanceof ServerLevel serverLevel)) {
return;
}
if (++this.playerSummonAge >= this.playerSummonLifetime) {
this.discard();
return;
}
Entity entity = serverLevel.getEntity(this.playerSummonerId);
if (!(entity instanceof Player player) || !player.isAlive()) {
this.discard();
return;
}
LivingEntity target = player.getLastHurtMob();
if (target == null || !target.isAlive() || !this.canTargetEntity(target)) {
target = player.getLastHurtByMob();
}
if (target != null && target.isAlive() && this.canTargetEntity(target)) {
this.setTarget(target);
} else if (this.getTarget() != null && !this.canTargetEntity(this.getTarget())) {
this.setTarget(null);
}
}
private void tickQueenSummonTarget() {
if (this.summonerId == null || !(this.level() instanceof ServerLevel serverLevel)) {
return;
}
Entity entity = serverLevel.getEntity(this.summonerId);
if (!(entity instanceof QueenEntity queen) || !queen.isAlive()) {
return;
}
LivingEntity target = queen.getTarget();
if (target != null && target.isAlive() && this.canTargetEntity(target)) {
this.setTarget(target);
} else {
this.setTarget(null);
}
}
private boolean canTargetEntity(LivingEntity target) {
if (target == null || target == this || this.isAlliedTo(target) || target.isAlliedTo(this)) {
return false;
}
if (this.playerSummonerId != null) {
if (!(this.level() instanceof ServerLevel serverLevel)) {
return false;
}
Entity entity = serverLevel.getEntity(this.playerSummonerId);
if (!(entity instanceof Player player)) {
return false;
}
return target == player.getLastHurtMob() || target == player.getLastHurtByMob();
}
return true;
}
@Override
public void setTarget(@Nullable LivingEntity target) {
super.setTarget(target != null && !this.canTargetEntity(target) ? null : target);
}
private void tickQueenSummonCleanup() {
if (this.summonerId == null || !(this.level() instanceof ServerLevel serverLevel)) {
return;
}
Entity summoner = serverLevel.getEntity(this.summonerId);
if (summoner instanceof QueenEntity queen && queen.isAlive()) {
this.lostSummonerTicks = 0;
return;
}
if (++this.lostSummonerTicks > LOST_SUMMONER_GRACE_TICKS) {
this.discard();
}
}
private void takeoff() {
this.takeoffCooldown = TAKEOFF_COOLDOWN_TICKS;
this.setDeltaMovement(this.getDeltaMovement().add(0.0D, 0.55D, 0.0D));
this.hasImpulse = true;
LivingEntity target = this.getTarget();
if (target != null) {
Vec3 above = target.position().add(oppositeGravity(this).scale(3.0D));
this.getMoveControl().setWantedPosition(above.x, above.y, above.z, 1.2D);
}
}
private void spreadSwarmTarget(LivingEntity currentTarget) {
List<Player> nearbyPlayers = this.level().getEntitiesOfClass(
Player.class,
this.getBoundingBox().inflate(this.getAttributeValue(Attributes.FOLLOW_RANGE)),
player -> player.isAlive() && this.canAttack(player));
if (nearbyPlayers.size() < 2) {
return;
}
List<ManticoreEntity> swarm = this.level().getEntitiesOfClass(
ManticoreEntity.class,
this.getBoundingBox().inflate(32.0D),
manticore -> manticore != this && manticore.isAlive());
Player bestPlayer = null;
int bestLoad = Integer.MAX_VALUE;
for (Player player : nearbyPlayers) {
int load = 0;
for (ManticoreEntity manticore : swarm) {
if (manticore.getTarget() == player) {
load++;
}
}
if (load < bestLoad) {
bestLoad = load;
bestPlayer = player;
}
}
int currentLoad = 0;
for (ManticoreEntity manticore : swarm) {
if (manticore.getTarget() == currentTarget) {
currentLoad++;
}
}
if (bestPlayer != null && bestPlayer != currentTarget && currentLoad - bestLoad >= 2) {
this.setTarget(bestPlayer);
}
}
private static Vec3 oppositeGravity(Entity entity) {
return AntarchyGravityApi.isGravityInverted(entity) ? new Vec3(0.0D, -1.0D, 0.0D) : new Vec3(0.0D, 1.0D, 0.0D);
}
private void tickQueenSummonInversionRule() {
if (!this.isQueenSummoned() || this.canUseQueenSummonInversion()) {
return;
}
if (this.hasEffect(AntarchyObjects.INVERTED_EFFECT.get())) {
this.removeEffect(AntarchyObjects.INVERTED_EFFECT.get());
}
if (AntarchyGravityApi.isGravityInverted(this) && AntarchyGravityApi.isGravityForced(this)) {
AntarchyGravityApi.setGravityDirection(this, AntarchyGravityDirection.DOWN, false);
}
this.setNoGravity(false);
}
private boolean canUseQueenSummonInversion() {
return ThoraxisUndersideManager.isThoraxis(this.level())
&& this.getY() < ThoraxisUndersideManager.GRAVITY_FLIP_Y + 4;
}
void performSting(LivingEntity enemy) {
this.swing(InteractionHand.MAIN_HAND);
if (enemy.hurt(this.damageSources().mobAttack(this), (float) this.getAttributeValue(Attributes.ATTACK_DAMAGE))) {
enemy.addEffect(new MobEffectInstance(MobEffects.POISON, AntarchySettings.manticoreStingPoisonTicks(), 0), this);
}
this.stingCooldown = STING_COOLDOWN_TICKS;
this.triggerAnim("controller", "sting");
}
boolean stingReady() {
return this.stingCooldown <= 0;
}
@Override
public boolean doHurtTarget(Entity target) {
boolean hurt = super.doHurtTarget(target);
if (hurt) {
this.triggerAnim("controller", this.onGround() ? "bite" : "fly_bite");
}
return hurt;
}
@Override
public boolean causeFallDamage(float fallDistance, float multiplier, DamageSource source) {
return false;
}
@Override
public boolean canBeAffected(MobEffectInstance effectInstance) {
if (this.isQueenSummoned() && effectInstance.getEffect() == AntarchyObjects.INVERTED_EFFECT.get()) {
return this.canUseQueenSummonInversion();
}
return super.canBeAffected(effectInstance);
}
@Override
public boolean isInWall() {
return false;
}
@Override
public void addAdditionalSaveData(CompoundTag tag) {
super.addAdditionalSaveData(tag);
if (this.summonerId != null) {
tag.putUUID(SUMMONER_KEY, this.summonerId);
}
if (this.playerSummonerId != null) {
tag.putUUID(PLAYER_SUMMONER_KEY, this.playerSummonerId);
tag.putInt(PLAYER_SUMMON_LIFETIME_KEY, this.playerSummonLifetime);
}
}
@Override
public void readAdditionalSaveData(CompoundTag tag) {
super.readAdditionalSaveData(tag);
this.summonerId = tag.hasUUID(SUMMONER_KEY) ? tag.getUUID(SUMMONER_KEY) : null;
this.playerSummonerId = tag.hasUUID(PLAYER_SUMMONER_KEY) ? tag.getUUID(PLAYER_SUMMONER_KEY) : null;
this.playerSummonLifetime = tag.getInt(PLAYER_SUMMON_LIFETIME_KEY);
this.playerSummonAge = 0;
this.lostSummonerTicks = 0;
}
@Override
public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
controllers.add(new AnimationController<>(this, "controller", 3, this::locomotionPredicate)
.triggerableAnim("bite", BITE_ANIM)
.triggerableAnim("fly_bite", FLY_BITE_ANIM)
.triggerableAnim("sting", STING_ANIM)
.triggerableAnim("fly_start", FLY_START_ANIM)
.triggerableAnim("fly_land", FLY_LAND_ANIM));
}
private PlayState locomotionPredicate(AnimationState<ManticoreEntity> state) {
if (!this.onGround()) {
return state.setAndContinue(FLY_ANIM);
}
if (state.isMoving()) {
return state.setAndContinue(WALK_ANIM);
}
return state.setAndContinue(IDLE_ANIM);
}
@Override
public AnimatableInstanceCache getAnimatableInstanceCache() {
return this.geoCache;
}
private static class ManticoreAttackGoal extends MeleeAttackGoal {
private final ManticoreEntity manticore;
ManticoreAttackGoal(ManticoreEntity manticore) {
super(manticore, 1.1D, true);
this.manticore = manticore;
}
@Override
protected void checkAndPerformAttack(LivingEntity enemy, double distance) {
if (this.isTimeToAttack()) {
this.resetAttackCooldown();
if (this.manticore.onGround() && this.manticore.stingReady() && this.manticore.getRandom().nextFloat() < STING_CHANCE) {
this.manticore.performSting(enemy);
return;
}
this.manticore.swing(InteractionHand.MAIN_HAND);
this.manticore.doHurtTarget(enemy);
return;
}
super.checkAndPerformAttack(enemy, distance);
}
}
}
@@ -0,0 +1,21 @@
package com.craisinlord.antarchy.content.entity;
import com.craisinlord.antarchy.Antarchy;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.vehicle.Boat;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.Level;
public class RoyalBoatEntity extends Boat {
public RoyalBoatEntity(EntityType<? extends Boat> entityType, Level level) {
super(entityType, level);
this.setVariant(Type.OAK);
}
@Override
public Item getDropItem() {
return BuiltInRegistries.ITEM.get(new ResourceLocation(Antarchy.MODID, "royal_boat"));
}
}
@@ -0,0 +1,22 @@
package com.craisinlord.antarchy.content.entity;
import com.craisinlord.antarchy.Antarchy;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.vehicle.Boat;
import net.minecraft.world.entity.vehicle.ChestBoat;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.Level;
public class RoyalChestBoatEntity extends ChestBoat {
public RoyalChestBoatEntity(EntityType<? extends Boat> entityType, Level level) {
super(entityType, level);
this.setVariant(Type.OAK);
}
@Override
public Item getDropItem() {
return BuiltInRegistries.ITEM.get(new ResourceLocation(Antarchy.MODID, "royal_chest_boat"));
}
}
@@ -0,0 +1,21 @@
package com.craisinlord.antarchy.content.entity;
import com.craisinlord.antarchy.Antarchy;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.vehicle.Boat;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.Level;
public class TruffaloBoatEntity extends Boat {
public TruffaloBoatEntity(EntityType<? extends Boat> entityType, Level level) {
super(entityType, level);
this.setVariant(Type.OAK);
}
@Override
public Item getDropItem() {
return BuiltInRegistries.ITEM.get(new ResourceLocation(Antarchy.MODID, "truffalo_boat"));
}
}
@@ -0,0 +1,22 @@
package com.craisinlord.antarchy.content.entity;
import com.craisinlord.antarchy.Antarchy;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.vehicle.Boat;
import net.minecraft.world.entity.vehicle.ChestBoat;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.Level;
public class TruffaloChestBoatEntity extends ChestBoat {
public TruffaloChestBoatEntity(EntityType<? extends Boat> entityType, Level level) {
super(entityType, level);
this.setVariant(Type.OAK);
}
@Override
public Item getDropItem() {
return BuiltInRegistries.ITEM.get(new ResourceLocation(Antarchy.MODID, "truffalo_chest_boat"));
}
}
@@ -0,0 +1,283 @@
package com.craisinlord.antarchy.content.entity;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.AntarchySoundEvents;
import java.util.Optional;
import net.minecraft.core.Direction;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.util.RandomSource;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.MobSpawnType;
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.ai.goal.FloatGoal;
import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal;
import net.minecraft.world.entity.ai.goal.MeleeAttackGoal;
import net.minecraft.world.entity.ai.goal.RandomLookAroundGoal;
import net.minecraft.world.entity.ai.goal.RandomStrollGoal;
import net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal;
import net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal;
import net.minecraft.world.entity.monster.Monster;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.ServerLevelAccessor;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.MultifaceBlock;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.core.BlockPos;
import net.minecraft.world.phys.Vec3;
import software.bernie.geckolib.animatable.GeoEntity;
import software.bernie.geckolib.core.animatable.instance.AnimatableInstanceCache;
import software.bernie.geckolib.core.animation.AnimatableManager;
import software.bernie.geckolib.core.animation.AnimationController;
import software.bernie.geckolib.core.animation.AnimationState;
import software.bernie.geckolib.core.object.PlayState;
import software.bernie.geckolib.core.animation.RawAnimation;
import software.bernie.geckolib.util.GeckoLibUtil;
public class WormEntity extends Monster implements GeoEntity {
private static final ResourceKey<Biome> WORMY_CAVES = ResourceKey.create(
Registries.BIOME,
new ResourceLocation(Antarchy.MODID, "wormy_caves")
);
private static final ResourceLocation LOAM_ID = new ResourceLocation(Antarchy.MODID, "loam");
private static final ResourceLocation MUCUS_ID = new ResourceLocation(Antarchy.MODID, "mucus");
private static final int MUCUS_TRAIL_INTERVAL = 6;
private static final double MUCUS_TRAIL_MIN_DISTANCE_SQR = 0.36D;
private static final EntityDataAccessor<Integer> ANIMATION_STATE = SynchedEntityData.defineId(WormEntity.class, EntityDataSerializers.INT);
private static final int ANIM_INGROUND = 0;
private static final int ANIM_CRAWL = 1;
private static final int ANIM_ATTACK = 2;
private static final RawAnimation INGROUND_ANIM = RawAnimation.begin().thenLoop("inground");
private static final RawAnimation CRAWL_ANIM = RawAnimation.begin().thenLoop("crawl");
private static final RawAnimation ATTACK_ANIM = RawAnimation.begin().thenPlay("attack");
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
private int attackAnimationTicks;
private int mucusTrailCooldown;
private Vec3 lastMucusTrailPosition;
public WormEntity(EntityType<? extends WormEntity> entityType, Level level) {
super(entityType, level);
this.xpReward = 8;
}
public static AttributeSupplier.Builder createAttributes() {
return Monster.createMonsterAttributes()
.add(Attributes.MAX_HEALTH, 36.0D)
.add(Attributes.MOVEMENT_SPEED, 0.22D)
.add(Attributes.ATTACK_DAMAGE, 7.0D)
.add(Attributes.FOLLOW_RANGE, 28.0D)
.add(Attributes.KNOCKBACK_RESISTANCE, 0.45D);
}
public static boolean canSpawn(EntityType<WormEntity> entityType, ServerLevelAccessor level, MobSpawnType spawnReason, BlockPos pos, RandomSource random) {
if ((spawnReason == MobSpawnType.NATURAL || spawnReason == MobSpawnType.CHUNK_GENERATION)
&& !level.getBiome(pos).is(WORMY_CAVES)) {
return false;
}
Optional<Block> loam = BuiltInRegistries.BLOCK.getOptional(LOAM_ID);
BlockState floor = level.getBlockState(pos.below());
if (!floor.is(Blocks.MUD) && (loam.isEmpty() || !floor.is(loam.get()))) {
return false;
}
return level.getBlockState(pos.above()).getCollisionShape(level, pos.above()).isEmpty()
&& level.getBlockState(pos.above(2)).getCollisionShape(level, pos.above(2)).isEmpty()
&& Monster.checkMonsterSpawnRules(entityType, level, spawnReason, pos, random);
}
@Override
protected void defineSynchedData() {
super.defineSynchedData();
this.entityData.define(ANIMATION_STATE, ANIM_INGROUND);
}
@Override
protected void registerGoals() {
this.goalSelector.addGoal(1, new FloatGoal(this));
this.goalSelector.addGoal(2, new MeleeAttackGoal(this, 1.05D, false));
this.goalSelector.addGoal(3, new RandomStrollGoal(this, 0.8D));
this.goalSelector.addGoal(4, new LookAtPlayerGoal(this, Player.class, 8.0F));
this.goalSelector.addGoal(5, new RandomLookAroundGoal(this));
this.targetSelector.addGoal(1, new HurtByTargetGoal(this));
this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true));
}
@Override
public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
controllers.add(new AnimationController<>(this, "main_controller", 0, this::mainAnimController)
.triggerableAnim("attack", ATTACK_ANIM));
}
private PlayState mainAnimController(AnimationState<WormEntity> state) {
return switch (this.entityData.get(ANIMATION_STATE)) {
case ANIM_CRAWL -> state.setAndContinue(CRAWL_ANIM);
case ANIM_ATTACK -> state.setAndContinue(ATTACK_ANIM);
default -> state.setAndContinue(INGROUND_ANIM);
};
}
@Override
public AnimatableInstanceCache getAnimatableInstanceCache() {
return this.geoCache;
}
@Override
public boolean doHurtTarget(Entity target) {
boolean hurt = super.doHurtTarget(target);
if (hurt && target instanceof LivingEntity) {
this.attackAnimationTicks = 45;
this.entityData.set(ANIMATION_STATE, ANIM_ATTACK);
this.triggerAnim("main_controller", "attack");
this.playSound(AntarchySoundEvents.MOLEWORM_ATTACK.get(), 0.45F, 0.85F + this.random.nextFloat() * 0.2F);
}
return hurt;
}
@Override
public void tick() {
super.tick();
if (this.level().isClientSide) {
return;
}
this.tickMucusTrail();
if (this.attackAnimationTicks > 0) {
this.attackAnimationTicks--;
}
this.updateAnimationState();
}
private void tickMucusTrail() {
if (this.mucusTrailCooldown > 0) {
this.mucusTrailCooldown--;
}
Vec3 velocity = this.getDeltaMovement();
if (this.mucusTrailCooldown > 0
|| !this.isAlive()
|| !this.onGround()
|| this.isInWaterOrBubble()
|| this.isPassenger()
|| velocity.horizontalDistanceSqr() < 0.0025D) {
return;
}
Vec3 currentPosition = this.position();
if (this.lastMucusTrailPosition != null
&& this.lastMucusTrailPosition.distanceToSqr(currentPosition) < MUCUS_TRAIL_MIN_DISTANCE_SQR) {
return;
}
this.lastMucusTrailPosition = currentPosition;
this.mucusTrailCooldown = MUCUS_TRAIL_INTERVAL;
Vec3 horizontalDirection = new Vec3(velocity.x, 0.0D, velocity.z).normalize();
BlockPos trailPos = BlockPos.containing(
this.getX() - horizontalDirection.x * 0.45D,
this.getBoundingBox().minY + 0.01D,
this.getZ() - horizontalDirection.z * 0.45D
);
boolean placed = this.placeFloorMucus(trailPos);
if (placed && this.random.nextFloat() < 0.35F) {
Vec3 side = new Vec3(-horizontalDirection.z, 0.0D, horizontalDirection.x);
double sideOffset = this.random.nextBoolean() ? 0.55D : -0.55D;
this.placeFloorMucus(BlockPos.containing(
trailPos.getX() + 0.5D + side.x * sideOffset,
trailPos.getY(),
trailPos.getZ() + 0.5D + side.z * sideOffset
));
}
}
private boolean placeFloorMucus(BlockPos pos) {
Optional<Block> mucus = BuiltInRegistries.BLOCK.getOptional(MUCUS_ID);
if (mucus.isEmpty() || !(mucus.get() instanceof MultifaceBlock mucusBlock)) {
return false;
}
BlockState existing = this.level().getBlockState(pos);
var floorFace = MultifaceBlock.getFaceProperty(Direction.DOWN);
if (existing.is(mucusBlock)) {
if (existing.getValue(floorFace)) {
return true;
}
BlockState merged = existing.setValue(floorFace, true);
if (merged.canSurvive(this.level(), pos)) {
this.level().setBlock(pos, merged, Block.UPDATE_ALL);
return true;
}
return false;
}
if (!existing.isAir()) {
return false;
}
BlockPos supportPos = pos.below();
BlockState support = this.level().getBlockState(supportPos);
if (!support.isFaceSturdy(this.level(), supportPos, Direction.UP)) {
return false;
}
BlockState mucusState = mucusBlock.defaultBlockState().setValue(floorFace, true);
if (!mucusState.canSurvive(this.level(), pos)) {
return false;
}
this.level().setBlock(pos, mucusState, Block.UPDATE_ALL);
return true;
}
private void updateAnimationState() {
if (this.attackAnimationTicks > 0) {
this.entityData.set(ANIMATION_STATE, ANIM_ATTACK);
return;
}
Vec3 velocity = this.getDeltaMovement();
if (this.getTarget() != null || velocity.horizontalDistanceSqr() > 1.0E-4D || this.walkAnimation.speed() > 0.03F) {
this.entityData.set(ANIMATION_STATE, ANIM_CRAWL);
return;
}
this.entityData.set(ANIMATION_STATE, ANIM_INGROUND);
}
@Override
protected Entity.MovementEmission getMovementEmission() {
return Entity.MovementEmission.EVENTS;
}
@Override
protected SoundEvent getAmbientSound() {
return AntarchySoundEvents.MOLEWORM_AMBIENT.get();
}
@Override
protected SoundEvent getHurtSound(DamageSource damageSource) {
return AntarchySoundEvents.MOLEWORM_HURT.get();
}
@Override
protected SoundEvent getDeathSound() {
return AntarchySoundEvents.MOLEWORM_HURT.get();
}
@Override
protected void playStepSound(BlockPos pos, BlockState state) {
this.playSound(AntarchySoundEvents.MOLEWORM_DIG.get(), 0.15F, 0.9F + this.random.nextFloat() * 0.2F);
}
}
@@ -0,0 +1,91 @@
package com.craisinlord.antarchy.content.entity;
import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.content.item.WormHookTetherManager;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.projectile.ThrowableItemProjectile;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.EntityHitResult;
import net.minecraft.world.phys.HitResult;
/**
* Thrown out by the worm hook item. Sticks into the first block it hits and stays there,
* anchoring a swing rope back to the thrower until the player detaches.
*/
public class WormHookProjectileEntity extends ThrowableItemProjectile {
private boolean stuck = false;
public WormHookProjectileEntity(EntityType<? extends WormHookProjectileEntity> entityType, Level level) {
super(entityType, level);
}
public WormHookProjectileEntity(EntityType<? extends WormHookProjectileEntity> entityType, LivingEntity owner, Level level) {
super(entityType, owner, level);
}
@Override
protected Item getDefaultItem() {
return AntarchyObjects.WORM_HOOK.get();
}
@Override
public void tick() {
if (this.stuck) {
this.setDeltaMovement(net.minecraft.world.phys.Vec3.ZERO);
}
super.tick();
if (!this.level().isClientSide && !this.stuck && this.tickCount > 200) {
this.discard();
}
}
@Override
protected void onHit(HitResult hitResult) {
if (this.level().isClientSide || this.stuck) {
return;
}
if (hitResult instanceof EntityHitResult) {
this.discard();
return;
}
if (!(hitResult instanceof BlockHitResult blockHit)) {
return;
}
if (!(this.getOwner() instanceof ServerPlayer player)) {
this.discard();
return;
}
this.stuck = true;
this.setDeltaMovement(net.minecraft.world.phys.Vec3.ZERO);
this.setNoGravity(true);
this.setPos(blockHit.getLocation());
boolean attached = WormHookTetherManager.attach(player, this);
if (!attached) {
this.discard();
return;
}
this.level().playSound(null, this.blockPosition(), SoundEvents.FISHING_BOBBER_THROW, SoundSource.PLAYERS, 0.8F, 1.4F);
}
public boolean isStuck() {
return this.stuck;
}
public void removeHook() {
this.discard();
}
}
@@ -0,0 +1,68 @@
package com.craisinlord.antarchy.content.entity.royal;
import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.content.AntarchySoundEvents;
import com.craisinlord.antarchy.content.entity.royal.beam.RoyalBeamElement;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
import net.minecraft.world.level.Level;
public class PrinceEntity extends RoyalMountEntity {
public PrinceEntity(EntityType<? extends PrinceEntity> entityType, Level level) {
super(entityType, level);
}
public static AttributeSupplier.Builder createAttributes() {
return RoyalMountEntity.createBaseAttributes(
AntarchySettings.princeHealth(), AntarchySettings.princeAttackDamage(),
AntarchySettings.princeMovementSpeed(), AntarchySettings.princeFlyingSpeed(),
AntarchySettings.princeArmor(), AntarchySettings.princeKnockbackResistance(),
AntarchySettings.princeFollowRange());
}
@Override
protected RoyalBeamElement boltElement(int lateralIndex) {
return switch (lateralIndex) {
case -1 -> RoyalBeamElement.FIRE;
case 1 -> RoyalBeamElement.ICE;
default -> RoyalBeamElement.LIGHTNING;
};
}
@Override
protected String geoName() {
return "prince";
}
@Override
protected EntityType<? extends RoyalBoltEntity> boltType() {
return AntarchyObjects.ROYAL_BOLT.get();
}
@Override
protected SoundEvent idleSound() {
return AntarchySoundEvents.PRINCE_IDLE.get();
}
@Override
protected SoundEvent biteSound() {
return AntarchySoundEvents.PRINCE_BITE.get();
}
@Override
protected SoundEvent shootSound() {
return AntarchySoundEvents.PRINCE_SHOOT.get();
}
@Override
protected SoundEvent flySound() {
return AntarchySoundEvents.PRINCE_FLY.get();
}
@Override
protected SoundEvent stepSound() {
return AntarchySoundEvents.PRINCE_STEP.get();
}
}
@@ -0,0 +1,68 @@
package com.craisinlord.antarchy.content.entity.royal;
import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.content.AntarchySoundEvents;
import com.craisinlord.antarchy.content.entity.royal.beam.RoyalBeamElement;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
import net.minecraft.world.level.Level;
public class PrincessEntity extends RoyalMountEntity {
public PrincessEntity(EntityType<? extends PrincessEntity> entityType, Level level) {
super(entityType, level);
}
public static AttributeSupplier.Builder createAttributes() {
return RoyalMountEntity.createBaseAttributes(
AntarchySettings.princessHealth(), AntarchySettings.princessAttackDamage(),
AntarchySettings.princessMovementSpeed(), AntarchySettings.princessFlyingSpeed(),
AntarchySettings.princessArmor(), AntarchySettings.princessKnockbackResistance(),
AntarchySettings.princessFollowRange());
}
@Override
protected RoyalBeamElement boltElement(int lateralIndex) {
return switch (lateralIndex) {
case -1 -> RoyalBeamElement.FIRE;
case 1 -> RoyalBeamElement.ICE;
default -> RoyalBeamElement.LIGHTNING;
};
}
@Override
protected String geoName() {
return "princess";
}
@Override
protected EntityType<? extends RoyalBoltEntity> boltType() {
return AntarchyObjects.ROYAL_BOLT.get();
}
@Override
protected SoundEvent idleSound() {
return AntarchySoundEvents.PRINCESS_IDLE.get();
}
@Override
protected SoundEvent biteSound() {
return AntarchySoundEvents.PRINCESS_BITE.get();
}
@Override
protected SoundEvent shootSound() {
return AntarchySoundEvents.PRINCESS_SHOOT.get();
}
@Override
protected SoundEvent flySound() {
return AntarchySoundEvents.PRINCESS_FLY.get();
}
@Override
protected SoundEvent stepSound() {
return AntarchySoundEvents.PRINCESS_STEP.get();
}
}
@@ -0,0 +1,542 @@
package com.craisinlord.antarchy.content.entity.royal;
import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.AntarchySoundEvents;
import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.content.AntarchyTags;
import com.craisinlord.antarchy.content.entity.ManticoreEntity;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import net.minecraft.core.BlockPos;
import net.minecraft.core.particles.DustParticleOptions;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.BossEvent;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.attributes.AttributeInstance;
import net.minecraft.world.entity.ai.attributes.AttributeModifier;
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.MobSpawnType;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
import com.craisinlord.antarchy.content.entity.royal.beam.RoyalBeamSettings;
import com.craisinlord.antarchy.content.entity.royal.beam.RoyalBeamTerrainMode;
import com.craisinlord.antarchy.content.entity.royal.attack.RoyalAttackLane;
import com.craisinlord.antarchy.content.entity.royal.attack.RoyalEffectController;
import com.craisinlord.antarchy.content.entity.portal.DimensionalTearEntity;
import com.craisinlord.antarchy.content.time.TimeDilationApi;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.player.Player;
public class QueenEntity extends RoyalBossEntity {
private static final String SUMMON_COOLDOWN_KEY = "ManticoreSummonCooldownTicks";
private static final int FAILED_SUMMON_RETRY_TICKS = 20;
private static final int POSITION_ATTEMPTS_PER_MANTICORE = 8;
private static final ResourceLocation ACCEL_SPEED_ID = new ResourceLocation("antarchy", "queen_royal_acceleration_speed");
private static final ResourceLocation ACCEL_FLY_ID = new ResourceLocation("antarchy", "queen_royal_acceleration_fly");
private static final DustParticleOptions ACCEL_DUST = new DustParticleOptions(new org.joml.Vector3f(1.0F, 0.15F, 0.15F), 2.0F);
private static final int GRAVITY_STOMP_COOLDOWN = 180;
private static final int TIME_FIELD_COOLDOWN = 260;
private static final int MOMENTUM_LOCK_COOLDOWN = 420;
private static final int MOMENTUM_LOCK_DURATION = 90;
private static final double MOMENTUM_LOCK_RADIUS = 26.0D;
private static final int CRUSHING_GRAVITY_COOLDOWN = 360;
private static final int CRUSHING_GRAVITY_DURATION = 120;
private static final double CRUSHING_GRAVITY_RADIUS = 18.0D;
private static final double CRUSHING_GRAVITY_STRENGTH = 0.12D;
private static final int ACCELERATION_COOLDOWN = 500;
private static final int ACCELERATION_DURATION = 140;
private static final int BLACK_HOLE_COOLDOWN = 340;
private int manticoreSummonCooldownTicks;
private int gravityStompCooldownTicks;
private int timeFieldCooldownTicks;
private int momentumLockCooldownTicks;
private int crushingGravityCooldownTicks;
private int accelerationCooldownTicks;
private int blackHoleCooldownTicks;
private int idleWanderCooldownTicks;
private int finalTimeControlTicks;
private final RoyalEffectController royalEffects = new RoyalEffectController();
private final Map<UUID, Vec3> frozenVelocities = new HashMap<>();
public QueenEntity(EntityType<? extends QueenEntity> entityType, Level level) {
super(entityType, level);
}
public static AttributeSupplier.Builder createAttributes() {
return createBaseAttributes(AntarchySettings.queenHealth(), AntarchySettings.queenAttackDamage());
}
@Override
protected String geoName() {
return "queen";
}
@Override
protected boolean isFlyingBoss() {
return true;
}
@Override
protected SoundEvent royalFlyLoopSound() {
return AntarchySoundEvents.QUEEN_FLY_LOOP.get();
}
@Override
protected BossEvent.BossBarColor bossBarColor() {
return BossEvent.BossBarColor.PURPLE;
}
@Override
protected SoundEvent royalIdleSound() {
return AntarchySoundEvents.QUEEN_IDLE.get();
}
@Override
protected SoundEvent royalHurtSound() {
return AntarchySoundEvents.QUEEN_HURT.get();
}
@Override
protected SoundEvent royalDeathSound() {
return AntarchySoundEvents.QUEEN_DEATH.get();
}
@Override
protected SoundEvent royalBiteSound() {
return AntarchySoundEvents.QUEEN_BITE.get();
}
@Override
protected SoundEvent royalBeamShootSound() { return AntarchySoundEvents.QUEEN_BEAM_SHOOT.get(); }
@Override
protected SoundEvent royalBeamStartSound() { return AntarchySoundEvents.QUEEN_BEAM_START.get(); }
@Override
protected SoundEvent royalBeamLoopSound() { return AntarchySoundEvents.QUEEN_BEAM_LOOP.get(); }
@Override
protected SoundEvent royalBeamEndSound() { return AntarchySoundEvents.QUEEN_BEAM_END.get(); }
@Override
protected RoyalBeamSettings royalBeamSettings() {
return new RoyalBeamSettings(AntarchySettings.queenBeamRange(), AntarchySettings.queenBeamTracking(), 7.5D,
AntarchySettings.queenBeamDurationTicks(), AntarchySettings.queenBeamTravelTicks(), AntarchySettings.queenBeamCooldownTicks(), 6.0F, 6.0F,
(float) AntarchySettings.queenBeamDamage(), 1.0F, 3, 100.0D,
(float) AntarchySettings.queenBeamTerrainRadius(), 4.0F, AntarchySettings.queenBeamTerrainCap(),
1.0F, 0.08F, 15.0F, true, true);
}
@Override
protected RoyalBeamTerrainMode royalBeamTerrainMode() {
return RoyalBeamTerrainMode.DESTROY;
}
public static boolean isFieldImmune(Entity entity) {
return entity instanceof RoyalBossEntity
|| entity.getType().is(AntarchyTags.Entities.TIME_DILATION_IMMUNE);
}
@Override
public void tick() {
super.tick();
if (this.level().isClientSide || this.isDeadOrDying()) {
return;
}
LivingEntity target = this.getTarget();
if (target == null || !target.isAlive() || !this.canAttack(target)) {
this.tickIdleWander();
}
this.tickManticoreSummon();
this.tickQueenAbilities();
this.tickFinalTimeControl();
this.royalEffects.tick();
}
private void tickIdleWander() {
if (this.idleWanderCooldownTicks-- > 0) {
return;
}
this.idleWanderCooldownTicks = 80 + this.random.nextInt(80);
double angle = this.random.nextDouble() * Mth.TWO_PI;
double radius = 6.0D + this.random.nextDouble() * 14.0D;
double wx = this.getX() + Math.cos(angle) * radius;
double wz = this.getZ() + Math.sin(angle) * radius;
this.getMoveControl().setWantedPosition(wx, this.groundYBelow(wx, wz), wz, 0.7D);
}
private int cooldown(int base) {
return this.royalEffects.active("acceleration") ? Math.max(20, base / 2) : base;
}
private void tickManticoreSummon() {
if (this.manticoreSummonCooldownTicks > 0) {
this.manticoreSummonCooldownTicks--;
return;
}
if (!(this.level() instanceof ServerLevel serverLevel)) {
return;
}
LivingEntity target = this.getTarget();
if (target == null || !target.isAlive() || !this.canAttack(target)) {
return;
}
int currentCount = ManticoreEntity.countSummonedBy(serverLevel, this.getUUID());
int cap = AntarchySettings.queenManticoreCap();
if (currentCount >= cap) {
this.manticoreSummonCooldownTicks = FAILED_SUMMON_RETRY_TICKS;
return;
}
int summonCount = Math.min(AntarchySettings.queenManticoreSummonCount(), cap - currentCount);
int spawned = this.spawnManticoreTears(serverLevel, target, summonCount);
this.manticoreSummonCooldownTicks = spawned > 0
? this.cooldown(Math.max(20, AntarchySettings.queenManticoreSummonCooldownTicks()))
: FAILED_SUMMON_RETRY_TICKS;
if (spawned > 0) {
this.triggerAnim("body_action", "minion_spawn");
this.playRoyalSound(AntarchySoundEvents.QUEEN_ROAR.get(), 0.8F + this.random.nextFloat() * 0.12F);
serverLevel.sendParticles(ParticleTypes.PORTAL, this.getX(), this.getY() + 2.0D, this.getZ(),
36, 3.0D, 2.0D, 3.0D, 0.15D);
}
}
private int spawnManticoreTears(ServerLevel level, LivingEntity target, int count) {
int pairs = Math.max(1, Math.min(3, (count + 1) / 2));
int created = 0;
for (int i = 0; i < pairs; i++) {
double angle = this.random.nextDouble() * Mth.TWO_PI;
double distance = Math.max(8.0D, AntarchySettings.queenManticoreSummonRange() * 0.65D);
Vec3 firstPos = this.position().add(Math.cos(angle) * distance, 1.0D, Math.sin(angle) * distance);
Vec3 secondPos = this.position().add(Math.cos(angle + Math.PI) * distance, 1.0D, Math.sin(angle + Math.PI) * distance);
DimensionalTearEntity first = DimensionalTearEntity.createQueenManticoreTear(level, firstPos,
(float) Math.toDegrees(angle), 240, this.getUUID(), Math.min(3, count));
DimensionalTearEntity second = DimensionalTearEntity.createQueenManticoreTear(level, secondPos,
(float) Math.toDegrees(angle + Math.PI), 240, this.getUUID(), Math.min(3, count));
first.linkTo(second);
second.linkTo(first);
level.addFreshEntity(first);
level.addFreshEntity(second);
created++;
}
return created;
}
private void tickQueenAbilities() {
if (this.gravityStompCooldownTicks > 0) this.gravityStompCooldownTicks--;
if (this.timeFieldCooldownTicks > 0) this.timeFieldCooldownTicks--;
if (this.momentumLockCooldownTicks > 0) this.momentumLockCooldownTicks--;
if (this.crushingGravityCooldownTicks > 0) this.crushingGravityCooldownTicks--;
if (this.accelerationCooldownTicks > 0) this.accelerationCooldownTicks--;
if (this.blackHoleCooldownTicks > 0) this.blackHoleCooldownTicks--;
if (!(this.level() instanceof ServerLevel level)) {
return;
}
LivingEntity target = this.getTarget();
if (target == null || !target.isAlive()) {
return;
}
Phase phase = this.phase();
boolean majorBusy = this.royalEffects.active("momentum_lock") || this.royalEffects.active("crushing_gravity");
boolean allowOverlap = phase != Phase.ONE;
String[] candidates = phase == Phase.ONE
? new String[] {"gravity_stomp", "time_field", "black_hole"}
: new String[] {"gravity_stomp", "time_field", "black_hole", "momentum_lock", "crushing_gravity", "acceleration"};
String selected = this.attackScheduler.chooseWeighted(candidates, id -> {
if (!this.queenAttackReady(id, phase, majorBusy, allowOverlap)) return 0;
return switch (id) {
case "black_hole", "crushing_gravity" -> phase == Phase.THREE ? 5 : 3;
case "momentum_lock", "acceleration" -> phase == Phase.THREE ? 4 : 2;
default -> 4;
};
}, this.random);
if (selected == null) return;
int cooldown = switch (selected) {
case "gravity_stomp" -> GRAVITY_STOMP_COOLDOWN;
case "time_field" -> TIME_FIELD_COOLDOWN;
case "black_hole" -> BLACK_HOLE_COOLDOWN;
case "momentum_lock" -> MOMENTUM_LOCK_COOLDOWN;
case "crushing_gravity" -> CRUSHING_GRAVITY_COOLDOWN;
default -> ACCELERATION_COOLDOWN;
};
if (!this.beginRoyalAttack(selected, RoyalAttackLane.HAZARD, this.cooldown(cooldown),
10, 1, 14, new com.craisinlord.antarchy.content.entity.royal.attack.RoyalAttackScheduler.Action() {
@Override public void onStart() {
if ("gravity_stomp".equals(selected)) {
QueenEntity.this.triggerAnim("body_action", "stomp");
} else {
QueenEntity.this.triggerAnim("body_action", "wing_gust");
}
}
@Override public void onActive(int elapsedTicks) {
QueenEntity.this.performSelectedQueenAttack(selected, phase, cooldown, level, target);
}
})) return;
}
private void performSelectedQueenAttack(String selected, Phase phase, int cooldown,
ServerLevel level, LivingEntity target) {
switch (selected) {
case "gravity_stomp" -> { this.gravityStompCooldownTicks = cooldown; this.performGravityStomp(level, target); }
case "time_field" -> {
this.timeFieldCooldownTicks = cooldown;
double fieldRate = phase == Phase.THREE ? 0.1D : 0.4D;
TimeDilationApi.createField(level, target.position(), 12.0D, fieldRate, 140 + phase.ordinal() * 40);
}
case "black_hole" -> { this.blackHoleCooldownTicks = cooldown; this.castBlackHole(level, target); }
case "momentum_lock" -> { this.momentumLockCooldownTicks = cooldown; this.startMomentumLock(level); }
case "crushing_gravity" -> {
this.crushingGravityCooldownTicks = cooldown;
this.royalEffects.start("crushing_gravity", CRUSHING_GRAVITY_DURATION,
() -> {}, this::tickCrushingGravity, () -> {});
this.playRoyalSound(AntarchySoundEvents.QUEEN_ROAR.get(), 0.6F);
}
case "acceleration" -> { this.accelerationCooldownTicks = cooldown; this.startRoyalAcceleration(); }
}
}
private boolean queenAttackReady(String id, Phase phase, boolean majorBusy, boolean allowOverlap) {
if (!allowOverlap && majorBusy) return false;
return switch (id) {
case "gravity_stomp" -> this.gravityStompCooldownTicks <= 0;
case "time_field" -> this.timeFieldCooldownTicks <= 0;
case "black_hole" -> this.blackHoleCooldownTicks <= 0;
case "momentum_lock" -> !this.royalEffects.active("momentum_lock") && this.momentumLockCooldownTicks <= 0;
case "crushing_gravity" -> phase != Phase.ONE && !this.royalEffects.active("crushing_gravity") && this.crushingGravityCooldownTicks <= 0;
case "acceleration" -> phase == Phase.THREE && !this.royalEffects.active("acceleration") && this.accelerationCooldownTicks <= 0;
default -> false;
};
}
private void tickFinalTimeControl() {
if (this.phase() != Phase.THREE || !(this.level() instanceof ServerLevel level)) {
return;
}
if (!this.royalEffects.active("acceleration") && this.accelerationCooldownTicks <= 0) {
this.startRoyalAcceleration();
}
if (this.finalTimeControlTicks-- <= 0) {
this.finalTimeControlTicks = AntarchySettings.queenFinalTimeFieldCooldownTicks();
LivingEntity target = this.getTarget();
if (target != null && target.isAlive()) {
TimeDilationApi.createField(level, target.position(), AntarchySettings.queenFinalTimeFieldRadius(),
AntarchySettings.queenFinalTimeFieldRate(), AntarchySettings.queenFinalTimeFieldDurationTicks());
}
}
}
private void performGravityStomp(ServerLevel level, LivingEntity target) {
this.triggerAnim("body_action", "stomp");
this.playRoyalSound(AntarchySoundEvents.QUEEN_ROAR.get(), 0.7F);
DamageSource source = this.damageSources().mobAttack(this);
for (LivingEntity living : level.getEntitiesOfClass(LivingEntity.class,
this.getBoundingBox().inflate(18.0D), entity -> entity instanceof Player && entity.isAlive())) {
double distance = Math.max(1.0D, living.distanceTo(this));
living.hurt(source, (float) (12.0D * Math.max(0.25D, 1.0D - distance / 24.0D)));
Vec3 push = living.position().subtract(this.position()).normalize().scale(1.3D);
living.setDeltaMovement(living.getDeltaMovement().add(push.x, 0.65D, push.z));
living.hasImpulse = true;
}
level.sendParticles(ParticleTypes.SONIC_BOOM, this.getX(), this.getY() + 1.0D, this.getZ(), 4, 2.0D, 0.5D, 2.0D, 0.0D);
}
private void castBlackHole(ServerLevel level, LivingEntity target) {
Vec3 anchor = target.position().add(
(this.random.nextDouble() - 0.5D) * 4.0D,
2.0D + this.random.nextDouble() * 2.0D,
(this.random.nextDouble() - 0.5D) * 4.0D);
RoyalBlackHoleEntity hole = RoyalBlackHoleEntity.create(level, anchor, this.getUUID());
level.addFreshEntity(hole);
this.triggerAnim("body_action", "wing_gust");
this.playRoyalSound(AntarchySoundEvents.QUEEN_ROAR.get(), 1.0F);
level.sendParticles(ParticleTypes.REVERSE_PORTAL, anchor.x, anchor.y, anchor.z, 60, 1.0D, 1.0D, 1.0D, 0.4D);
}
private void startMomentumLock(ServerLevel level) {
this.momentumLockCooldownTicks = MOMENTUM_LOCK_COOLDOWN;
this.royalEffects.start("momentum_lock", MOMENTUM_LOCK_DURATION, () -> {
this.frozenVelocities.clear();
for (Entity entity : level.getEntitiesOfClass(Entity.class,
this.getBoundingBox().inflate(MOMENTUM_LOCK_RADIUS))) {
if (entity == this || isFieldImmune(entity) || entity instanceof Player player && player.isCreative()) {
continue;
}
this.frozenVelocities.put(entity.getUUID(), entity.getDeltaMovement());
}
}, this::tickMomentumLockEffect, () -> {
for (Map.Entry<UUID, Vec3> entry : this.frozenVelocities.entrySet()) {
Entity entity = level.getEntity(entry.getKey());
if (entity != null && entity.isAlive()) {
entity.setDeltaMovement(entry.getValue());
entity.hasImpulse = true;
}
}
this.frozenVelocities.clear();
});
this.triggerAnim("body_action", "wing_gust");
this.playRoyalSound(AntarchySoundEvents.QUEEN_ROAR.get(), 0.5F);
}
private void tickMomentumLockEffect() {
if (!(this.level() instanceof ServerLevel level)) {
return;
}
for (Map.Entry<UUID, Vec3> entry : this.frozenVelocities.entrySet()) {
Entity entity = level.getEntity(entry.getKey());
if (entity == null || !entity.isAlive()) {
continue;
}
entity.setDeltaMovement(Vec3.ZERO);
entity.fallDistance = 0.0F;
entity.hasImpulse = true;
if (this.tickCount % 6 == 0) {
level.sendParticles(ParticleTypes.END_ROD, entity.getX(), entity.getY() + entity.getBbHeight() * 0.5D, entity.getZ(),
2, 0.2D, 0.2D, 0.2D, 0.0D);
}
}
}
private void tickCrushingGravity() {
if (!this.royalEffects.active("crushing_gravity")) {
return;
}
if (!(this.level() instanceof ServerLevel level)) {
return;
}
for (Entity entity : level.getEntitiesOfClass(Entity.class,
this.getBoundingBox().inflate(CRUSHING_GRAVITY_RADIUS))) {
if (entity == this || isFieldImmune(entity) || entity instanceof Player player && player.isCreative()) {
continue;
}
double downSign = AntarchyGravityApi.isGravityInverted(entity) ? 1.0D : -1.0D;
entity.setDeltaMovement(entity.getDeltaMovement().add(0.0D, downSign * CRUSHING_GRAVITY_STRENGTH, 0.0D));
entity.hasImpulse = true;
}
if (this.tickCount % 8 == 0) {
level.sendParticles(ACCEL_DUST, this.getX(), this.getY() + 1.0D, this.getZ(), 20,
CRUSHING_GRAVITY_RADIUS * 0.5D, 1.0D, CRUSHING_GRAVITY_RADIUS * 0.5D, 0.0D);
}
}
private void startRoyalAcceleration() {
this.accelerationCooldownTicks = ACCELERATION_COOLDOWN;
this.royalEffects.start("acceleration", ACCELERATION_DURATION, () -> {
this.setRoyalAccelerated(true);
this.playRoyalSound(AntarchySoundEvents.QUEEN_ROAR.get(), 1.4F);
applyAccelModifier(this.getAttribute(Attributes.MOVEMENT_SPEED), ACCEL_SPEED_ID);
applyAccelModifier(this.getAttribute(Attributes.FLYING_SPEED), ACCEL_FLY_ID);
}, this::tickRoyalAccelerationEffect, () -> {
this.setRoyalAccelerated(false);
removeAccelModifier(this.getAttribute(Attributes.MOVEMENT_SPEED), ACCEL_SPEED_ID);
removeAccelModifier(this.getAttribute(Attributes.FLYING_SPEED), ACCEL_FLY_ID);
});
}
private static void applyAccelModifier(AttributeInstance attribute, ResourceLocation id) {
UUID uuid = UUID.nameUUIDFromBytes(id.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8));
if (attribute == null || attribute.getModifier(uuid) != null) {
return;
}
attribute.addTransientModifier(new AttributeModifier(uuid, id.toString(), 0.9D, AttributeModifier.Operation.MULTIPLY_BASE));
}
private static void removeAccelModifier(AttributeInstance attribute, ResourceLocation id) {
if (attribute != null) {
attribute.removeModifier(UUID.nameUUIDFromBytes(id.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8)));
}
}
private void tickRoyalAccelerationEffect() {
if (!this.isRoyalAccelerated()) {
return;
}
if (this.level() instanceof ServerLevel level && this.tickCount % 2 == 0) {
level.sendParticles(ACCEL_DUST,
this.getX() + (this.random.nextDouble() - 0.5D) * this.getBbWidth(),
this.getY() + this.random.nextDouble() * this.getBbHeight(),
this.getZ() + (this.random.nextDouble() - 0.5D) * this.getBbWidth(),
6, 0.4D, 0.6D, 0.4D, 0.02D);
}
}
@Override
public void die(DamageSource damageSource) {
if (!this.level().isClientSide && this.level() instanceof ServerLevel level) {
DimensionalTearEntity.discardQueenOwnedTears(level, this.getUUID());
}
this.royalEffects.clear();
this.frozenVelocities.clear();
removeAccelModifier(this.getAttribute(Attributes.MOVEMENT_SPEED), ACCEL_SPEED_ID);
removeAccelModifier(this.getAttribute(Attributes.FLYING_SPEED), ACCEL_FLY_ID);
super.die(damageSource);
}
private boolean spawnManticore(ServerLevel serverLevel, LivingEntity target) {
double minimumDistance = Math.max(4.0D, this.getBbWidth() * 0.55D);
double maximumDistance = Math.max(minimumDistance + 1.0D, AntarchySettings.queenManticoreSummonRange());
for (int attempt = 0; attempt < POSITION_ATTEMPTS_PER_MANTICORE; attempt++) {
double angle = this.random.nextDouble() * Mth.TWO_PI;
double distance = Mth.lerp(this.random.nextDouble(), minimumDistance, maximumDistance);
double x = this.getX() + Math.cos(angle) * distance;
double y = this.getY() + (this.random.nextDouble() - 0.5D) * 2.0D;
double z = this.getZ() + Math.sin(angle) * distance;
ManticoreEntity manticore = AntarchyObjects.MANTICORE.get().create(serverLevel);
if (manticore == null) {
return false;
}
manticore.moveTo(x, y, z, this.random.nextFloat() * 360.0F, 0.0F);
BlockPos spawnPos = manticore.blockPosition();
if (!serverLevel.noCollision(manticore)
|| !serverLevel.isEmptyBlock(spawnPos)
|| !ManticoreEntity.canSpawn(AntarchyObjects.MANTICORE.get(), serverLevel, MobSpawnType.MOB_SUMMONED, spawnPos, this.random)) {
manticore.discard();
continue;
}
manticore.finalizeSpawn(serverLevel, serverLevel.getCurrentDifficultyAt(spawnPos), MobSpawnType.MOB_SUMMONED, null, null);
manticore.markQueenSummoned(this.getUUID());
manticore.setTarget(target);
serverLevel.addFreshEntity(manticore);
return true;
}
return false;
}
@Override
public void addAdditionalSaveData(CompoundTag tag) {
super.addAdditionalSaveData(tag);
tag.putInt(SUMMON_COOLDOWN_KEY, this.manticoreSummonCooldownTicks);
tag.putInt("GravityStompCooldownTicks", this.gravityStompCooldownTicks);
tag.putInt("TimeFieldCooldownTicks", this.timeFieldCooldownTicks);
tag.putInt("AccelerationCooldownTicks", this.accelerationCooldownTicks);
tag.putInt("BlackHoleCooldownTicks", this.blackHoleCooldownTicks);
}
@Override
public void readAdditionalSaveData(CompoundTag tag) {
super.readAdditionalSaveData(tag);
this.manticoreSummonCooldownTicks = Math.max(0, tag.getInt(SUMMON_COOLDOWN_KEY));
this.gravityStompCooldownTicks = Math.max(0, tag.getInt("GravityStompCooldownTicks"));
this.timeFieldCooldownTicks = Math.max(0, tag.getInt("TimeFieldCooldownTicks"));
this.momentumLockCooldownTicks = Math.max(0, tag.getInt("MomentumLockCooldownTicks"));
this.crushingGravityCooldownTicks = Math.max(0, tag.getInt("CrushingGravityCooldownTicks"));
this.accelerationCooldownTicks = Math.max(0, tag.getInt("AccelerationCooldownTicks"));
this.blackHoleCooldownTicks = Math.max(0, tag.getInt("BlackHoleCooldownTicks"));
}
}
@@ -0,0 +1,51 @@
package com.craisinlord.antarchy.content.entity.royal;
import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.AntarchyObjects;
import java.util.UUID;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
public final class RoyalAssailantBlackHoleEntity extends RoyalBlackHoleEntity {
public RoyalAssailantBlackHoleEntity(EntityType<? extends RoyalAssailantBlackHoleEntity> entityType, Level level) {
super(entityType, level);
}
public static RoyalAssailantBlackHoleEntity create(ServerLevel level, Vec3 center, @Nullable UUID ownerId) {
RoyalAssailantBlackHoleEntity hole = new RoyalAssailantBlackHoleEntity(
AntarchyObjects.ROYAL_ASSAILANT_BLACK_HOLE.get(), level);
hole.setPos(center.x, center.y, center.z);
hole.setRadius(AntarchySettings.queenBlackHoleRadius());
hole.setActiveTicks(AntarchySettings.queenBlackHoleActiveTicks());
hole.setOwnerId(ownerId);
return hole;
}
@Override
protected boolean risesWhileActive() {
return true;
}
@Override
protected double timeDilationRadius() {
return Math.max(2.0D, this.radius() * 0.55D);
}
@Override
protected boolean hasVisibleTimeDilationField() {
return false;
}
@Override
protected int collapseDurationTicks() {
return 12;
}
@Override
protected boolean pullsTerrainBlocks() {
return false;
}
}
@@ -0,0 +1,268 @@
package com.craisinlord.antarchy.content.entity.royal;
import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.content.time.TimeDilationApi;
import com.craisinlord.antarchy.config.AntarchySettings;
import java.util.UUID;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.level.GameRules;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
import software.bernie.geckolib.animatable.GeoEntity;
import software.bernie.geckolib.core.animatable.instance.AnimatableInstanceCache;
import software.bernie.geckolib.core.animation.AnimatableManager;
import software.bernie.geckolib.core.animation.AnimationController;
import software.bernie.geckolib.core.animation.AnimationState;
import software.bernie.geckolib.core.object.PlayState;
import software.bernie.geckolib.core.animation.RawAnimation;
import software.bernie.geckolib.util.GeckoLibUtil;
public class RoyalBlackHoleEntity extends Entity implements GeoEntity {
private static final EntityDataAccessor<Float> RADIUS =
SynchedEntityData.defineId(RoyalBlackHoleEntity.class, EntityDataSerializers.FLOAT);
private static final EntityDataAccessor<Boolean> COLLAPSING =
SynchedEntityData.defineId(RoyalBlackHoleEntity.class, EntityDataSerializers.BOOLEAN);
private static final String OWNER_KEY = "OwnerUuid";
private static final String AGE_KEY = "Age";
private static final String ACTIVE_KEY = "ActiveTicks";
private static final double DEFAULT_RADIUS = 14.0D;
private static final int DEFAULT_ACTIVE_TICKS = 120;
private static final int COLLAPSE_TICKS = 12;
@Nullable
private UUID ownerId;
private int age;
private int activeTicks = DEFAULT_ACTIVE_TICKS;
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
public RoyalBlackHoleEntity(EntityType<? extends RoyalBlackHoleEntity> entityType, Level level) {
super(entityType, level);
this.noPhysics = true;
this.noCulling = true;
}
public static RoyalBlackHoleEntity create(ServerLevel level, Vec3 center, double radius, int activeTicks, @Nullable UUID ownerId) {
RoyalBlackHoleEntity hole = new RoyalBlackHoleEntity(AntarchyObjects.ROYAL_BLACK_HOLE.get(), level);
hole.setPos(center.x, center.y, center.z);
hole.setRadius(radius > 0.0D ? radius : AntarchySettings.queenBlackHoleRadius());
hole.activeTicks = activeTicks > 0 ? Math.max(20, activeTicks) : AntarchySettings.queenBlackHoleActiveTicks();
hole.ownerId = ownerId;
return hole;
}
public static RoyalBlackHoleEntity create(ServerLevel level, Vec3 center, @Nullable UUID ownerId) {
return create(level, center, AntarchySettings.queenBlackHoleRadius(), AntarchySettings.queenBlackHoleActiveTicks(), ownerId);
}
protected void setActiveTicks(int activeTicks) {
this.activeTicks = activeTicks > 0 ? Math.max(20, activeTicks) : DEFAULT_ACTIVE_TICKS;
}
protected void setOwnerId(@Nullable UUID ownerId) {
this.ownerId = ownerId;
}
protected boolean risesWhileActive() {
return false;
}
protected double timeDilationRadius() {
return Math.max(2.0D, this.radius() * 0.55D);
}
protected boolean hasVisibleTimeDilationField() {
return true;
}
protected int collapseDurationTicks() {
return COLLAPSE_TICKS;
}
protected boolean pullsTerrainBlocks() {
return true;
}
@Override
protected void defineSynchedData() {
this.entityData.define(RADIUS, (float) DEFAULT_RADIUS);
this.entityData.define(COLLAPSING, false);
}
public void setRadius(double radius) {
this.entityData.set(RADIUS, (float) Math.max(1.0D, radius));
}
public double radius() {
return this.entityData.get(RADIUS);
}
public boolean isCollapsing() {
return this.entityData.get(COLLAPSING);
}
@Override
public void tick() {
super.tick();
if (this.level().isClientSide) {
this.spawnClientParticles();
return;
}
this.age++;
if (!this.isCollapsing()) {
if (this.risesWhileActive()) {
this.setPos(this.getX(), this.getY() + 0.035D, this.getZ());
}
this.applyPull();
if (this.age % 10 == 0 && this.level() instanceof ServerLevel level) {
if (this.hasVisibleTimeDilationField()) {
TimeDilationApi.createField(level, this.position(), this.timeDilationRadius(), 0.18D, 14, this.ownerId);
}
if (this.pullsTerrainBlocks() && this.age % 20 == 0 && level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
RoyalBlockDestruction.destroySphere(level, this, this.position(), Math.min(2.0D, this.radius() * 0.2D), 8, 30.0D, 0.0F);
}
}
if (this.age >= this.activeTicks) {
this.entityData.set(COLLAPSING, true);
this.age = 0;
}
return;
}
if (this.age >= this.collapseDurationTicks()) {
this.collapse();
this.discard();
}
}
private void applyPull() {
Vec3 center = this.position();
double radius = this.radius();
for (Entity entity : this.level().getEntitiesOfClass(Entity.class, new AABB(center, center).inflate(radius))) {
if (entity == this || this.isImmune(entity)) {
continue;
}
Vec3 toCenter = center.subtract(entity.position());
double distance = toCenter.length();
if (distance < 0.5D || distance > radius) {
continue;
}
double strength = AntarchySettings.queenBlackHolePullStrength() * (1.0D - distance / radius);
Vec3 pull = toCenter.scale(strength / distance);
entity.setDeltaMovement(entity.getDeltaMovement().scale(0.86D).add(pull));
entity.hasImpulse = true;
entity.fallDistance = 0.0F;
}
}
private void collapse() {
if (!(this.level() instanceof ServerLevel level)) {
return;
}
Vec3 center = this.position();
double radius = this.radius();
DamageSource source = this.ownerEntity() != null
? this.damageSources().mobAttack(this.ownerEntity())
: this.damageSources().magic();
for (LivingEntity living : level.getEntitiesOfClass(LivingEntity.class, new AABB(center, center).inflate(radius))) {
if (this.isImmune(living)) {
continue;
}
double distance = Math.max(1.0D, living.position().distanceTo(center));
float falloff = (float) Math.max(0.25D, 1.0D - distance / radius);
living.hurt(source, 26.0F * falloff);
Vec3 launch = living.position().subtract(center).normalize().scale(2.6D * falloff);
living.setDeltaMovement(launch.x, 1.1D * falloff + 0.4D, launch.z);
living.hasImpulse = true;
}
if (level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
RoyalBlockDestruction.destroySphere(level, this, center, radius * 0.4D, 160, 60.0D, 0.1F);
}
level.sendParticles(ParticleTypes.EXPLOSION_EMITTER, center.x, center.y, center.z, 8, 3.0D, 3.0D, 3.0D, 0.0D);
level.sendParticles(ParticleTypes.REVERSE_PORTAL, center.x, center.y, center.z, 120, 4.0D, 4.0D, 4.0D, 0.6D);
}
private boolean isImmune(Entity entity) {
if (entity instanceof RoyalBossEntity) {
return true;
}
if (this.ownerId != null && this.ownerId.equals(entity.getUUID())) {
return true;
}
return entity.getType().is(com.craisinlord.antarchy.content.AntarchyTags.Entities.TIME_DILATION_IMMUNE);
}
@Nullable
private LivingEntity ownerEntity() {
if (this.ownerId == null || !(this.level() instanceof ServerLevel level)) {
return null;
}
return level.getEntity(this.ownerId) instanceof LivingEntity living ? living : null;
}
private void spawnClientParticles() {
Vec3 center = this.position();
for (int i = 0; i < 6; i++) {
double angle = this.random.nextDouble() * Math.PI * 2.0D;
double r = this.radius() * (0.3D + this.random.nextDouble() * 0.7D);
double px = center.x + Math.cos(angle) * r;
double pz = center.z + Math.sin(angle) * r;
double py = center.y + (this.random.nextDouble() - 0.5D) * 3.0D;
this.level().addParticle(ParticleTypes.PORTAL, px, py, pz,
(center.x - px) * 0.18D, (center.y - py) * 0.18D, (center.z - pz) * 0.18D);
}
}
@Override
public boolean isPickable() {
return false;
}
@Override
public boolean isIgnoringBlockTriggers() {
return true;
}
@Override
protected void readAdditionalSaveData(CompoundTag tag) {
this.setRadius(tag.getDouble("Radius"));
this.age = tag.getInt(AGE_KEY);
this.activeTicks = tag.contains(ACTIVE_KEY) ? tag.getInt(ACTIVE_KEY) : DEFAULT_ACTIVE_TICKS;
this.ownerId = tag.hasUUID(OWNER_KEY) ? tag.getUUID(OWNER_KEY) : null;
}
@Override
protected void addAdditionalSaveData(CompoundTag tag) {
tag.putDouble("Radius", this.radius());
tag.putInt(AGE_KEY, this.age);
tag.putInt(ACTIVE_KEY, this.activeTicks);
if (this.ownerId != null) {
tag.putUUID(OWNER_KEY, this.ownerId);
}
}
@Override
public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
controllers.add(new AnimationController<>(this, "black_hole_controller", 0, this::blackHoleController));
}
private PlayState blackHoleController(AnimationState<RoyalBlackHoleEntity> state) {
return state.setAndContinue(RawAnimation.begin().thenLoop("animation"));
}
@Override
public AnimatableInstanceCache getAnimatableInstanceCache() {
return this.geoCache;
}
}
@@ -0,0 +1,69 @@
package com.craisinlord.antarchy.content.entity.royal;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.GameRules;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3;
public final class RoyalBlockDestruction {
private RoyalBlockDestruction() {
}
public static int destroySphere(
ServerLevel level,
Entity source,
Vec3 center,
double radius,
int maxBlocks,
double maxResistance,
float dropChance
) {
if (maxBlocks <= 0 || radius <= 0.0D || !level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
return 0;
}
int minX = net.minecraft.util.Mth.floor(center.x - radius);
int minY = net.minecraft.util.Mth.floor(center.y - radius);
int minZ = net.minecraft.util.Mth.floor(center.z - radius);
int maxX = net.minecraft.util.Mth.floor(center.x + radius);
int maxY = net.minecraft.util.Mth.floor(center.y + radius);
int maxZ = net.minecraft.util.Mth.floor(center.z + radius);
double radiusSqr = radius * radius;
int destroyed = 0;
for (BlockPos cursor : BlockPos.betweenClosed(minX, minY, minZ, maxX, maxY, maxZ)) {
if (destroyed >= maxBlocks) {
break;
}
if (cursor.distToCenterSqr(center.x, center.y, center.z) > radiusSqr
|| cursor.getY() < level.getMinBuildHeight()
|| cursor.getY() >= level.getMaxBuildHeight()
|| !level.getWorldBorder().isWithinBounds(cursor)) {
continue;
}
BlockPos pos = cursor.immutable();
BlockState existing = level.getBlockState(pos);
if (existing.isAir()
|| level.getBlockEntity(pos) != null
|| existing.getDestroySpeed(level, pos) < 0.0F
|| existing.getBlock().getExplosionResistance() > maxResistance
|| existing.getCollisionShape(level, pos).isEmpty()) {
continue;
}
if (level.random.nextFloat() <= dropChance) {
level.destroyBlock(pos, true, source);
} else {
level.levelEvent(2001, pos, Block.getId(existing));
level.setBlockAndUpdate(pos, Blocks.AIR.defaultBlockState());
}
destroyed++;
}
return destroyed;
}
}
@@ -0,0 +1,137 @@
package com.craisinlord.antarchy.content.entity.royal;
import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.entity.royal.beam.RoyalBeamElement;
import net.minecraft.core.particles.ParticleOptions;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LightningBolt;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.projectile.AbstractHurtingProjectile;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.EntityHitResult;
import net.minecraft.world.phys.HitResult;
public class RoyalBoltEntity extends AbstractHurtingProjectile {
private static final EntityDataAccessor<Integer> ELEMENT =
SynchedEntityData.defineId(RoyalBoltEntity.class, EntityDataSerializers.INT);
public RoyalBoltEntity(EntityType<? extends RoyalBoltEntity> entityType, Level level) {
super(entityType, level);
}
@Override
protected void defineSynchedData() {
super.defineSynchedData();
this.entityData.define(ELEMENT, RoyalBeamElement.GENERIC.ordinal());
}
public void setElement(RoyalBeamElement element) {
this.entityData.set(ELEMENT, element.ordinal());
}
public RoyalBeamElement getElement() {
RoyalBeamElement[] values = RoyalBeamElement.values();
return values[Math.floorMod(this.entityData.get(ELEMENT), values.length)];
}
@Override
public void addAdditionalSaveData(CompoundTag tag) {
super.addAdditionalSaveData(tag);
tag.putInt("Element", this.entityData.get(ELEMENT));
}
@Override
public void readAdditionalSaveData(CompoundTag tag) {
super.readAdditionalSaveData(tag);
if (tag.contains("Element")) {
this.entityData.set(ELEMENT, tag.getInt("Element"));
}
}
protected double getDefaultGravity() {
return 0.02D;
}
@Override
protected float getInertia() {
return 0.97F;
}
@Override
protected boolean shouldBurn() {
return false;
}
@Override
protected ParticleOptions getTrailParticle() {
return switch (this.getElement()) {
case FIRE -> ParticleTypes.FLAME;
case LIGHTNING -> ParticleTypes.ELECTRIC_SPARK;
case ICE -> ParticleTypes.SNOWFLAKE;
case GENERIC -> ParticleTypes.WITCH;
};
}
@Override
protected void onHitEntity(EntityHitResult result) {
super.onHitEntity(result);
if (this.level().isClientSide) {
return;
}
Entity target = result.getEntity();
Entity owner = this.getOwner();
if (target == owner || (owner != null && target == owner.getVehicle())) {
return;
}
float damage = (float) AntarchySettings.royalBoltDamage();
target.hurt(this.damageSources().mobProjectile(this, owner instanceof LivingEntity le ? le : null), damage);
if (target instanceof LivingEntity living) {
living.knockback(0.35D, this.getX() - target.getX(), this.getZ() - target.getZ());
switch (this.getElement()) {
case FIRE -> living.setRemainingFireTicks(Math.max(living.getRemainingFireTicks(), 80));
case ICE -> {
living.addEffect(new MobEffectInstance(MobEffects.MOVEMENT_SLOWDOWN, 100, 1));
living.setTicksFrozen(Math.min(living.getTicksRequiredToFreeze() + 40, living.getTicksFrozen() + 140));
}
case LIGHTNING -> {
if (this.level() instanceof ServerLevel serverLevel) {
LightningBolt bolt = EntityType.LIGHTNING_BOLT.create(serverLevel);
if (bolt != null) {
bolt.moveTo(target.getX(), target.getY(), target.getZ());
bolt.setVisualOnly(true);
if (owner instanceof net.minecraft.server.level.ServerPlayer serverPlayer) {
bolt.setCause(serverPlayer);
}
serverLevel.addFreshEntity(bolt);
}
}
}
case GENERIC -> {
}
}
}
}
@Override
protected void onHit(HitResult result) {
super.onHit(result);
if (!this.level().isClientSide) {
this.level().broadcastEntityEvent(this, (byte) 3);
this.discard();
}
}
@Override
public boolean isPickable() {
return false;
}
}
Loaded 100 of 3734 files, more files were not shown because too many files have changed in this diff. Show more