1.20 progres

king and queen balancing
Antmail
Blockle
This commit is contained in:
craisinlord committed 2026-09-15 19:10:28 -05:00
1 parent c1d60d735d
commit fc5028a15b
430 files changed
+30569 -1684

No files matched your search

@@ -0,0 +1,10 @@
package com.craisinlord.antarchy.compat.infinity;
/** Shared result vocabulary for future asynchronous Infinity dimension integrations. */
public enum InfinityWarpResult {
REJECTED,
PENDING,
READY,
FAILED_GENERATION,
TIMEOUT
}
@@ -0,0 +1,118 @@
package com.craisinlord.antarchy.config;
import net.minecraft.resources.ResourceKey;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/** Shared reflection metadata for presenting the Antarchy settings consistently. */
public final class AntarchyConfigCatalog {
public enum Section { MOBS, TOOLS, MISC }
public enum ValueType { BOOLEAN, INTEGER, DOUBLE, FLOAT, STRING }
public record Definition(String name, Section section, ValueType valueType,
Class<?> setterType, Method getter, Method setter) {}
private static final List<Definition> DEFINITIONS = buildDefinitions();
private AntarchyConfigCatalog() {}
public static List<Definition> definitions() {
return DEFINITIONS;
}
public static Map<String, Definition> definitionsByName() {
Map<String, Definition> result = new LinkedHashMap<>();
for (Definition definition : DEFINITIONS) {
result.put(definition.name(), definition);
}
return Map.copyOf(result);
}
private static List<Definition> buildDefinitions() {
Map<String, Method> getters = Arrays.stream(AntarchySettings.class.getMethods())
.filter(AntarchyConfigCatalog::isPublicStatic)
.filter(method -> method.getParameterCount() == 0)
.filter(method -> method.getReturnType() != Void.TYPE)
.collect(java.util.stream.Collectors.toMap(Method::getName, method -> method,
(first, ignored) -> first, LinkedHashMap::new));
return Arrays.stream(AntarchySettings.class.getMethods())
.filter(AntarchyConfigCatalog::isPublicStatic)
.filter(method -> method.getName().startsWith("set"))
.filter(method -> method.getParameterCount() == 1)
.sorted(java.util.Comparator.comparing(Method::getName))
.map(setter -> definitionFor(setter, getters))
.filter(java.util.Objects::nonNull)
.toList();
}
private static Definition definitionFor(Method setter, Map<String, Method> getters) {
String name = decapitalize(setter.getName().substring(3));
Method getter = getters.get(name);
if (getter == null) return null;
Class<?> setterType = setter.getParameterTypes()[0];
ValueType valueType = valueTypeFor(setterType, getter.getReturnType());
return valueType == null ? null : new Definition(name, sectionFor(name), valueType, setterType, getter, setter);
}
private static ValueType valueTypeFor(Class<?> setterType, Class<?> getterType) {
if (setterType == boolean.class && getterType == boolean.class) return ValueType.BOOLEAN;
if (setterType == int.class && getterType == int.class) return ValueType.INTEGER;
if (setterType == double.class && getterType == double.class) return ValueType.DOUBLE;
if (setterType == float.class && getterType == float.class) return ValueType.FLOAT;
if (setterType == String.class && ResourceKey.class.isAssignableFrom(getterType)) return ValueType.STRING;
return null;
}
private static boolean isPublicStatic(Method method) {
int modifiers = method.getModifiers();
return Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers);
}
private static String decapitalize(String value) {
return value.isEmpty() ? value : Character.toLowerCase(value.charAt(0)) + value.substring(1);
}
private static Section sectionFor(String name) {
return isMiscSetting(name) ? Section.MISC : isToolSetting(name) ? Section.TOOLS : Section.MOBS;
}
private static boolean isMiscSetting(String name) {
return name.equals("disableInfinityBookPortalCreation")
|| name.equals("rainbowAntsLeadToInfinityDimensions")
|| name.equals("permanentPortalsEnabled")
|| name.equals("permanentPortalsFlintAndSteelEnabled")
|| name.equals("elythiaPortalEnabled") || name.equals("thoraxisPortalEnabled")
|| name.equals("cavarynPortalEnabled") || name.equals("hushweedSporeLifetimeSeconds")
|| name.equals("elythiaFireflyParticlesEnabled") || name.equals("duplicatorTreeEnabled")
|| name.equals("glowVinesUnderLeaves") || name.equals("swingThroughGrassEnabled")
|| name.equals("fabricKeybindingConflictFixEnabled")
|| name.equals("experimentalSettingsPopupDisabled") || name.equals("unlockAllArchives")
|| name.equals("entitySpecificFireOverlayEnabled") || name.equals("dreamSandEnabled")
|| name.startsWith("dreamSand") || name.equals("ichorWitherEnabled")
|| name.startsWith("diamondMinecart") || name.startsWith("hoverboard");
}
private static boolean isToolSetting(String name) {
return name.equals("sizeChangingRaysEnabled") || name.startsWith("sizeRay")
|| name.equals("shrinkingPotionDelta") || name.equals("growthPotionDelta")
|| name.startsWith("ultimate") || name.startsWith("battleAxe")
|| name.startsWith("bigBertha") || name.startsWith("attitudeAdjuster")
|| name.startsWith("scorpionWhip") || name.startsWith("bloodCrystal")
|| name.startsWith("nightmareHelmet") || name.startsWith("nightmareChestplate")
|| name.startsWith("nightmareLeggings") || name.startsWith("nightmareBoots")
|| name.startsWith("nightmareArmorDreadAura") || name.startsWith("primordialArmor")
|| name.startsWith("nightmareSword") || name.startsWith("basiliskDagger")
|| name.startsWith("squidzooka") || name.equals("invertProjectilesFromInvertedPlayers")
|| name.startsWith("gravityGun") || name.startsWith("critterCage")
|| name.startsWith("minersDream") || name.equals("potentNyxiteInvertedDurationSeconds")
|| name.equals("corneaEarNightVisionSeconds") || name.startsWith("american")
|| name.startsWith("mogglesVision") || name.equals("ductTapeRepairPercentPerUse")
|| name.startsWith("fallenKingCrown");
}
}
@@ -0,0 +1,42 @@
package com.craisinlord.antarchy.content.block;
import com.craisinlord.antarchy.content.block.entity.QueenTrailSpawnMarkerBlockEntity;
import java.util.function.Supplier;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
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;
/** Invisible worldgen marker which activates the Queen at a generated trail terminus. */
public final class QueenTrailSpawnMarkerBlock extends BaseEntityBlock {
private final Supplier<? extends BlockEntityType<QueenTrailSpawnMarkerBlockEntity>> type;
public QueenTrailSpawnMarkerBlock(Supplier<? extends BlockEntityType<QueenTrailSpawnMarkerBlockEntity>> type,
BlockBehaviour.Properties properties) {
super(properties);
this.type = type;
}
@Override
public RenderShape getRenderShape(BlockState state) { return RenderShape.INVISIBLE; }
@Override
public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
return new QueenTrailSpawnMarkerBlockEntity(pos, state, type);
}
@Override
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level, BlockState state, BlockEntityType<T> blockEntityType) {
if (level.isClientSide) return null;
return createTickerHelper(blockEntityType, type.get(), (tickLevel, tickPos, tickState, marker) -> {
if (tickLevel instanceof ServerLevel serverLevel) QueenTrailSpawnMarkerBlockEntity.serverTick(serverLevel, marker);
});
}
}
@@ -0,0 +1,90 @@
package com.craisinlord.antarchy.content.block;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.block.entity.UnderVaultBlockEntity;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.server.level.ServerLevel;
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.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.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.level.block.state.properties.DirectionProperty;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.resources.ResourceLocation;
import java.util.function.Supplier;
public final class UnderVaultBlock extends BaseEntityBlock {
public static final DirectionProperty FACING = DirectionProperty.create("facing");
public static final BooleanProperty ACTIVE = BooleanProperty.create("active");
private final Supplier<? extends BlockEntityType<UnderVaultBlockEntity>> blockEntityType;
public UnderVaultBlock(Supplier<? extends BlockEntityType<UnderVaultBlockEntity>> blockEntityType,
BlockBehaviour.Properties properties) {
super(properties);
this.blockEntityType = blockEntityType;
registerDefaultState(stateDefinition.any().setValue(FACING, Direction.DOWN).setValue(ACTIVE, true));
}
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
builder.add(FACING, ACTIVE);
}
@Override
public BlockState getStateForPlacement(net.minecraft.world.item.context.BlockPlaceContext context) {
return context.getClickedFace() == Direction.DOWN
? defaultBlockState().setValue(FACING, Direction.DOWN)
: null;
}
@Override
public RenderShape getRenderShape(BlockState state) {
return RenderShape.MODEL;
}
@Override
public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
return new UnderVaultBlockEntity(pos, state, blockEntityType);
}
@Override
public InteractionResult use(BlockState state, Level level, BlockPos pos, Player player,
InteractionHand hand, BlockHitResult hit) {
ItemStack stack = player.getItemInHand(hand);
if (!state.getValue(ACTIVE) || !isKey(stack)) {
return InteractionResult.PASS;
}
if (!level.isClientSide && level instanceof ServerLevel serverLevel
&& level.getBlockEntity(pos) instanceof UnderVaultBlockEntity vault) {
if (vault.unlock(serverLevel, pos, player, stack)) {
level.setBlock(pos, state.setValue(ACTIVE, false), Block.UPDATE_ALL);
return InteractionResult.CONSUME;
}
}
return InteractionResult.sidedSuccess(level.isClientSide);
}
private static boolean isKey(ItemStack stack) {
return stack.is(BuiltInRegistries.ITEM.get(new ResourceLocation(Antarchy.MODID, "undertrial_key")));
}
@Override
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level, BlockState state,
BlockEntityType<T> type) {
return createTickerHelper(type, blockEntityType.get(), UnderVaultBlockEntity::tick);
}
}
@@ -0,0 +1,58 @@
package com.craisinlord.antarchy.content.block;
import com.craisinlord.antarchy.content.block.entity.UndertrialSpawnerBlockEntity;
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.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.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import java.util.function.Supplier;
public final class UndertrialSpawnerBlock extends BaseEntityBlock {
public static final BooleanProperty ACTIVE = BooleanProperty.create("active");
public static final BooleanProperty OMINOUS = BooleanProperty.create("ominous");
private final Supplier<? extends BlockEntityType<UndertrialSpawnerBlockEntity>> blockEntityType;
public UndertrialSpawnerBlock(Supplier<? extends BlockEntityType<UndertrialSpawnerBlockEntity>> blockEntityType,
BlockBehaviour.Properties properties) {
super(properties);
this.blockEntityType = blockEntityType;
registerDefaultState(stateDefinition.any().setValue(ACTIVE, false).setValue(OMINOUS, false));
}
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
builder.add(ACTIVE, OMINOUS);
}
@Override
public BlockState getStateForPlacement(BlockPlaceContext context) {
return context.getClickedFace() == Direction.DOWN ? defaultBlockState() : null;
}
@Override
public RenderShape getRenderShape(BlockState state) {
return RenderShape.MODEL;
}
@Override
public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
return new UndertrialSpawnerBlockEntity(pos, state, blockEntityType);
}
@Override
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level, BlockState state,
BlockEntityType<T> type) {
return createTickerHelper(type, blockEntityType.get(), UndertrialSpawnerBlockEntity::tick);
}
}
@@ -0,0 +1,83 @@
package com.craisinlord.antarchy.content.block.entity;
import com.craisinlord.antarchy.content.entity.royal.QueenEntity;
import java.util.UUID;
import java.util.function.Supplier;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.Difficulty;
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 final class QueenTrailSpawnMarkerBlockEntity extends BlockEntity {
private static final double ACTIVATION_DISTANCE_SQUARED = 96.0D * 96.0D;
private long siteId;
private BlockPos spawnPos = BlockPos.ZERO;
private BlockPos homePos = BlockPos.ZERO;
private float yaw;
private boolean configured;
private boolean consumed;
private UUID queenUuid;
public QueenTrailSpawnMarkerBlockEntity(BlockPos pos, BlockState state,
Supplier<? extends BlockEntityType<QueenTrailSpawnMarkerBlockEntity>> type) {
super(type.get(), pos, state);
}
public void configure(long siteId, BlockPos spawnPos, BlockPos homePos, float yaw) {
this.siteId = siteId;
this.spawnPos = spawnPos.immutable();
this.homePos = homePos.immutable();
this.yaw = yaw;
this.configured = true;
setChanged();
}
public long getSiteId() { return siteId; }
public BlockPos getSpawnPos() { return spawnPos; }
public boolean isConfigured() { return configured; }
public static void serverTick(ServerLevel level, QueenTrailSpawnMarkerBlockEntity marker) {
if (!marker.configured || marker.consumed || level.getDifficulty() == Difficulty.PEACEFUL) return;
if (Math.floorMod(level.getGameTime(), 20L) != Math.floorMod(marker.siteId, 20L)) return;
boolean nearby = level.players().stream().anyMatch(player -> !player.isSpectator()
&& player.distanceToSqr(marker.spawnPos.getX() + .5D, marker.spawnPos.getY() + .5D,
marker.spawnPos.getZ() + .5D) <= ACTIVATION_DISTANCE_SQUARED);
if (!nearby) return;
for (int chunkX = (marker.spawnPos.getX() - 16) >> 4; chunkX <= (marker.spawnPos.getX() + 16) >> 4; chunkX++)
for (int chunkZ = (marker.spawnPos.getZ() - 16) >> 4; chunkZ <= (marker.spawnPos.getZ() + 16) >> 4; chunkZ++)
level.getChunk(chunkX, chunkZ);
QueenEntity queen = QueenEntity.spawnFromUndersideTrail(level, marker.spawnPos, marker.homePos, marker.siteId, marker.yaw);
if (queen != null) {
marker.consumed = true;
marker.queenUuid = queen.getUUID();
marker.setChanged();
}
}
@Override
public void load(CompoundTag tag) {
super.load(tag);
siteId = tag.getLong("site_id");
spawnPos = BlockPos.of(tag.getLong("spawn_pos"));
homePos = BlockPos.of(tag.getLong("home_pos"));
yaw = tag.getFloat("yaw");
configured = tag.getBoolean("configured");
consumed = tag.getBoolean("consumed");
queenUuid = tag.hasUUID("queen_uuid") ? tag.getUUID("queen_uuid") : null;
}
@Override
protected void saveAdditional(CompoundTag tag) {
super.saveAdditional(tag);
tag.putLong("site_id", siteId);
tag.putLong("spawn_pos", spawnPos.asLong());
tag.putLong("home_pos", homePos.asLong());
tag.putFloat("yaw", yaw);
tag.putBoolean("configured", configured);
tag.putBoolean("consumed", consumed);
if (queenUuid != null) tag.putUUID("queen_uuid", queenUuid);
}
}
@@ -0,0 +1,83 @@
package com.craisinlord.antarchy.content.block.entity;
import com.craisinlord.antarchy.Antarchy;
import net.minecraft.core.BlockPos;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.particles.SimpleParticleType;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets;
import net.minecraft.world.level.storage.loot.LootParams;
import net.minecraft.world.level.storage.loot.LootTable;
import net.minecraft.world.phys.Vec3;
import net.minecraft.core.Direction;
import net.minecraft.core.dispenser.DefaultDispenseItemBehavior;
import java.util.function.Supplier;
public final class UnderVaultBlockEntity extends BlockEntity {
private boolean unlocked;
public UnderVaultBlockEntity(BlockPos pos, net.minecraft.world.level.block.state.BlockState state,
Supplier<? extends BlockEntityType<UnderVaultBlockEntity>> type) {
super(type.get(), pos, state);
}
public static void tick(Level level, BlockPos pos, net.minecraft.world.level.block.state.BlockState state,
UnderVaultBlockEntity blockEntity) {
if (level.isClientSide && state.getValue(com.craisinlord.antarchy.content.block.UnderVaultBlock.ACTIVE)
&& level.random.nextInt(4) == 0) {
Player player = level.getNearestPlayer(pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D, 8.0D, false);
if (player != null) {
Vec3 start = Vec3.atCenterOf(pos);
Vec3 end = player.position().add(0.0D, player.getBbHeight() * 0.5D, 0.0D);
Vec3 point = start.lerp(end, 0.15D + level.random.nextDouble() * 0.7D);
level.addParticle(particle("undervault_connection"), point.x, point.y, point.z, 0.0D, 0.0D, 0.0D);
}
}
}
private static SimpleParticleType particle(String id) {
return (SimpleParticleType) BuiltInRegistries.PARTICLE_TYPE.get(new ResourceLocation(Antarchy.MODID, id));
}
public boolean unlock(ServerLevel level, BlockPos pos, Player player, ItemStack key) {
if (unlocked) {
return false;
}
if (!player.getAbilities().instabuild) {
key.shrink(1);
}
LootTable table = level.getServer().getLootData().getLootTable(
new ResourceLocation(Antarchy.MODID, "chests/undertrial_vault"));
LootParams params = new LootParams.Builder(level).withParameter(
net.minecraft.world.level.storage.loot.parameters.LootContextParams.ORIGIN,
Vec3.atCenterOf(pos)).create(LootContextParamSets.CHEST);
for (ItemStack stack : table.getRandomItems(params)) {
DefaultDispenseItemBehavior.spawnItem(level, stack, 2, Direction.DOWN,
Vec3.atBottomCenterOf(pos).relative(Direction.DOWN, 1.2D));
}
unlocked = true;
setChanged();
level.levelEvent(3011, pos, 0);
return true;
}
@Override
public void load(CompoundTag tag) {
super.load(tag);
unlocked = tag.getBoolean("Unlocked");
}
@Override
protected void saveAdditional(CompoundTag tag) {
super.saveAdditional(tag);
tag.putBoolean("Unlocked", unlocked);
}
}
@@ -0,0 +1,113 @@
package com.craisinlord.antarchy.content.block.entity;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.block.UndertrialSpawnerBlock;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.particles.SimpleParticleType;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.monster.Zombie;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
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.level.storage.loot.parameters.LootContextParamSets;
import net.minecraft.world.level.storage.loot.LootParams;
import net.minecraft.world.level.storage.loot.LootTable;
import net.minecraft.world.level.storage.loot.parameters.LootContextParam;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import net.minecraft.core.dispenser.DefaultDispenseItemBehavior;
import java.util.List;
import java.util.function.Supplier;
public final class UndertrialSpawnerBlockEntity extends BlockEntity {
private static final String COOLDOWN_TAG = "Cooldown";
private static final String WAVES_TAG = "Waves";
private static final int MAX_WAVES = 6;
private int cooldown;
private int waves;
public UndertrialSpawnerBlockEntity(BlockPos pos, BlockState state,
Supplier<? extends BlockEntityType<UndertrialSpawnerBlockEntity>> type) {
super(type.get(), pos, state);
}
public static void tick(Level level, BlockPos pos, BlockState state, UndertrialSpawnerBlockEntity blockEntity) {
if (!(level instanceof ServerLevel serverLevel)) {
if (level.isClientSide && state.getValue(UndertrialSpawnerBlock.ACTIVE)
&& level.random.nextInt(2) == 0) {
level.addParticle(particle("undertrial_omen"), pos.getX() + 0.5D + (level.random.nextDouble() - 0.5D) * 0.7D,
pos.getY() + 0.45D + level.random.nextDouble() * 0.35D,
pos.getZ() + 0.5D + (level.random.nextDouble() - 0.5D) * 0.7D, 0.0D, 0.01D, 0.0D);
}
return;
}
if (blockEntity.cooldown > 0) {
blockEntity.cooldown--;
return;
}
if (!state.getValue(UndertrialSpawnerBlock.ACTIVE)) {
return;
}
if (serverLevel.getNearestPlayer(pos.getX() + 0.5D, pos.getY() - 0.5D, pos.getZ() + 0.5D, 16.0D, false) == null) {
return;
}
long livingTrialMobs = serverLevel.getEntitiesOfClass(Mob.class, new AABB(pos).inflate(16.0D),
mob -> mob.getTags().contains("antarchy_undertrial_mob")).size();
if (livingTrialMobs > 0) {
return;
}
if (blockEntity.waves >= MAX_WAVES) {
blockEntity.ejectReward(serverLevel, pos);
serverLevel.setBlock(pos, state.setValue(UndertrialSpawnerBlock.ACTIVE, false), 3);
return;
}
Zombie zombie = EntityType.ZOMBIE.create(serverLevel);
if (zombie != null) {
zombie.moveTo(pos.getX() + 0.5D, pos.getY() - 1.0D, pos.getZ() + 0.5D,
serverLevel.random.nextFloat() * 360.0F, 0.0F);
zombie.addTag("antarchy_undertrial_mob");
serverLevel.addFreshEntity(zombie);
blockEntity.waves++;
blockEntity.cooldown = 80;
blockEntity.setChanged();
}
}
private static SimpleParticleType particle(String id) {
return (SimpleParticleType) BuiltInRegistries.PARTICLE_TYPE.get(new ResourceLocation(Antarchy.MODID, id));
}
private void ejectReward(ServerLevel level, BlockPos pos) {
ResourceLocation id = new ResourceLocation(Antarchy.MODID, "spawners/undertrial");
LootTable table = level.getServer().getLootData().getLootTable(id);
LootParams params = new LootParams.Builder(level).create(LootContextParamSets.EMPTY);
for (ItemStack stack : table.getRandomItems(params)) {
DefaultDispenseItemBehavior.spawnItem(level, stack, 2, Direction.DOWN,
Vec3.atBottomCenterOf(pos).relative(Direction.DOWN, 1.2D));
}
level.levelEvent(3014, pos, 0);
}
@Override
public void load(CompoundTag tag) {
super.load(tag);
cooldown = tag.getInt(COOLDOWN_TAG);
waves = tag.getInt(WAVES_TAG);
}
@Override
protected void saveAdditional(CompoundTag tag) {
super.saveAdditional(tag);
tag.putInt(COOLDOWN_TAG, cooldown);
tag.putInt(WAVES_TAG, waves);
}
}
@@ -0,0 +1,67 @@
package com.craisinlord.antarchy.content.client.particle;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.particle.Particle;
import net.minecraft.client.particle.ParticleProvider;
import net.minecraft.client.particle.ParticleRenderType;
import net.minecraft.client.particle.SpriteSet;
import net.minecraft.client.particle.TextureSheetParticle;
import net.minecraft.core.particles.SimpleParticleType;
/** Lightweight 1.20 equivalent of the Undertrial's detection/omen particles. */
public final class UndertrialParticle extends TextureSheetParticle {
private final SpriteSet sprites;
private final boolean animated;
private UndertrialParticle(ClientLevel level, double x, double y, double z, double xd, double yd, double zd,
SpriteSet sprites, boolean animated) {
super(level, x, y, z, xd, yd, zd);
this.sprites = sprites;
this.animated = animated;
this.gravity = 0.0F;
this.friction = 0.96F;
this.hasPhysics = false;
this.quadSize = animated ? 0.16F + this.random.nextFloat() * 0.08F : 0.2F + this.random.nextFloat() * 0.1F;
this.lifetime = animated ? 10 : 24;
this.setAlpha(0.9F);
this.pickSprite(sprites);
}
@Override
public void tick() {
super.tick();
if (this.animated) {
this.setSprite(this.sprites.get(this.age, this.lifetime));
}
this.setAlpha(0.9F * (1.0F - (float) this.age / (float) this.lifetime));
}
@Override
public ParticleRenderType getRenderType() {
return ParticleRenderType.PARTICLE_SHEET_TRANSLUCENT;
}
public static final class DetectionProvider implements ParticleProvider<SimpleParticleType> {
private final SpriteSet sprites;
public DetectionProvider(SpriteSet sprites) { this.sprites = sprites; }
@Override
public Particle createParticle(SimpleParticleType type, ClientLevel level, double x, double y, double z,
double xd, double yd, double zd) {
return new UndertrialParticle(level, x, y, z, xd, yd, zd, this.sprites, true);
}
}
public static final class OmenProvider implements ParticleProvider<SimpleParticleType> {
private final SpriteSet sprites;
public OmenProvider(SpriteSet sprites) { this.sprites = sprites; }
@Override
public Particle createParticle(SimpleParticleType type, ClientLevel level, double x, double y, double z,
double xd, double yd, double zd) {
return new UndertrialParticle(level, x, y, z, xd, yd, zd, this.sprites, false);
}
}
}
@@ -0,0 +1,34 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.content.block.entity.UnderVaultBlockEntity;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
import net.minecraft.client.renderer.texture.OverlayTexture;
import net.minecraft.world.item.ItemDisplayContext;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
/** Compact 1.20 vault display: a rotating key replaces the vanilla vault core. */
public final class UnderVaultRenderer implements BlockEntityRenderer<UnderVaultBlockEntity> {
public UnderVaultRenderer() {
}
public UnderVaultRenderer(BlockEntityRendererProvider.Context ignored) {
}
@Override
public void render(UnderVaultBlockEntity blockEntity, float partialTick, PoseStack poseStack,
MultiBufferSource buffer, int packedLight, int packedOverlay) {
poseStack.pushPose();
poseStack.translate(0.5D, 0.55D, 0.5D);
poseStack.mulPose(com.mojang.math.Axis.YP.rotationDegrees((blockEntity.getLevel().getGameTime() + partialTick) * 2.0F));
poseStack.scale(0.65F, 0.65F, 0.65F);
Minecraft.getInstance().getItemRenderer().renderStatic(new ItemStack(Items.TRIPWIRE_HOOK),
ItemDisplayContext.FIXED, packedLight, OverlayTexture.NO_OVERLAY, poseStack, buffer,
blockEntity.getLevel(), 0);
poseStack.popPose();
}
}
@@ -0,0 +1,38 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.content.block.entity.UndertrialSpawnerBlockEntity;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
import net.minecraft.client.renderer.entity.EntityRenderDispatcher;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.level.Level;
/** 1.20 visual equivalent for the 1.21 trial-spawner display entity. */
public final class UndertrialSpawnerRenderer implements BlockEntityRenderer<UndertrialSpawnerBlockEntity> {
private final EntityRenderDispatcher dispatcher;
public UndertrialSpawnerRenderer(BlockEntityRendererProvider.Context context) {
this.dispatcher = context.getEntityRenderer();
}
@Override
public void render(UndertrialSpawnerBlockEntity blockEntity, float partialTick, PoseStack poseStack,
MultiBufferSource buffer, int packedLight, int packedOverlay) {
Level level = blockEntity.getLevel();
if (level == null) return;
Entity display = EntityType.ZOMBIE.create(level);
if (display == null) return;
display.setPos(blockEntity.getBlockPos().getX() + 0.5D,
blockEntity.getBlockPos().getY() + 0.5D,
blockEntity.getBlockPos().getZ() + 0.5D);
float spin = (level.getGameTime() + partialTick) * 2.0F;
poseStack.pushPose();
poseStack.translate(0.0D, -0.5D, 0.0D);
dispatcher.render(display, 0.0D, 0.0D, 0.0D, spin, partialTick,
poseStack, buffer, packedLight);
poseStack.popPose();
}
}
@@ -0,0 +1,76 @@
package com.craisinlord.antarchy.content.command;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.block.entity.QueenTrailSpawnMarkerBlockEntity;
import com.craisinlord.antarchy.content.worldgen.thoraxis.QueenTrailGrid;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.context.CommandContext;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.core.BlockPos;
import net.minecraft.core.registries.Registries;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.ClickEvent;
import net.minecraft.network.chat.Style;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.Mth;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
public final class QueenLocateCommand {
private static final ResourceKey<Level> THORAXIS = ResourceKey.create(Registries.DIMENSION,
new ResourceLocation(Antarchy.MODID, "thoraxis"));
private QueenLocateCommand() {}
public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
dispatcher.register(Commands.literal("locatequeen").requires(source -> source.hasPermission(2))
.executes(QueenLocateCommand::locate));
}
private static int locate(CommandContext<CommandSourceStack> context) {
CommandSourceStack source = context.getSource();
ServerLevel level = source.getServer().getLevel(THORAXIS);
if (level == null) { source.sendFailure(Component.literal("Thoraxis is not available.")); return 0; }
int ox = Mth.floor(source.getPosition().x), oz = Mth.floor(source.getPosition().z);
int cx = Math.floorDiv(ox, QueenTrailGrid.SPACING), cz = Math.floorDiv(oz, QueenTrailGrid.SPACING);
List<QueenTrailGrid.Site> sites = new ArrayList<>();
for (int x = cx - 8; x <= cx + 8; x++) for (int z = cz - 8; z <= cz + 8; z++) sites.add(QueenTrailGrid.site(level.getSeed(), x, z));
sites.sort(Comparator.comparingDouble(site -> distanceSquared(ox, oz, site)));
for (int i = 0; i < Math.min(64, sites.size()); i++) {
QueenTrailGrid.Site site = sites.get(i);
level.getChunk(site.terminalX() >> 4, site.terminalZ() >> 4);
QueenTrailSpawnMarkerBlockEntity marker = findMarker(level, site);
if (marker == null) continue;
BlockPos spawn = marker.getSpawnPos();
int distance = Mth.floor(Math.sqrt(distanceSquared(ox, oz, site)));
String coords = "[" + spawn.getX() + ", " + spawn.getY() + ", " + spawn.getZ() + "]";
String tp = "/execute in " + THORAXIS.location() + " run tp @s " + spawn.getX() + " " + spawn.getY() + " " + spawn.getZ();
source.sendSuccess(() -> Component.literal("Nearest Queen spawn in " + THORAXIS.location() + ": ")
.append(Component.literal(coords).withStyle(Style.EMPTY.withColor(net.minecraft.ChatFormatting.AQUA)
.withUnderlined(true).withClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, tp))))
.append(Component.literal(" (" + distance + " blocks away)")), false);
return distance;
}
source.sendFailure(Component.literal("No configured Queen spawn was found within the search area."));
return 0;
}
private static QueenTrailSpawnMarkerBlockEntity findMarker(ServerLevel level, QueenTrailGrid.Site site) {
BlockPos.MutableBlockPos cursor = new BlockPos.MutableBlockPos(site.terminalX(), level.getMinBuildHeight(), site.terminalZ());
for (int y = level.getMinBuildHeight(); y < level.getMaxBuildHeight(); y++) {
cursor.setY(y);
BlockEntity entity = level.getBlockEntity(cursor);
if (entity instanceof QueenTrailSpawnMarkerBlockEntity marker && marker.isConfigured() && marker.getSiteId() == site.id()) return marker;
}
return null;
}
private static double distanceSquared(int ox, int oz, QueenTrailGrid.Site site) {
double dx = site.terminalX() - ox, dz = site.terminalZ() - oz;
return dx * dx + dz * dz;
}
}
@@ -306,7 +306,7 @@ public final class CreativeTabContents {
entry("scorpion_whip", "combat"),
entry("fallen_king_crown", "combat"),
entry("brutalfly_elytra", "combat"),
entry("jumpy_boots", "combat"),
entry("springy_boots", "combat"),
entry("raw_uranium_scrap", "ingredients"),
entry("raw_titanium_scrap", "ingredients"),
entry("bluestone", "redstone_blocks"),
@@ -0,0 +1,41 @@
package com.craisinlord.antarchy.content.enchantment;
import java.util.Map;
import java.util.function.Supplier;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.enchantment.Enchantment;
import net.minecraft.world.item.enchantment.EnchantmentHelper;
/** Loader-neutral access to the 1.20 registered enchantments. */
public final class AntarchyEnchantments {
private static Supplier<? extends Enchantment> featherRising = () -> null;
private static Supplier<? extends Enchantment> chronosphere = () -> null;
private AntarchyEnchantments() {}
public static void bind(Supplier<? extends Enchantment> feather, Supplier<? extends Enchantment> chrono) {
featherRising = feather;
chronosphere = chrono;
}
public static Enchantment featherRising() { return featherRising.get(); }
public static Enchantment chronosphere() { return chronosphere.get(); }
public static int featherRisingLevel(LivingEntity entity) { return level(entity, EquipmentSlot.FEET, featherRising()); }
public static int chronosphereLevel(LivingEntity entity) { return level(entity, EquipmentSlot.CHEST, chronosphere()); }
private static int level(LivingEntity entity, EquipmentSlot slot, Enchantment enchantment) {
return enchantment == null ? 0 : EnchantmentHelper.getItemEnchantmentLevel(enchantment, entity.getItemBySlot(slot));
}
public static void ensure(ItemStack stack, Enchantment enchantment, int level) {
if (enchantment == null) return;
Map<Enchantment, Integer> enchantments = new java.util.HashMap<>(EnchantmentHelper.getEnchantments(stack));
if (enchantments.getOrDefault(enchantment, 0) < level) {
enchantments.put(enchantment, level);
EnchantmentHelper.setEnchantments(enchantments, stack);
}
}
}
@@ -0,0 +1,20 @@
package com.craisinlord.antarchy.content.enchantment;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.item.enchantment.Enchantment;
import net.minecraft.world.item.enchantment.EnchantmentCategory;
public final class ChronosphereEnchantment extends Enchantment {
public ChronosphereEnchantment(Rarity rarity, EquipmentSlot... slots) {
super(rarity, EnchantmentCategory.ARMOR_CHEST, slots);
}
@Override
public int getMaxLevel() { return 3; }
@Override
public int getMinCost(int level) { return 15 + (level - 1) * 15; }
@Override
public int getMaxCost(int level) { return getMinCost(level) + 35; }
}
@@ -0,0 +1,14 @@
package com.craisinlord.antarchy.content.enchantment;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.item.enchantment.Enchantment;
import net.minecraft.world.item.enchantment.EnchantmentCategory;
public final class FeatherRisingEnchantment extends Enchantment {
public FeatherRisingEnchantment(Rarity rarity, EquipmentSlot... slots) {
super(rarity, EnchantmentCategory.ARMOR_FEET, slots);
}
@Override
public int getMaxLevel() { return 3; }
}
@@ -6,6 +6,9 @@ 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 com.craisinlord.antarchy.content.gravity.AntarchyGravityDirection;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityTransition;
import com.craisinlord.antarchy.content.worldgen.thoraxis.ThoraxisUndersideManager;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@@ -41,11 +44,39 @@ 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 long naturalTrailSiteId = Long.MIN_VALUE;
private BlockPos naturalTrailHome = BlockPos.ZERO;
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);
public static QueenEntity spawnFromUndersideTrail(ServerLevel level, BlockPos spawnPos, BlockPos homePos,
long siteId, float yaw) {
if (!ThoraxisUndersideManager.isThoraxis(level) || spawnPos.getY() >= 0) return null;
java.util.List<QueenEntity> existing = level.getEntitiesOfClass(QueenEntity.class,
new net.minecraft.world.phys.AABB(homePos).inflate(160.0D),
queen -> queen.naturalTrailSiteId == siteId && queen.isAlive());
if (!existing.isEmpty()) return existing.get(0);
QueenEntity queen = AntarchyObjects.QUEEN.get().create(level);
if (queen == null) return null;
queen.moveTo(spawnPos.getX() + .5D, spawnPos.getY(), spawnPos.getZ() + .5D, yaw, 0.0F);
if (!level.noCollision(queen, queen.getBoundingBox())) {
queen.discard();
return null;
}
queen.finalizeSpawn(level, level.getCurrentDifficultyAt(spawnPos), MobSpawnType.CHUNK_GENERATION, null, null);
queen.naturalTrailSiteId = siteId;
queen.naturalTrailHome = homePos.immutable();
queen.setPersistenceRequired();
queen.setNoGravity(false);
ThoraxisUndersideManager.applyUndersideInversion(queen);
AntarchyGravityApi.setGravityDirection(queen, AntarchyGravityDirection.UP, true,
new AntarchyGravityTransition(12));
queen.resetFallDistance();
return level.addFreshEntity(queen) ? queen : null;
}
private static final int GRAVITY_STOMP_COOLDOWN = 180;
private static final int TIME_FIELD_COOLDOWN = 260;
private static final int MOMENTUM_LOCK_COOLDOWN = 420;
@@ -8,7 +8,7 @@ public final class JumpyBootsHelper {
public static final float MAX_VERTICAL_BOOST = 2.0F;
public static final float SPRINT_FORWARD_BOOST = 0.3F;
public static final long FALL_PROTECTION_TICKS = 120L;
public static final String FALL_PROTECTION_NBT_KEY = "antarchy:jumpy_boots_protection_until";
public static final String FALL_PROTECTION_NBT_KEY = "antarchy:springy_boots_protection_until";
private JumpyBootsHelper() {
}
@@ -28,7 +28,7 @@ public final class JumpyBootsItem extends ArmorItem {
@Override
public void appendHoverText(ItemStack stack, Level level, List<Component> tooltipComponents, TooltipFlag tooltipFlag) {
tooltipComponents.add(Component.translatable("tooltip.antarchy.jumpy_boots").withStyle(ChatFormatting.DARK_RED));
tooltipComponents.add(Component.translatable("tooltip.antarchy.springy_boots").withStyle(ChatFormatting.DARK_RED));
super.appendHoverText(stack, level, tooltipComponents, tooltipFlag);
}
}
@@ -9,7 +9,7 @@ import net.minecraft.resources.ResourceLocation;
public record JumpyBootsLaunchPayload(int chargeTicks, boolean sprinting) implements CustomPacketPayload {
public static final Type<JumpyBootsLaunchPayload> TYPE =
new Type<>(new ResourceLocation(Antarchy.MODID, "jumpy_boots_launch"));
new Type<>(new ResourceLocation(Antarchy.MODID, "springy_boots_launch"));
public static final StreamCodec<ByteBuf, JumpyBootsLaunchPayload> STREAM_CODEC =
StreamCodec.composite(
ByteBufCodecs.VAR_INT, JumpyBootsLaunchPayload::chargeTicks,
@@ -0,0 +1,49 @@
package com.craisinlord.antarchy.content.time;
import com.craisinlord.antarchy.content.enchantment.AntarchyEnchantments;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
public final class ChronosphereManager {
public static final double RATE = 0.35D;
private static final Map<UUID, UUID> ACTIVE_FIELDS = new HashMap<>();
private ChronosphereManager() {}
public static void refresh(ServerPlayer player) {
if (!(player.level() instanceof ServerLevel level)) return;
int enchantmentLevel = AntarchyEnchantments.chronosphereLevel(player);
UUID playerId = player.getUUID();
TimeDilationFieldEntity field = null;
UUID known = ACTIVE_FIELDS.get(playerId);
if (known != null && level.getEntity(known) instanceof TimeDilationFieldEntity existing) field = existing;
if (enchantmentLevel <= 0 || !player.isAlive()) {
ACTIVE_FIELDS.remove(playerId);
if (field != null) field.discard();
return;
}
if (field == null) {
for (net.minecraft.world.entity.Entity entity : level.getAllEntities()) {
if (entity instanceof TimeDilationFieldEntity candidate && candidate.isChronosphere() && candidate.isOwnedBy(player)) {
field = candidate;
break;
}
}
}
if (field == null) {
field = TimeDilationApi.createField(level, player.position(), radius(enchantmentLevel), RATE, -1, player.getUUID());
field.configureChronosphere(player);
} else {
field.configureChronosphere(player);
}
field.setFieldRadius(radius(enchantmentLevel));
ACTIVE_FIELDS.put(playerId, field.getUUID());
}
public static double radius(int level) {
return level == 1 ? 1.5D : level == 2 ? 2.5D : 3.5D;
}
}
@@ -7,7 +7,11 @@ import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.entity.projectile.Projectile;
import net.minecraft.world.level.Level;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.phys.Vec3;
import java.util.UUID;
@@ -25,6 +29,10 @@ public class TimeDilationFieldEntity extends Entity {
private int durationTicks = -1;
private int age;
private UUID ownerId;
private UUID anchorId;
private int missingAnchorTicks;
private boolean visual = true;
private boolean chronosphere;
public TimeDilationFieldEntity(EntityType<? extends TimeDilationFieldEntity> entityType, Level level) {
super(entityType, level);
@@ -50,6 +58,17 @@ public class TimeDilationFieldEntity extends Entity {
return this.ownerId != null && entity != null && this.ownerId.equals(entity.getUUID());
}
public void attachTo(Entity anchor) { this.anchorId = anchor == null ? null : anchor.getUUID(); this.missingAnchorTicks = 0; }
public void configureChronosphere(Entity owner) {
this.ownerId = owner.getUUID(); attachTo(owner); this.durationTicks = -1; this.visual = false; this.chronosphere = true;
}
public boolean isChronosphere() { return chronosphere; }
public boolean isVisual() { return visual; }
public boolean affects(Entity entity) {
if (isOwnedBy(entity) || anchorId != null && anchorId.equals(entity.getUUID())) return false;
return !chronosphere || entity instanceof LivingEntity || entity instanceof Projectile || entity instanceof ItemEntity;
}
@Override
protected void defineSynchedData() {
this.entityData.define(RADIUS, 8.0F);
@@ -62,6 +81,18 @@ public class TimeDilationFieldEntity extends Entity {
if (this.level().isClientSide) {
return;
}
if (anchorId != null && this.level() instanceof ServerLevel level) {
Entity anchor = level.getEntity(anchorId);
if (anchor != null && anchor.isAlive()) {
setPos(anchor.getX(), anchor.getY(), anchor.getZ());
missingAnchorTicks = 0;
if (chronosphere && age % 20 == 0) {
if (!(anchor instanceof net.minecraft.server.level.ServerPlayer player)
|| com.craisinlord.antarchy.content.enchantment.AntarchyEnchantments.chronosphereLevel(player) <= 0) { discard(); return; }
setFieldRadius(ChronosphereManager.radius(com.craisinlord.antarchy.content.enchantment.AntarchyEnchantments.chronosphereLevel(player)));
}
} else if (++missingAnchorTicks > 20) { discard(); return; }
}
this.age++;
if (this.durationTicks >= 0 && this.age >= this.durationTicks) {
this.discard();
@@ -81,6 +112,8 @@ public class TimeDilationFieldEntity extends Entity {
return radius * radius;
}
public double influenceRadius() { return chronosphere ? fieldRadius() * 2.0D : fieldRadius(); }
public void setFieldRate(double rate) {
this.entityData.set(RATE, (float) TimeDilationMath.clampRate(rate));
}
@@ -117,6 +150,9 @@ public class TimeDilationFieldEntity extends Entity {
this.durationTicks = tag.contains(DURATION_KEY) ? tag.getInt(DURATION_KEY) : -1;
this.age = tag.getInt(AGE_KEY);
this.ownerId = tag.hasUUID("OwnerUuid") ? tag.getUUID("OwnerUuid") : null;
this.anchorId = tag.hasUUID("AnchorUuid") ? tag.getUUID("AnchorUuid") : null;
this.visual = !tag.contains("Visual") || tag.getBoolean("Visual");
this.chronosphere = tag.getBoolean("Chronosphere");
}
@Override
@@ -128,5 +164,8 @@ public class TimeDilationFieldEntity extends Entity {
if (this.ownerId != null) {
tag.putUUID("OwnerUuid", this.ownerId);
}
if (this.anchorId != null) tag.putUUID("AnchorUuid", this.anchorId);
tag.putBoolean("Visual", this.visual);
tag.putBoolean("Chronosphere", this.chronosphere);
}
}
@@ -32,7 +32,7 @@ public final class TimeDilationFieldSampler {
public static double sample(List<TimeDilationFieldEntity> fields, Entity entity) {
double combinedRate = TimeDilationMath.NORMAL_RATE;
for (TimeDilationFieldEntity field : fields) {
if (field.isOwnedBy(entity)) {
if (!field.affects(entity)) {
continue;
}
Vec3 center = field.position();
@@ -40,8 +40,18 @@ public final class TimeDilationFieldSampler {
double dy = center.y - entity.getY();
double dz = center.z - entity.getZ();
double distanceSqr = dx * dx + dy * dy + dz * dz;
if (distanceSqr < field.fieldRadiusSqr()) {
double falloff = TimeDilationMath.gaussianFalloff(Math.sqrt(distanceSqr), field.fieldRadius());
double distance = Math.sqrt(distanceSqr);
double radius = field.fieldRadius();
double falloff;
if (field.isChronosphere()) {
if (distance >= radius * 2.0D) continue;
falloff = distance <= radius ? 1.0D : TimeDilationMath.gaussianFalloff(distance - radius, radius);
} else if (distanceSqr < field.fieldRadiusSqr()) {
falloff = TimeDilationMath.gaussianFalloff(distance, radius);
} else {
continue;
}
if (falloff > 0.0D) {
combinedRate *= TimeDilationMath.localFieldRate(field.effectiveFieldRate(), falloff);
}
}
@@ -27,6 +27,9 @@ public final class TimeDilationManager {
public static void tickServer(MinecraftServer server) {
for (ServerLevel level : server.getAllLevels()) {
for (ServerPlayer player : level.players()) {
ChronosphereManager.refresh(player);
}
List<TimeDilationFieldEntity> fields = collectFields(level);
TrackingState tracking = TRACKING.computeIfAbsent(level, ignored -> new TrackingState());
discoverFieldEntities(level, fields, tracking);
@@ -56,6 +59,7 @@ public final class TimeDilationManager {
private static void syncFieldSnapshots(ServerLevel level, List<TimeDilationFieldEntity> fields, TrackingState tracking) {
List<TimeDilationFieldSnapshot> snapshots = fields.stream()
.filter(TimeDilationFieldEntity::isVisual)
.map(field -> new TimeDilationFieldSnapshot(field.getX(), field.getY(), field.getZ(),
field.fieldRadius(), field.fieldRate(), field.fieldAge(), field.fieldDurationTicks()))
.limit(128)
@@ -81,7 +85,7 @@ public final class TimeDilationManager {
private static void discoverFieldEntities(ServerLevel level, List<TimeDilationFieldEntity> fields, TrackingState tracking) {
for (TimeDilationFieldEntity field : fields) {
double radius = field.fieldRadius();
double radius = field.influenceRadius();
var center = field.position();
var area = new net.minecraft.world.phys.AABB(center.x - radius, center.y - radius, center.z - radius,
center.x + radius, center.y + radius, center.z + radius);
@@ -18,7 +18,7 @@ public final class TimeDilationParticles {
return;
}
for (TimeDilationFieldEntity field : fields) {
spawnBorder(level, field.position(), field.fieldRadius());
if (field.isVisual()) spawnBorder(level, field.position(), field.fieldRadius());
}
}
@@ -17,7 +17,9 @@ import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext;
import net.minecraft.world.level.levelgen.feature.configurations.NoneFeatureConfiguration;
public final class JumpyBugEggCeilingFeature extends Feature<NoneFeatureConfiguration> {
private static final ResourceLocation JUMPY_BUG_EGG_ID = new ResourceLocation(Antarchy.MODID, "jumpy_bug_egg");
// The 1.20 implementation keeps its historical Java name, but the public
// registry id follows the 1.21 rename.
private static final ResourceLocation JUMPY_BUG_EGG_ID = new ResourceLocation(Antarchy.MODID, "springbug_egg");
private static final int SEARCH_RADIUS = 8;
private static final int VERTICAL_SCAN = 24;
private static final int SEARCH_ATTEMPTS = 24;
@@ -0,0 +1,107 @@
package com.craisinlord.antarchy.content.worldgen.thoraxis;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.block.entity.QueenTrailSpawnMarkerBlockEntity;
import net.minecraft.core.BlockPos;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.feature.Feature;
import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext;
import net.minecraft.world.level.levelgen.feature.configurations.NoneFeatureConfiguration;
/** Generates the deterministic underside Queen trail and its one-shot spawn marker. */
public final class QueenHavocTrailFeature extends Feature<NoneFeatureConfiguration> {
private static final ResourceLocation DREAM_SAND = id("dream_sand");
private static final ResourceLocation DREAM_SANDSTONE = id("dream_sandstone");
private static final ResourceLocation CUT_DREAM_SANDSTONE = id("cut_dream_sandstone");
private static final ResourceLocation DREAM_FIRE = id("dream_fire_ceiling");
private static final ResourceLocation SPAWN_MARKER = id("queen_trail_spawn_marker");
public QueenHavocTrailFeature() { super(NoneFeatureConfiguration.CODEC); }
@Override
public boolean place(FeaturePlaceContext<NoneFeatureConfiguration> context) {
WorldGenLevel level = context.level();
ChunkPos chunk = new ChunkPos(context.origin());
int cellX = Math.floorDiv(chunk.getMiddleBlockX(), QueenTrailGrid.SPACING);
int cellZ = Math.floorDiv(chunk.getMiddleBlockZ(), QueenTrailGrid.SPACING);
boolean placed = false;
for (int gx = cellX - 1; gx <= cellX + 1; gx++) for (int gz = cellZ - 1; gz <= cellZ + 1; gz++) {
QueenTrailGrid.Site site = QueenTrailGrid.site(level.getSeed(), gx, gz);
int reach = site.length() + 30;
if (site.terminalX() + reach < chunk.getMinBlockX() || site.terminalX() - reach > chunk.getMaxBlockX()
|| site.terminalZ() + reach < chunk.getMinBlockZ() || site.terminalZ() - reach > chunk.getMaxBlockZ()) continue;
for (int i = 0; i < site.footprints(); i++) {
double distance = 18.0D + (site.footprints() - 1 - i) * site.stride();
double side = (i & 1) == 0 ? -2.5D : 2.5D;
double cx = site.terminalX() - site.directionX() * distance - site.directionZ() * side;
double cz = site.terminalZ() - site.directionZ() * distance + site.directionX() * side;
placed |= print(level, chunk, site, i, cx, cz);
}
if (site.terminalX() >= chunk.getMinBlockX() && site.terminalX() <= chunk.getMaxBlockX()
&& site.terminalZ() >= chunk.getMinBlockZ() && site.terminalZ() <= chunk.getMaxBlockZ()) placed |= marker(level, site);
}
return placed;
}
private static boolean print(WorldGenLevel level, ChunkPos chunk, QueenTrailGrid.Site site, int index, double cx, double cz) {
boolean placed = false;
for (int x = Math.max(chunk.getMinBlockX(), (int) cx - 4); x <= Math.min(chunk.getMaxBlockX(), (int) cx + 4); x++)
for (int z = Math.max(chunk.getMinBlockZ(), (int) cz - 4); z <= Math.min(chunk.getMaxBlockZ(), (int) cz + 4); z++) {
double dx = x + .5D - cx, dz = z + .5D - cz;
double forward = dx * site.directionX() + dz * site.directionZ();
double lateral = -dx * site.directionZ() + dz * site.directionX();
boolean pad = sq((forward + .5D) / 2.5D) + sq(lateral / 2.0D) <= 1.0D;
boolean toes = toe(forward, lateral, -1.5D) || toe(forward, lateral, 0.0D) || toe(forward, lateral, 1.5D);
if (pad || toes) placed |= carve(level, x, z, index > site.footprints() * .78D ? 3 : index > site.footprints() * .35D ? 2 : 1,
index % 3 == 0 ? CUT_DREAM_SANDSTONE : DREAM_SANDSTONE);
}
return placed;
}
private static boolean marker(WorldGenLevel level, QueenTrailGrid.Site site) {
BlockPos ceiling = findCeiling(level, site.terminalX(), site.terminalZ());
if (ceiling == null) return false;
BlockPos markerPos = ceiling.above(4);
level.setBlock(markerPos, block(SPAWN_MARKER, Blocks.BARRIER).defaultBlockState(), 2);
BlockEntity entity = level.getBlockEntity(markerPos);
if (!(entity instanceof QueenTrailSpawnMarkerBlockEntity marker)) return false;
marker.configure(site.id(), new BlockPos(site.terminalX(), ceiling.getY() - 17, site.terminalZ()),
new BlockPos(site.terminalX(), ceiling.getY() - 2, site.terminalZ()),
(float) Math.toDegrees(Math.atan2(site.directionZ(), site.directionX())) - 90.0F);
return true;
}
private static boolean carve(WorldGenLevel level, int x, int z, int depth, ResourceLocation backingId) {
BlockPos ceiling = findCeiling(level, x, z);
if (ceiling == null) return false;
level.setBlock(ceiling.above(depth), block(backingId, Blocks.SANDSTONE).defaultBlockState(), 2);
for (int i = 0; i < depth; i++) level.setBlock(ceiling.above(i), Blocks.AIR.defaultBlockState(), 2);
return true;
}
private static BlockPos findCeiling(WorldGenLevel level, int x, int z) {
BlockPos.MutableBlockPos cursor = new BlockPos.MutableBlockPos(x, -8, z);
int minimum = Math.max(level.getMinBuildHeight() + 20, -240);
for (int y = -8; y >= minimum; y--) {
cursor.setY(y);
if (isDreamSurface(level.getBlockState(cursor)) && level.getBlockState(cursor.below()).isAir()) return cursor.immutable();
}
return null;
}
private static boolean isDreamSurface(BlockState state) {
ResourceLocation id = BuiltInRegistries.BLOCK.getKey(state.getBlock());
return DREAM_SAND.equals(id) || DREAM_SANDSTONE.equals(id) || CUT_DREAM_SANDSTONE.equals(id);
}
private static boolean toe(double forward, double lateral, double center) { return sq((forward - 2.2D) / 1.25D) + sq((lateral - center) / .8D) <= 1.0D; }
private static double sq(double value) { return value * value; }
private static Block block(ResourceLocation id, Block fallback) { return BuiltInRegistries.BLOCK.getOptional(id).orElse(fallback); }
private static ResourceLocation id(String path) { return new ResourceLocation(Antarchy.MODID, path); }
}
@@ -0,0 +1,34 @@
package com.craisinlord.antarchy.content.worldgen.thoraxis;
import net.minecraft.util.RandomSource;
/** Shared deterministic layout used by Queen trail generation and locatequeen. */
public final class QueenTrailGrid {
public static final int SPACING = 1_536;
private static final int JITTER_MARGIN = 256;
private QueenTrailGrid() {}
public static Site site(long worldSeed, int cellX, int cellZ) {
long seed = mix(worldSeed ^ 0x51A7E4D39B20C16DL, cellX, cellZ);
RandomSource random = RandomSource.create(seed);
int usable = SPACING - JITTER_MARGIN * 2;
int x = cellX * SPACING + JITTER_MARGIN + random.nextInt(usable);
int z = cellZ * SPACING + JITTER_MARGIN + random.nextInt(usable);
double angle = random.nextDouble() * Math.PI * 2.0D;
return new Site(seed, x, z, Math.cos(angle), Math.sin(angle), 16 + random.nextInt(7), 6 + random.nextInt(2));
}
private static long mix(long seed, int x, int z) {
long value = seed ^ (long) x * 0x9E3779B97F4A7C15L ^ (long) z * 0xC2B2AE3D27D4EB4FL;
value ^= value >>> 30;
value *= 0xBF58476D1CE4E5B9L;
value ^= value >>> 27;
value *= 0x94D049BB133111EBL;
return value ^ value >>> 31;
}
public record Site(long id, int terminalX, int terminalZ, double directionX, double directionZ, int footprints, int stride) {
public int length() { return this.footprints * this.stride + 18; }
}
}
@@ -0,0 +1,61 @@
package com.craisinlord.antarchy.content.worldgen.thoraxis;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.feature.Feature;
import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext;
import net.minecraft.world.level.levelgen.feature.configurations.NoneFeatureConfiguration;
/** Carves the vertical transition throat between the upper world and Thoraxis underside. */
public final class ThoraxisTransitionThroatFeature extends Feature<NoneFeatureConfiguration> {
public ThoraxisTransitionThroatFeature() { super(NoneFeatureConfiguration.CODEC); }
@Override
public boolean place(FeaturePlaceContext<NoneFeatureConfiguration> context) {
WorldGenLevel level = context.level();
BlockPos origin = context.origin();
BlockPos center = new BlockPos(origin.getX(), ThoraxisUndersideManager.GRAVITY_FLIP_Y, origin.getZ());
int upperSurface = level.getHeight(Heightmap.Types.WORLD_SURFACE_WG, center.getX(), center.getZ()) - 1;
int undersideSurface = findUndersideSurface(level, center);
if (upperSurface < ThoraxisUndersideManager.GRAVITY_FLIP_Y || undersideSurface < level.getMinBuildHeight()) return false;
long seed = context.random().nextLong();
for (int y = undersideSurface; y <= upperSurface; y++) {
int layerX = noise(seed, Math.floorDiv(y, 5), 17) * 2;
int layerZ = noise(seed, Math.floorDiv(y, 5), 31) * 2;
double radiusX = 3.5D + noise01(seed, Math.floorDiv(y, 11), 43) * 2.0D;
double radiusZ = 3.5D + noise01(seed, Math.floorDiv(y, 13), 59) * 2.0D;
for (int x = -7; x <= 7; x++) for (int z = -7; z <= 7; z++) {
double dx = x - layerX, dz = z - layerZ;
double distance = Math.sqrt((dx * dx) / (radiusX * radiusX) + (dz * dz) / (radiusZ * radiusZ));
if (distance <= 0.72D + noise01(seed, x * 3 + Math.floorDiv(y, 3), z * 3 + 71) * 0.42D)
level.setBlock(center.offset(x, y, z), Blocks.AIR.defaultBlockState(), 2);
}
}
return true;
}
private static int findUndersideSurface(WorldGenLevel level, BlockPos center) {
int minY = level.getMinBuildHeight();
int firstSolid = Integer.MIN_VALUE;
for (int y = ThoraxisUndersideManager.GRAVITY_FLIP_Y - 1; y >= minY; y--)
if (!level.getBlockState(center.atY(y)).isAir()) { firstSolid = y; break; }
if (firstSolid == Integer.MIN_VALUE) return minY;
int y = firstSolid;
while (y > minY && !level.getBlockState(center.atY(y - 1)).isAir()) y--;
return y;
}
private static double noise01(long seed, int x, int z) {
long value = seed + 0x9E3779B97F4A7C15L;
value ^= x * 0x632BE59BD9B4E019L;
value ^= z * 0xC6BC279692B5CC83L;
value = (value ^ (value >>> 30)) * 0xBF58476D1CE4E5B9L;
value = (value ^ (value >>> 27)) * 0x94D049BB133111EBL;
value ^= value >>> 31;
return (value >>> 11) * 0x1.0p-53;
}
private static int noise(long seed, int x, int z) { return noise01(seed, x, z) < 0.5D ? -1 : 1; }
}
@@ -222,4 +222,9 @@ public final class ThoraxisUndersideManager {
public static boolean isAboveUndersideExit(Entity entity) {
return isThoraxis(entity.level()) && entity.getY() >= EXIT_UNDERSIDE_Y;
}
/** True while an entity is inside the inverted-gravity underside volume. */
public static boolean isInUnderside(Entity entity) {
return isThoraxis(entity.level()) && entity.getY() < EXIT_UNDERSIDE_Y;
}
}
@@ -1,11 +1,30 @@
package com.craisinlord.antarchy.mixins.gravity;
import com.craisinlord.antarchy.content.worldgen.thoraxis.ThoraxisUndersideManager;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import net.minecraft.world.entity.projectile.ThrowableProjectile;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
@Mixin(ThrowableProjectile.class)
/*
* Flips thrown projectile gravity for inverted shooters.
*/
public abstract class ThrowableProjectileGravityMixin {
@Shadow
protected abstract float getGravity();
@WrapOperation(method = "tick",
at = @At(value = "INVOKE",
target = "Lnet/minecraft/world/entity/projectile/ThrowableProjectile;setDeltaMovement(DDD)V"))
private void antarchy$reverseUndersideGravity(ThrowableProjectile projectile, double x, double y, double z,
Operation<Void> original) {
if (ThoraxisUndersideManager.isInUnderside(projectile)) {
// Vanilla has already subtracted getGravity(); add twice to turn it into an upward acceleration.
y += this.getGravity() * 2.0D;
}
original.call(projectile, x, y, z);
}
}
@@ -0,0 +1,22 @@
package com.craisinlord.antarchy.mixins.time;
import com.craisinlord.antarchy.content.enchantment.AntarchyEnchantments;
import com.craisinlord.antarchy.content.time.ChronosphereManager;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.item.ItemStack;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(LivingEntity.class)
public abstract class LivingEntityChronosphereEquipmentMixin {
@Inject(method = "setItemSlot", at = @At("TAIL"))
private void antarchy$refreshChronosphere(EquipmentSlot slot, ItemStack stack, CallbackInfo callback) {
if (slot != EquipmentSlot.CHEST) return;
LivingEntity self = (LivingEntity) (Object) this;
if (self instanceof ServerPlayer player) ChronosphereManager.refresh(player);
}
}
@@ -0,0 +1,19 @@
package com.craisinlord.antarchy.mixins.time;
import com.craisinlord.antarchy.content.time.TimeDilationApi;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemCooldowns;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(Player.class)
public abstract class PlayerCooldownTimeDilationMixin {
@Redirect(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/ItemCooldowns;tick()V"))
private void antarchy$advanceCooldowns(ItemCooldowns cooldowns) {
Player player = (Player) (Object) this;
if (TimeDilationApi.consumeTick(player, "player_item_cooldowns")) {
cooldowns.tick();
}
}
}