1.20 progres
king and queen balancing Antmail Blockle
This commit is contained in:
1 parent
c1d60d735d
commit
fc5028a15b
430 files changed
+30569
-1684
No files matched your search
+10
@@ -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
|
||||
}
|
||||
+118
@@ -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");
|
||||
}
|
||||
}
|
||||
+42
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
+90
@@ -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);
|
||||
}
|
||||
}
|
||||
+58
@@ -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);
|
||||
}
|
||||
}
|
||||
+83
@@ -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);
|
||||
}
|
||||
}
|
||||
+83
@@ -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);
|
||||
}
|
||||
}
|
||||
+113
@@ -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);
|
||||
}
|
||||
}
|
||||
+67
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+34
@@ -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();
|
||||
}
|
||||
}
|
||||
+38
@@ -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();
|
||||
}
|
||||
}
|
||||
+76
@@ -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;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -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"),
|
||||
|
||||
+41
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+20
@@ -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; }
|
||||
}
|
||||
+14
@@ -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; }
|
||||
}
|
||||
+31
@@ -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;
|
||||
|
||||
+1
-1
@@ -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() {
|
||||
}
|
||||
|
||||
+1
-1
@@ -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);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -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,
|
||||
|
||||
+49
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+39
@@ -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);
|
||||
}
|
||||
}
|
||||
+13
-3
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+5
-1
@@ -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);
|
||||
|
||||
+1
-1
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-1
@@ -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;
|
||||
|
||||
+107
@@ -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); }
|
||||
}
|
||||
+34
@@ -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; }
|
||||
}
|
||||
}
|
||||
+61
@@ -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; }
|
||||
}
|
||||
+5
@@ -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;
|
||||
}
|
||||
}
|
||||
+19
@@ -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);
|
||||
}
|
||||
}
|
||||
+22
@@ -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);
|
||||
}
|
||||
}
|
||||
+19
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user