new king sounds

worm sizes
performance improvements
This commit is contained in:
craisinlord committed 2026-09-09 22:55:40 -05:00
1 parent 89d41dd7e1
commit f4f4e0c717
96 files changed
+2070 -501

No files matched your search

@@ -254,6 +254,7 @@ public final class AntarchySettings {
private static double queenBlackHoleRadius = 14.0D;
private static int queenBlackHoleActiveTicks = 120;
private static double queenBlackHolePullStrength = 0.16D;
private static int queenBlackHoleBlockSuctionCap = 160;
private static double queenFinalTimeFieldRadius = 20.0D;
private static double queenFinalTimeFieldRate = 0.05D;
private static int queenFinalTimeFieldDurationTicks = 45;
@@ -273,7 +274,7 @@ public final class AntarchySettings {
private static double royalBossKnockbackResistance = 1.0D;
private static double royalBossStepHeight = 3.0D;
private static double royalBossMaxSingleHitDamage = 250.0D;
private static double royalBossBiteReach = 10.0D;
private static double royalBossBiteReach = 12.0D;
private static double royalBossBiteDamageMultiplier = 1.6D;
private static int royalBossBiteCooldownTicks = 45;
private static boolean basiliskPetrifyingGazeEnabled = true;
@@ -558,6 +559,10 @@ public final class AntarchySettings {
private static double queenBeamTracking = 0.1D;
private static double queenBeamTerrainRadius = 5.0D;
private static int queenBeamTerrainCap = 192;
private static int queenBeamPhaseOneCap = 1;
private static int queenBeamPhaseTwoCap = 2;
private static int queenBeamPhaseThreeCap = 3;
private static double queenBeamPhaseThreeMaxVolleyChance = 0.2D;
private static double kingBeamDamage = 50.0D;
private static double kingBeamRange = 100.0D;
private static double kingBeamMinimumRange = 32.0D;
@@ -2051,6 +2056,8 @@ public final class AntarchySettings {
public static void setQueenBlackHoleActiveTicks(int v) { queenBlackHoleActiveTicks = v; }
public static double queenBlackHolePullStrength() { return queenBlackHolePullStrength; }
public static void setQueenBlackHolePullStrength(double v) { queenBlackHolePullStrength = v; }
public static int queenBlackHoleBlockSuctionCap() { return queenBlackHoleBlockSuctionCap; }
public static void setQueenBlackHoleBlockSuctionCap(int v) { queenBlackHoleBlockSuctionCap = v; }
public static double queenFinalTimeFieldRadius() { return queenFinalTimeFieldRadius; }
public static void setQueenFinalTimeFieldRadius(double v) { queenFinalTimeFieldRadius = v; }
public static double queenFinalTimeFieldRate() { return queenFinalTimeFieldRate; }
@@ -2910,6 +2917,14 @@ public final class AntarchySettings {
public static void setQueenBeamTerrainRadius(double v) { queenBeamTerrainRadius = v; }
public static int queenBeamTerrainCap() { return queenBeamTerrainCap; }
public static void setQueenBeamTerrainCap(int v) { queenBeamTerrainCap = v; }
public static int queenBeamPhaseOneCap() { return queenBeamPhaseOneCap; }
public static void setQueenBeamPhaseOneCap(int v) { queenBeamPhaseOneCap = v; }
public static int queenBeamPhaseTwoCap() { return queenBeamPhaseTwoCap; }
public static void setQueenBeamPhaseTwoCap(int v) { queenBeamPhaseTwoCap = v; }
public static int queenBeamPhaseThreeCap() { return queenBeamPhaseThreeCap; }
public static void setQueenBeamPhaseThreeCap(int v) { queenBeamPhaseThreeCap = v; }
public static double queenBeamPhaseThreeMaxVolleyChance() { return queenBeamPhaseThreeMaxVolleyChance; }
public static void setQueenBeamPhaseThreeMaxVolleyChance(double v) { queenBeamPhaseThreeMaxVolleyChance = v; }
public static double kingBeamDamage() { return kingBeamDamage; }
public static void setKingBeamDamage(double v) { kingBeamDamage = v; }
public static double kingBeamRange() { return kingBeamRange; }
@@ -101,6 +101,8 @@ public final class AntarchyObjects {
@SuppressWarnings("unchecked")
public static Supplier<EntityType<com.craisinlord.antarchy.content.entity.royal.RoyalBlackHoleEntity>> ROYAL_BLACK_HOLE = (Supplier<EntityType<com.craisinlord.antarchy.content.entity.royal.RoyalBlackHoleEntity>>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<EntityType<com.craisinlord.antarchy.content.entity.royal.RoyalAssailantBlackHoleEntity>> ROYAL_ASSAILANT_BLACK_HOLE = (Supplier<EntityType<com.craisinlord.antarchy.content.entity.royal.RoyalAssailantBlackHoleEntity>>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Block> VORTEX_LENS = (Supplier<Block>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<BlockEntityType<VortexLensBlockEntity>> VORTEX_LENS_BLOCK_ENTITY = (Supplier<BlockEntityType<VortexLensBlockEntity>>) UNBOUND;
@@ -691,6 +693,10 @@ public final class AntarchyObjects {
ROYAL_BLACK_HOLE = supplier;
}
public static void setRoyalAssailantBlackHole(Supplier<EntityType<com.craisinlord.antarchy.content.entity.royal.RoyalAssailantBlackHoleEntity>> supplier) {
ROYAL_ASSAILANT_BLACK_HOLE = supplier;
}
public static void setVortexLens(Supplier<Block> supplier) {
VORTEX_LENS = supplier;
}
@@ -213,6 +213,9 @@ public final class AntarchySoundEvents {
public static Supplier<SoundEvent> PRINCESS_SHOOT = (Supplier<SoundEvent>) UNBOUND;
public static Supplier<SoundEvent> PRINCESS_FLY = (Supplier<SoundEvent>) UNBOUND;
public static Supplier<SoundEvent> PRINCESS_STEP = (Supplier<SoundEvent>) UNBOUND;
public static Supplier<SoundEvent> KING_DECREE = (Supplier<SoundEvent>) UNBOUND;
public static Supplier<SoundEvent> KING_SUCCESS = (Supplier<SoundEvent>) UNBOUND;
public static Supplier<SoundEvent> KING_JUDGEMENT = (Supplier<SoundEvent>) UNBOUND;
private AntarchySoundEvents() {
}
@@ -417,7 +420,10 @@ public final class AntarchySoundEvents {
Supplier<SoundEvent> princessBite,
Supplier<SoundEvent> princessShoot,
Supplier<SoundEvent> princessFly,
Supplier<SoundEvent> princessStep
Supplier<SoundEvent> princessStep,
Supplier<SoundEvent> kingDecree,
Supplier<SoundEvent> kingSuccess,
Supplier<SoundEvent> kingJudgement
) {
SQUIDZOOKA_FIRE = squidzookaFire;
SHRINK_RAY_CHARGE = shrinkRayCharge;
@@ -619,6 +625,9 @@ public final class AntarchySoundEvents {
PRINCESS_SHOOT = princessShoot;
PRINCESS_FLY = princessFly;
PRINCESS_STEP = princessStep;
KING_DECREE = kingDecree;
KING_SUCCESS = kingSuccess;
KING_JUDGEMENT = kingJudgement;
}
public static void bindDimensionalTearSounds(
@@ -85,6 +85,7 @@ public final class AntarchyTags {
public static final TagKey<EntityType<?>> DIMENSIONAL_TEAR_COMMON_SPAWNS = TagKey.create(Registries.ENTITY_TYPE, id("dimensional_tear_common_spawns"));
public static final TagKey<EntityType<?>> DIMENSIONAL_TEAR_RARE_SPAWNS = TagKey.create(Registries.ENTITY_TYPE, id("dimensional_tear_rare_spawns"));
public static final TagKey<EntityType<?>> TIME_DILATION_IMMUNE = TagKey.create(Registries.ENTITY_TYPE, id("time_dilation_immune"));
public static final TagKey<EntityType<?>> QUEEN_DOES_NOT_ATTACK = TagKey.create(Registries.ENTITY_TYPE, id("queen_does_not_attack"));
public static final TagKey<EntityType<?>> COMMANDED_BLACKLIST = TagKey.create(Registries.ENTITY_TYPE, id("commanded_blacklist"));
public static final TagKey<EntityType<?>> DILATED_BLACKLIST = TagKey.create(Registries.ENTITY_TYPE, id("dilated_blacklist"));
@@ -170,7 +170,7 @@ public abstract class RoyalEggBlock extends BaseEntityBlock {
return;
}
mount.moveTo(pos.getX() + 0.5D, pos.getY(), pos.getZ() + 0.5D, level.random.nextFloat() * 360.0F, 0.0F);
mount.setBaby(true);
mount.setGrowthProgress(0.0F);
mount.finalizeSpawn(level, level.getCurrentDifficultyAt(pos), MobSpawnType.BREEDING, null);
if (placer != null) {
mount.setTame(true, true);
@@ -17,6 +17,7 @@ public final class ClientTimeDilationTicker {
private static ClientLevel activeLevel;
private static final Map<ItemEntity, ItemSpinState> ITEM_SPIN_STATES = new WeakHashMap<>();
private static final Map<LivingEntity, AttackAnimationState> ATTACK_ANIMATION_STATES = new WeakHashMap<>();
private static final Map<LivingEntity, AnimationClockState> WALK_ANIMATION_STATES = new WeakHashMap<>();
private static final Map<Entity, AnimationClockState> ANIMATION_CLOCKS = new WeakHashMap<>();
private ClientTimeDilationTicker() {
@@ -28,9 +29,12 @@ public final class ClientTimeDilationTicker {
activeFields = List.of();
ITEM_SPIN_STATES.clear();
ATTACK_ANIMATION_STATES.clear();
WALK_ANIMATION_STATES.clear();
ANIMATION_CLOCKS.clear();
ContractionAfterimages.clear();
com.craisinlord.antarchy.content.time.TimeDilationApi.clearSyncedClientRates();
}
ContractionAfterimages.tick(level);
}
public static float dilateAnimationTime(Entity entity, float vanillaTime) {
@@ -87,6 +91,22 @@ public final class ClientTimeDilationTicker {
return rate >= TimeDilationMath.NORMAL_RATE ? amount : amount * (float) rate;
}
public static float dilateWalkPosition(LivingEntity entity, float vanillaPosition) {
double rate = com.craisinlord.antarchy.content.time.TimeDilationApi.getRate(entity);
if (Math.abs(rate - TimeDilationMath.NORMAL_RATE) < 0.001D) {
WALK_ANIMATION_STATES.remove(entity);
return vanillaPosition;
}
AnimationClockState state = WALK_ANIMATION_STATES.computeIfAbsent(entity,
ignored -> new AnimationClockState(vanillaPosition));
float delta = vanillaPosition - state.lastVanillaTime;
if (delta >= 0.0F) {
state.temporalTime += delta * (float) rate;
}
state.lastVanillaTime = vanillaPosition;
return state.temporalTime;
}
public static float dilateItemSpin(ItemEntity item, float vanillaSpin) {
double rate = com.craisinlord.antarchy.content.time.TimeDilationApi.getRate(item);
if (rate >= TimeDilationMath.NORMAL_RATE) {
@@ -5,19 +5,19 @@ import com.craisinlord.antarchy.content.entity.royal.RoyalBlackHoleEntity;
import net.minecraft.resources.ResourceLocation;
import software.bernie.geckolib.model.GeoModel;
public class RoyalBlackHoleModel extends GeoModel<RoyalBlackHoleEntity> {
public class RoyalBlackHoleModel<T extends RoyalBlackHoleEntity> extends GeoModel<T> {
@Override
public ResourceLocation getModelResource(RoyalBlackHoleEntity animatable) {
public ResourceLocation getModelResource(T animatable) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "geo/black_hole.geo.json");
}
@Override
public ResourceLocation getTextureResource(RoyalBlackHoleEntity animatable) {
public ResourceLocation getTextureResource(T animatable) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "textures/entity/queen/black_hole.png");
}
@Override
public ResourceLocation getAnimationResource(RoyalBlackHoleEntity animatable) {
public ResourceLocation getAnimationResource(T animatable) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "animations/black_hole.animation.json");
}
}
@@ -45,34 +45,30 @@ public class RoyalBossModel extends GeoModel<RoyalBossEntity> {
this.getBone(anchor).ifPresent(bone -> bone.setTrackingMatrices(true));
}
RoyalHead.Slot slot = animatable.getRoyalBeamHeadSlot();
Vec3 beamEnd = animatable.getRoyalBeamEndPosition();
if (!animatable.isFiringRoyalBeam() || slot == null || beamEnd == null) {
return;
}
float partialTick = animationState.getPartialTick();
Vec3 origin = animatable.getRoyalBeamShootFrom(partialTick);
Vec3 direction = beamEnd.subtract(origin);
double horizontal = Math.sqrt(direction.x * direction.x + direction.z * direction.z);
if (direction.lengthSqr() < 1.0E-6D || horizontal < 1.0E-4D) {
return;
}
for (RoyalHead.Slot slot : RoyalHead.Slot.values()) {
Vec3 beamEnd = animatable.getRoyalBeamEndPosition(slot);
if (!animatable.isFiringRoyalBeam(slot) || beamEnd == null) continue;
Vec3 origin = animatable.getRoyalBeamShootFrom(slot, partialTick);
Vec3 direction = beamEnd.subtract(origin);
double horizontal = Math.sqrt(direction.x * direction.x + direction.z * direction.z);
if (direction.lengthSqr() < 1.0E-6D || horizontal < 1.0E-4D) continue;
float desiredYaw = (float) (Mth.atan2(-direction.x, direction.z) * Mth.RAD_TO_DEG);
float bodyYaw = Mth.rotLerp(partialTick, animatable.yRotO, animatable.getYRot());
float relativeYaw = Mth.clamp(Mth.wrapDegrees(desiredYaw - bodyYaw), -55.0F, 55.0F) * Mth.DEG_TO_RAD;
float pitch = Mth.clamp((float) -Mth.atan2(direction.y, horizontal) * Mth.RAD_TO_DEG,
-45.0F, 45.0F) * Mth.DEG_TO_RAD;
float desiredYaw = (float) (Mth.atan2(-direction.x, direction.z) * Mth.RAD_TO_DEG);
float bodyYaw = Mth.rotLerp(partialTick, animatable.yRotO, animatable.getYRot());
float relativeYaw = Mth.clamp(Mth.wrapDegrees(desiredYaw - bodyYaw), -55.0F, 55.0F) * Mth.DEG_TO_RAD;
float pitch = Mth.clamp((float) -Mth.atan2(direction.y, horizontal) * Mth.RAD_TO_DEG,
-45.0F, 45.0F) * Mth.DEG_TO_RAD;
String[] chain = AIM_CHAINS[slot.ordinal()];
float yawPerBone = relativeYaw / chain.length;
float pitchPerBone = pitch / chain.length;
for (String boneName : chain) {
this.getBone(boneName).ifPresent(bone -> {
bone.setRotY(bone.getRotY() + yawPerBone);
bone.setRotX(bone.getRotX() + pitchPerBone);
});
String[] chain = AIM_CHAINS[slot.ordinal()];
float yawPerBone = relativeYaw / chain.length;
float pitchPerBone = pitch / chain.length;
for (String boneName : chain) {
this.getBone(boneName).ifPresent(bone -> {
bone.setRotY(bone.getRotY() + yawPerBone);
bone.setRotX(bone.getRotX() + pitchPerBone);
});
}
}
}
@@ -14,6 +14,9 @@ public class RoyalElementalProjectileModel extends GeoModel<RoyalElementalProjec
@Override
public ResourceLocation getTextureResource(RoyalElementalProjectileEntity animatable) {
if (animatable.isDreamFireball()) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "textures/entity/queen/dream_fire_ball.png");
}
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID,
"textures/entity/king/" + (animatable.isIceball() ? "ice_ball" : "fire_ball") + ".png");
}
@@ -6,22 +6,40 @@ import net.minecraft.resources.ResourceLocation;
import software.bernie.geckolib.model.GeoModel;
public class WormModel extends GeoModel<WormEntity> {
private static final ResourceLocation MODEL = ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "geo/worm_large.geo.json");
private static final ResourceLocation TEXTURE = ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "textures/entity/worm/worm_large.png");
private static final ResourceLocation ANIMATION = ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "animations/worm_large.animation.json");
private static final ResourceLocation SMALL_MODEL = ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "geo/worm_small.geo.json");
private static final ResourceLocation MEDIUM_MODEL = ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "geo/worm_medium.geo.json");
private static final ResourceLocation LARGE_MODEL = ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "geo/worm_large.geo.json");
private static final ResourceLocation SMALL_TEXTURE = ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "textures/entity/worm/worm_small.png");
private static final ResourceLocation MEDIUM_TEXTURE = ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "textures/entity/worm/worm_medium.png");
private static final ResourceLocation LARGE_TEXTURE = ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "textures/entity/worm/worm_large.png");
private static final ResourceLocation SMALL_ANIMATION = ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "animations/worm_small.animation.json");
private static final ResourceLocation MEDIUM_ANIMATION = ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "animations/worm_medium.animation.json");
private static final ResourceLocation LARGE_ANIMATION = ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "animations/worm_large.animation.json");
@Override
public ResourceLocation getModelResource(WormEntity animatable) {
return MODEL;
return switch (animatable.getGrowthStage()) {
case SMALL -> SMALL_MODEL;
case MEDIUM -> MEDIUM_MODEL;
case LARGE -> LARGE_MODEL;
};
}
@Override
public ResourceLocation getTextureResource(WormEntity animatable) {
return TEXTURE;
return switch (animatable.getGrowthStage()) {
case SMALL -> SMALL_TEXTURE;
case MEDIUM -> MEDIUM_TEXTURE;
case LARGE -> LARGE_TEXTURE;
};
}
@Override
public ResourceLocation getAnimationResource(WormEntity animatable) {
return ANIMATION;
return switch (animatable.getGrowthStage()) {
case SMALL -> SMALL_ANIMATION;
case MEDIUM -> MEDIUM_ANIMATION;
case LARGE -> LARGE_ANIMATION;
};
}
}
@@ -19,7 +19,11 @@ public class RoyalBlackHoleRenderer extends GeoEntityRenderer<RoyalBlackHoleEnti
@Override
public void render(RoyalBlackHoleEntity entity, float entityYaw, float partialTick, PoseStack poseStack,
MultiBufferSource bufferSource, int packedLight) {
poseStack.pushPose();
float scale = 1.5F * entity.visualGrowth();
poseStack.scale(scale, scale, scale);
super.render(entity, entityYaw, partialTick, poseStack, bufferSource, 0xF000F0);
poseStack.popPose();
}
@Override
@@ -5,6 +5,7 @@ import com.craisinlord.antarchy.content.entity.royal.RoyalBossEntity;
import com.craisinlord.antarchy.content.entity.royal.KingEntity;
import com.craisinlord.antarchy.content.entity.royal.RoyalHead;
import com.craisinlord.antarchy.content.entity.royal.beam.RoyalBeamElement;
import com.craisinlord.antarchy.content.effect.RoyalEffectHooks;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import net.minecraft.client.renderer.MultiBufferSource;
@@ -13,15 +14,23 @@ import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.Mth;
import net.minecraft.world.phys.Vec3;
import net.minecraft.client.renderer.texture.OverlayTexture;
import org.jetbrains.annotations.Nullable;
import software.bernie.geckolib.cache.object.BakedGeoModel;
import software.bernie.geckolib.renderer.GeoEntityRenderer;
import software.bernie.geckolib.renderer.layer.GeoRenderLayer;
import software.bernie.geckolib.util.Color;
public class RoyalBossRenderer extends GeoEntityRenderer<RoyalBossEntity> {
private static final ResourceLocation QUEEN_BEAM_OUTER = ResourceLocation.fromNamespaceAndPath("antarchy", "textures/entity/queen/queen_beam_outer.png");
private static final ResourceLocation QUEEN_BEAM_INNER = ResourceLocation.fromNamespaceAndPath("antarchy", "textures/entity/queen/queen_beam_inner.png");
private static final ResourceLocation QUEEN_BEAM_END = ResourceLocation.fromNamespaceAndPath("antarchy", "textures/entity/queen/queen_beam_end_1.png");
private static final ResourceLocation QUEEN_PURPLE_BEAM_OUTER = ResourceLocation.fromNamespaceAndPath("antarchy", "textures/entity/queen/queen_purple_beam_outer.png");
private static final ResourceLocation QUEEN_PURPLE_BEAM_INNER = ResourceLocation.fromNamespaceAndPath("antarchy", "textures/entity/queen/queen_purple_beam_inner.png");
private static final ResourceLocation QUEEN_PURPLE_BEAM_END = ResourceLocation.fromNamespaceAndPath("antarchy", "textures/entity/queen/queen_purple_beam_end_1.png");
private static final ResourceLocation QUEEN_RED_BEAM_OUTER = ResourceLocation.fromNamespaceAndPath("antarchy", "textures/entity/queen/queen_red_beam_outer.png");
private static final ResourceLocation QUEEN_RED_BEAM_INNER = ResourceLocation.fromNamespaceAndPath("antarchy", "textures/entity/queen/queen_red_beam_inner.png");
private static final ResourceLocation QUEEN_RED_BEAM_END = ResourceLocation.fromNamespaceAndPath("antarchy", "textures/entity/queen/queen_red_beam_end_1.png");
private static final ResourceLocation QUEEN_BLACK_BEAM_OUTER = ResourceLocation.fromNamespaceAndPath("antarchy", "textures/entity/queen/queen_black_beam_outer.png");
private static final ResourceLocation QUEEN_BLACK_BEAM_INNER = ResourceLocation.fromNamespaceAndPath("antarchy", "textures/entity/queen/queen_black_beam_inner.png");
private static final ResourceLocation QUEEN_BLACK_BEAM_END = ResourceLocation.fromNamespaceAndPath("antarchy", "textures/entity/queen/queen_black_beam_end_1.png");
private static final ResourceLocation KING_FIRE_BEAM_OUTER = ResourceLocation.fromNamespaceAndPath("antarchy", "textures/entity/king/fire_beam_outer.png");
private static final ResourceLocation KING_FIRE_BEAM_INNER = ResourceLocation.fromNamespaceAndPath("antarchy", "textures/entity/king/fire_beam_inner.png");
private static final ResourceLocation KING_FIRE_BEAM_END = ResourceLocation.fromNamespaceAndPath("antarchy", "textures/entity/king/fire_beam_end_1.png");
@@ -34,6 +43,45 @@ public class RoyalBossRenderer extends GeoEntityRenderer<RoyalBossEntity> {
public RoyalBossRenderer(EntityRendererProvider.Context context) {
super(context, new RoyalBossModel());
this.shadowRadius = 8.0F;
this.addRenderLayer(new RoyalAfterimageLayer(this));
this.addRenderLayer(new TemporalContractionGeoLayer<>(this));
}
private static final class RoyalAfterimageLayer extends GeoRenderLayer<RoyalBossEntity> {
private RoyalAfterimageLayer(RoyalBossRenderer renderer) {
super(renderer);
}
@Override
public void render(PoseStack poseStack, RoyalBossEntity animatable, BakedGeoModel bakedModel, @Nullable RenderType renderType,
MultiBufferSource bufferSource, @Nullable VertexConsumer buffer, float partialTick,
int packedLight, int packedOverlay) {
if (!animatable.isRoyalAccelerated() && (RoyalEffectHooks.contractedHolder() == null
|| !animatable.hasEffect(RoyalEffectHooks.contractedHolder()))) return;
Vec3 movement = animatable.getDeltaMovement();
if (movement.lengthSqr() < 1.0E-5D) return;
RenderType afterimageType = RenderType.entityTranslucent(this.getRenderer().getTextureLocation(animatable));
VertexConsumer afterimageBuffer = bufferSource.getBuffer(afterimageType);
int[] colors = {0x66FF5F6F, 0x4DFF4D60, 0x33FF3B52, 0x1FFF2944};
for (int i = colors.length - 1; i >= 0; i--) {
poseStack.pushPose();
Vec3 offset = movement.scale(-(i + 1) * 3.0D / RoyalBossEntity.MODEL_RENDER_SCALE);
poseStack.translate(offset.x, offset.y, offset.z);
this.getRenderer().reRender(
bakedModel,
poseStack,
bufferSource,
animatable,
afterimageType,
afterimageBuffer,
partialTick,
0xF000F0,
OverlayTexture.NO_OVERLAY,
colors[i]
);
poseStack.popPose();
}
}
}
@Override
@@ -68,18 +116,22 @@ public class RoyalBossRenderer extends GeoEntityRenderer<RoyalBossEntity> {
public void render(RoyalBossEntity entity, float entityYaw, float partialTick, PoseStack poseStack,
MultiBufferSource bufferSource, int packedLight) {
super.render(entity, entityYaw, partialTick, poseStack, bufferSource, packedLight);
Vec3 end = entity.getRoyalBeamEndPosition();
if (!entity.isFiringRoyalBeam() || end == null) {
return;
for (RoyalHead.Slot slot : RoyalHead.Slot.values()) {
if (entity.isFiringRoyalBeam(slot) && entity.getRoyalBeamEndPosition(slot) != null) {
renderBeam(entity, slot, partialTick, poseStack, bufferSource);
}
}
}
private void renderBeam(RoyalBossEntity entity, RoyalHead.Slot beamHead, float partialTick,
PoseStack poseStack, MultiBufferSource bufferSource) {
Vec3 end = entity.getRoyalBeamEndPosition(beamHead);
if (end == null) return;
double x = Mth.lerp(partialTick, entity.xo, entity.getX());
double y = Mth.lerp(partialTick, entity.yo, entity.getY());
double z = Mth.lerp(partialTick, entity.zo, entity.getZ());
RoyalHead.Slot beamHead = entity.getRoyalBeamHeadSlot();
Vec3 trackedAnchor = beamHead == null
? null
: ((RoyalBossModel) this.getGeoModel()).getTrackedBeamAnchor(beamHead);
Vec3 start = (trackedAnchor != null ? trackedAnchor : entity.getRoyalBeamShootFrom(partialTick))
Vec3 trackedAnchor = ((RoyalBossModel) this.getGeoModel()).getTrackedBeamAnchor(beamHead);
Vec3 start = (trackedAnchor != null ? trackedAnchor : entity.getRoyalBeamShootFrom(beamHead, partialTick))
.subtract(x, y, z);
Vec3 finish = end.subtract(x, y, z);
Vec3 axis = finish.subtract(start);
@@ -95,9 +147,9 @@ public class RoyalBossRenderer extends GeoEntityRenderer<RoyalBossEntity> {
side1 = side1.normalize();
Vec3 side2 = dir.cross(side1).normalize();
ResourceLocation outer = QUEEN_BEAM_OUTER;
ResourceLocation inner = QUEEN_BEAM_INNER;
ResourceLocation endTexture = QUEEN_BEAM_END;
ResourceLocation outer = QUEEN_PURPLE_BEAM_OUTER;
ResourceLocation inner = QUEEN_PURPLE_BEAM_INNER;
ResourceLocation endTexture = QUEEN_PURPLE_BEAM_END;
if (entity instanceof KingEntity) {
switch (entity.getRoyalBeamElement()) {
case FIRE -> {
@@ -118,6 +170,21 @@ public class RoyalBossRenderer extends GeoEntityRenderer<RoyalBossEntity> {
case GENERIC -> {
}
}
} else {
switch (entity.getRoyalBeamElement(beamHead)) {
case QUEEN_RED -> {
outer = QUEEN_RED_BEAM_OUTER;
inner = QUEEN_RED_BEAM_INNER;
endTexture = QUEEN_RED_BEAM_END;
}
case QUEEN_BLACK -> {
outer = QUEEN_BLACK_BEAM_OUTER;
inner = QUEEN_BLACK_BEAM_INNER;
endTexture = QUEEN_BLACK_BEAM_END;
}
default -> {
}
}
}
float time = entity.tickCount + partialTick;
@@ -125,19 +192,19 @@ public class RoyalBossRenderer extends GeoEntityRenderer<RoyalBossEntity> {
float scroll = -time * BEAM_SCROLL_SPEED;
var pose = poseStack.last().pose();
VertexConsumer outerBuffer = bufferSource.getBuffer(RenderType.entityTranslucent(outer));
VertexConsumer outerBuffer = bufferSource.getBuffer(RenderType.entityTranslucentEmissive(outer));
drawBeamPlane(outerBuffer, pose, start, finish, side1.scale(BEAM_OUTER_RADIUS), scroll, scroll + tiles);
drawBeamPlane(outerBuffer, pose, start, finish, side2.scale(BEAM_OUTER_RADIUS), scroll, scroll + tiles);
float innerScroll = -time * BEAM_SCROLL_SPEED * 1.6F;
VertexConsumer innerBuffer = bufferSource.getBuffer(RenderType.entityTranslucent(inner));
VertexConsumer innerBuffer = bufferSource.getBuffer(RenderType.entityTranslucentEmissive(inner));
drawBeamPlane(innerBuffer, pose, start, finish, side1.scale(BEAM_INNER_RADIUS), innerScroll, innerScroll + tiles);
drawBeamPlane(innerBuffer, pose, start, finish, side2.scale(BEAM_INNER_RADIUS), innerScroll, innerScroll + tiles);
float impactSize = 1.1F + 0.35F * Mth.sin(time * 0.6F);
Vec3 camLeft = new Vec3(this.entityRenderDispatcher.camera.getLeftVector());
Vec3 camUp = new Vec3(this.entityRenderDispatcher.camera.getUpVector());
VertexConsumer endBuffer = bufferSource.getBuffer(RenderType.entityTranslucent(endTexture));
VertexConsumer endBuffer = bufferSource.getBuffer(RenderType.entityTranslucentEmissive(endTexture));
drawBillboard(endBuffer, pose, finish, camLeft.scale(impactSize), camUp.scale(impactSize));
}
@@ -6,8 +6,11 @@ import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import com.mojang.math.Axis;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.Mth;
import org.jetbrains.annotations.Nullable;
import software.bernie.geckolib.cache.object.BakedGeoModel;
import software.bernie.geckolib.renderer.GeoEntityRenderer;
@@ -17,13 +20,21 @@ public class RoyalElementalProjectileRenderer extends GeoEntityRenderer<RoyalEle
this.shadowRadius = 0.0F;
}
@Override
public RenderType getRenderType(RoyalElementalProjectileEntity animatable, ResourceLocation texture,
@Nullable MultiBufferSource bufferSource, float partialTick) {
return animatable.isIceball()
? RenderType.entityCutoutNoCull(texture)
: RenderType.entityTranslucentEmissive(texture);
}
@Override
public void preRender(PoseStack poseStack, RoyalElementalProjectileEntity animatable, BakedGeoModel model,
MultiBufferSource bufferSource, VertexConsumer buffer, boolean isReRender,
float partialTick, int packedLight, int packedOverlay, int colour) {
super.preRender(poseStack, animatable, model, bufferSource, buffer, isReRender, partialTick,
packedLight, packedOverlay, colour);
float scale = animatable.isIceball() ? 0.75F : 0.36F;
float scale = animatable.isIceball() ? 0.75F : 0.72F;
poseStack.scale(scale, scale, scale);
}
@@ -34,5 +45,8 @@ public class RoyalElementalProjectileRenderer extends GeoEntityRenderer<RoyalEle
float pitch = Mth.lerp(partialTick, entity.xRotO, entity.getXRot());
poseStack.mulPose(Axis.YP.rotationDegrees(180.0F - yaw));
poseStack.mulPose(Axis.XP.rotationDegrees(-pitch));
if (!entity.isIceball()) {
poseStack.mulPose(Axis.ZP.rotationDegrees(180.0F));
}
}
}
@@ -8,14 +8,31 @@ import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.Mth;
import net.minecraft.world.phys.Vec3;
import com.craisinlord.antarchy.content.entity.royal.PrinceEntity;
import com.craisinlord.antarchy.content.entity.royal.beam.RoyalBeamElement;
import org.jetbrains.annotations.Nullable;
import software.bernie.geckolib.cache.object.BakedGeoModel;
import software.bernie.geckolib.renderer.GeoEntityRenderer;
public class RoyalMountRenderer extends GeoEntityRenderer<RoyalMountEntity> {
private static final ResourceLocation QUEEN_OUTER = ResourceLocation.fromNamespaceAndPath("antarchy", "textures/entity/queen/queen_purple_beam_outer.png");
private static final ResourceLocation QUEEN_INNER = ResourceLocation.fromNamespaceAndPath("antarchy", "textures/entity/queen/queen_purple_beam_inner.png");
private static final ResourceLocation QUEEN_END = ResourceLocation.fromNamespaceAndPath("antarchy", "textures/entity/queen/queen_purple_beam_end_1.png");
private static final ResourceLocation KING_FIRE_OUTER = ResourceLocation.fromNamespaceAndPath("antarchy", "textures/entity/king/fire_beam_outer.png");
private static final ResourceLocation KING_FIRE_INNER = ResourceLocation.fromNamespaceAndPath("antarchy", "textures/entity/king/fire_beam_inner.png");
private static final ResourceLocation KING_FIRE_END = ResourceLocation.fromNamespaceAndPath("antarchy", "textures/entity/king/fire_beam_end_1.png");
private static final ResourceLocation KING_LIGHTNING_OUTER = ResourceLocation.fromNamespaceAndPath("antarchy", "textures/entity/king/lightning_beam_outer.png");
private static final ResourceLocation KING_LIGHTNING_INNER = ResourceLocation.fromNamespaceAndPath("antarchy", "textures/entity/king/lightning_beam_inner.png");
private static final ResourceLocation KING_LIGHTNING_END = ResourceLocation.fromNamespaceAndPath("antarchy", "textures/entity/king/lightning_beam_end_1.png");
private static final ResourceLocation KING_ICE_OUTER = ResourceLocation.fromNamespaceAndPath("antarchy", "textures/entity/king/ice_beam_outer.png");
private static final ResourceLocation KING_ICE_INNER = ResourceLocation.fromNamespaceAndPath("antarchy", "textures/entity/king/ice_beam_inner.png");
private static final ResourceLocation KING_ICE_END = ResourceLocation.fromNamespaceAndPath("antarchy", "textures/entity/king/ice_beam_end_1.png");
public RoyalMountRenderer(EntityRendererProvider.Context context) {
super(context, new RoyalMountModel());
this.shadowRadius = 1.1F;
this.addRenderLayer(new TemporalContractionGeoLayer<>(this));
}
@Override
@@ -27,8 +44,64 @@ public class RoyalMountRenderer extends GeoEntityRenderer<RoyalMountEntity> {
public void preRender(PoseStack poseStack, RoyalMountEntity animatable, BakedGeoModel model, @Nullable MultiBufferSource bufferSource,
@Nullable VertexConsumer buffer, boolean isReRender, float partialTick, int packedLight, int packedOverlay, int colour) {
super.preRender(poseStack, animatable, model, bufferSource, buffer, isReRender, partialTick, packedLight, packedOverlay, colour);
float scale = animatable.getAgeScale();
float scale = animatable.getGrowthScale();
poseStack.scale(scale, scale, scale);
this.shadowRadius = 1.1F * scale;
}
@Override
public void render(RoyalMountEntity entity, float entityYaw, float partialTick, PoseStack poseStack,
MultiBufferSource bufferSource, int packedLight) {
super.render(entity, entityYaw, partialTick, poseStack, bufferSource, packedLight);
Vec3 end = entity.getBeamEndPosition();
if (!entity.isFiringBeam() || end == null) return;
double x = Mth.lerp(partialTick, entity.xo, entity.getX());
double y = Mth.lerp(partialTick, entity.yo, entity.getY());
double z = Mth.lerp(partialTick, entity.zo, entity.getZ());
Vec3 start = entity.beamShootFrom().subtract(x, y, z);
Vec3 finish = end.subtract(x, y, z);
Vec3 axis = finish.subtract(start);
double length = axis.length();
if (length < 0.05D) return;
Vec3 dir = axis.scale(1.0D / length);
Vec3 side = dir.cross(new Vec3(0.0D, 1.0D, 0.0D));
if (side.lengthSqr() < 1.0E-4D) side = dir.cross(new Vec3(1.0D, 0.0D, 0.0D));
side = side.normalize();
ResourceLocation outer = QUEEN_OUTER;
ResourceLocation inner = QUEEN_INNER;
ResourceLocation endTexture = QUEEN_END;
if (entity instanceof PrinceEntity) {
switch (entity.getBeamElement()) {
case FIRE -> { outer = KING_FIRE_OUTER; inner = KING_FIRE_INNER; endTexture = KING_FIRE_END; }
case ICE -> { outer = KING_ICE_OUTER; inner = KING_ICE_INNER; endTexture = KING_ICE_END; }
default -> { outer = KING_LIGHTNING_OUTER; inner = KING_LIGHTNING_INNER; endTexture = KING_LIGHTNING_END; }
}
}
float time = entity.tickCount + partialTick;
float tiles = (float) (length / 3.0D);
org.joml.Matrix4f pose = poseStack.last().pose();
drawBeam(bufferSource.getBuffer(RenderType.entityTranslucentEmissive(outer)), pose, start, finish, side.scale(0.24D), -time * 0.32F, -time * 0.32F + tiles);
drawBeam(bufferSource.getBuffer(RenderType.entityTranslucentEmissive(inner)), pose, start, finish, side.scale(0.13D), -time * 0.5F, -time * 0.5F + tiles);
Vec3 up = new Vec3(this.entityRenderDispatcher.camera.getUpVector()).scale(0.55D);
Vec3 left = new Vec3(this.entityRenderDispatcher.camera.getLeftVector()).scale(0.55D);
drawQuad(bufferSource.getBuffer(RenderType.entityTranslucentEmissive(endTexture)), pose, finish.add(left).add(up), finish.subtract(left).add(up), finish.subtract(left).subtract(up), finish.add(left).subtract(up));
}
private static void drawBeam(VertexConsumer vertices, org.joml.Matrix4f pose, Vec3 start, Vec3 end, Vec3 half, float startV, float endV) {
drawQuad(vertices, pose, start.subtract(half), start.add(half), end.add(half), end.subtract(half), startV, endV);
drawQuad(vertices, pose, start.add(half), start.subtract(half), end.subtract(half), end.add(half), startV, endV);
}
private static void drawQuad(VertexConsumer vertices, org.joml.Matrix4f pose, Vec3 a, Vec3 b, Vec3 c, Vec3 d) {
drawQuad(vertices, pose, a, b, c, d, 0.0F, 1.0F);
}
private static void drawQuad(VertexConsumer vertices, org.joml.Matrix4f pose, Vec3 a, Vec3 b, Vec3 c, Vec3 d, float startV, float endV) {
vertex(vertices, pose, a, 0.0F, startV); vertex(vertices, pose, b, 1.0F, startV); vertex(vertices, pose, c, 1.0F, endV); vertex(vertices, pose, d, 0.0F, endV);
}
private static void vertex(VertexConsumer vertices, org.joml.Matrix4f pose, Vec3 pos, float u, float v) {
vertices.addVertex(pose, (float) pos.x, (float) pos.y, (float) pos.z).setColor(255, 255, 255, 255).setUv(u, v)
.setOverlay(net.minecraft.client.renderer.texture.OverlayTexture.NO_OVERLAY).setLight(0x00F000F0).setNormal(0.0F, 1.0F, 0.0F);
}
}
@@ -1,14 +1,11 @@
package com.craisinlord.antarchy.content.effect;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityDirection;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityTransition;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.effect.MobEffect;
import net.minecraft.world.effect.MobEffectCategory;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
public final class InvertedMobEffect extends MobEffect {
private static final AntarchyGravityTransition EFFECT_TRANSITION = new AntarchyGravityTransition(12);
@@ -22,19 +19,6 @@ public final class InvertedMobEffect extends MobEffect {
public boolean applyEffectTick(LivingEntity entity, int amplifier) {
if (!entity.level().isClientSide()) {
if (AntarchyGravityApi.getGravityDirection(entity) != AntarchyGravityDirection.UP || !AntarchyGravityApi.isGravityForced(entity)) {
MobEffectInstance effect = entity.getEffect(com.craisinlord.antarchy.content.AntarchyObjects.INVERTED_EFFECT.get());
if (entity instanceof Player || com.craisinlord.antarchy.content.worldgen.thoraxis.ThoraxisUndersideManager.isThoraxis(entity.level())) {
Antarchy.LOGGER.info(
"[antarchy-gravity] applying inverted gravity from effect targetType={} uuid={} pos=({}, {}, {}) dim={} duration={} ambient={} visible={} forcedBefore={} gameTime={}",
entity.getType(), entity.getUUID(), entity.getX(), entity.getY(), entity.getZ(),
entity.level().dimension().location(),
effect != null ? effect.getDuration() : -1,
effect != null && effect.isAmbient(),
effect != null && effect.isVisible(),
AntarchyGravityApi.isGravityForced(entity),
entity.level().getGameTime()
);
}
AntarchyGravityApi.setForcedGravityDirection(entity, AntarchyGravityDirection.UP, EFFECT_TRANSITION);
}
} else {
@@ -101,7 +101,7 @@ public class ManticoreEntity extends Monster implements GeoEntity {
return Mob.createMobAttributes()
.add(Attributes.MAX_HEALTH, AntarchySettings.manticoreHealth())
.add(Attributes.MOVEMENT_SPEED, 0.3D)
.add(Attributes.FLYING_SPEED, 0.6D)
.add(Attributes.FLYING_SPEED, 1.2D)
.add(Attributes.ATTACK_DAMAGE, AntarchySettings.manticoreAttackDamage())
.add(Attributes.FOLLOW_RANGE, 28.0D)
.add(Attributes.KNOCKBACK_RESISTANCE, 0.2D);
@@ -351,6 +351,16 @@ public class ScorpionEntity extends Monster implements GeoEntity {
ScorpionEntity.this.getNavigation().stop();
return;
}
// This goal overrides MeleeAttackGoal.tick, so the gravity mixin on the
// vanilla method cannot supply the inverted movement frame here.
LivingEntity target = ScorpionEntity.this.getTarget();
if (target != null && ThoraxisUndersideManager.shouldInvertInUnderside(ScorpionEntity.this)) {
ScorpionEntity.this.getNavigation().stop();
ScorpionEntity.this.getMoveControl().setWantedPosition(
target.getX(), target.getY(), target.getZ(), 1.15D);
this.checkAndPerformAttack(target);
return;
}
super.tick();
}
@@ -37,7 +37,12 @@ import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.MultifaceBlock;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.entity.EntityDimensions;
import net.minecraft.world.entity.Pose;
import net.minecraft.world.DifficultyInstance;
import net.minecraft.world.entity.SpawnGroupData;
import software.bernie.geckolib.animatable.GeoEntity;
import software.bernie.geckolib.animatable.instance.AnimatableInstanceCache;
import software.bernie.geckolib.animation.AnimatableManager;
@@ -48,6 +53,42 @@ import software.bernie.geckolib.animation.RawAnimation;
import software.bernie.geckolib.util.GeckoLibUtil;
public class WormEntity extends Monster implements GeoEntity {
public enum GrowthStage {
SMALL(0, EntityDimensions.scalable(0.55F, 0.8F), 8.0D, 0.29D, 2.0D, 20.0D, 0.05D, 2),
MEDIUM(1, EntityDimensions.scalable(0.9F, 1.5F), 20.0D, 0.25D, 4.0D, 24.0D, 0.2D, 5),
LARGE(2, EntityDimensions.scalable(1.35F, 2.75F), 36.0D, 0.22D, 7.0D, 28.0D, 0.45D, 8);
private final int id;
private final EntityDimensions dimensions;
private final double health;
private final double speed;
private final double damage;
private final double followRange;
private final double knockbackResistance;
private final int xp;
GrowthStage(int id, EntityDimensions dimensions, double health, double speed, double damage,
double followRange, double knockbackResistance, int xp) {
this.id = id;
this.dimensions = dimensions;
this.health = health;
this.speed = speed;
this.damage = damage;
this.followRange = followRange;
this.knockbackResistance = knockbackResistance;
this.xp = xp;
}
public static GrowthStage byId(int id) {
for (GrowthStage stage : values()) {
if (stage.id == id) return stage;
}
return LARGE;
}
}
private static final int SMALL_GROWTH_TICKS = 20 * 60 * 2;
private static final int MEDIUM_GROWTH_TICKS = 20 * 60 * 3;
private static final ResourceKey<Biome> WORMY_CAVES = ResourceKey.create(
Registries.BIOME,
ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "wormy_caves")
@@ -57,6 +98,7 @@ public class WormEntity extends Monster implements GeoEntity {
private static final int MUCUS_TRAIL_INTERVAL = 6;
private static final double MUCUS_TRAIL_MIN_DISTANCE_SQR = 0.36D;
private static final EntityDataAccessor<Integer> ANIMATION_STATE = SynchedEntityData.defineId(WormEntity.class, EntityDataSerializers.INT);
private static final EntityDataAccessor<Integer> GROWTH_STAGE = SynchedEntityData.defineId(WormEntity.class, EntityDataSerializers.INT);
private static final int ANIM_INGROUND = 0;
private static final int ANIM_CRAWL = 1;
private static final int ANIM_ATTACK = 2;
@@ -67,6 +109,7 @@ public class WormEntity extends Monster implements GeoEntity {
private int attackAnimationTicks;
private int mucusTrailCooldown;
private Vec3 lastMucusTrailPosition;
private int growthAge;
public WormEntity(EntityType<? extends WormEntity> entityType, Level level) {
super(entityType, level);
@@ -103,6 +146,67 @@ public class WormEntity extends Monster implements GeoEntity {
protected void defineSynchedData(SynchedEntityData.Builder builder) {
super.defineSynchedData(builder);
builder.define(ANIMATION_STATE, ANIM_INGROUND);
builder.define(GROWTH_STAGE, GrowthStage.LARGE.id);
}
public GrowthStage getGrowthStage() {
return GrowthStage.byId(this.entityData.get(GROWTH_STAGE));
}
public void setGrowthStage(GrowthStage stage) {
if (this.entityData.get(GROWTH_STAGE) == stage.id) return;
this.entityData.set(GROWTH_STAGE, stage.id);
this.refreshDimensions();
this.applyStageAttributes();
}
private void applyStageAttributes() {
GrowthStage stage = this.getGrowthStage();
if (this.getAttribute(Attributes.MAX_HEALTH) != null) this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(stage.health);
if (this.getAttribute(Attributes.MOVEMENT_SPEED) != null) this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(stage.speed);
if (this.getAttribute(Attributes.ATTACK_DAMAGE) != null) this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue(stage.damage);
if (this.getAttribute(Attributes.FOLLOW_RANGE) != null) this.getAttribute(Attributes.FOLLOW_RANGE).setBaseValue(stage.followRange);
if (this.getAttribute(Attributes.KNOCKBACK_RESISTANCE) != null) this.getAttribute(Attributes.KNOCKBACK_RESISTANCE).setBaseValue(stage.knockbackResistance);
this.xpReward = stage.xp;
if (!this.level().isClientSide) this.setHealth(Math.min(this.getHealth(), (float) stage.health));
}
@Override
public EntityDimensions getDefaultDimensions(Pose pose) {
return this.getGrowthStage().dimensions;
}
@Override
public void onSyncedDataUpdated(EntityDataAccessor<?> key) {
super.onSyncedDataUpdated(key);
if (GROWTH_STAGE.equals(key)) this.refreshDimensions();
}
@Override
public SpawnGroupData finalizeSpawn(ServerLevelAccessor level, DifficultyInstance difficulty, MobSpawnType spawnReason, SpawnGroupData spawnData) {
SpawnGroupData result = super.finalizeSpawn(level, difficulty, spawnReason, spawnData);
if (spawnReason == MobSpawnType.NATURAL || spawnReason == MobSpawnType.CHUNK_GENERATION || spawnReason == MobSpawnType.SPAWN_EGG) {
float roll = this.random.nextFloat();
this.setGrowthStage(roll < 0.4F ? GrowthStage.SMALL : roll < 0.6F ? GrowthStage.MEDIUM : GrowthStage.LARGE);
this.growthAge = 0;
}
return result;
}
@Override
public void addAdditionalSaveData(CompoundTag tag) {
super.addAdditionalSaveData(tag);
tag.putInt("GrowthStage", this.getGrowthStage().id);
tag.putInt("GrowthAge", this.growthAge);
}
@Override
public void readAdditionalSaveData(CompoundTag tag) {
super.readAdditionalSaveData(tag);
this.entityData.set(GROWTH_STAGE, tag.contains("GrowthStage") ? tag.getInt("GrowthStage") : GrowthStage.LARGE.id);
this.growthAge = tag.getInt("GrowthAge");
this.refreshDimensions();
this.applyStageAttributes();
}
@Override
@@ -153,6 +257,7 @@ public class WormEntity extends Monster implements GeoEntity {
if (this.level().isClientSide) {
return;
}
this.tickGrowth();
this.tickMucusTrail();
if (this.attackAnimationTicks > 0) {
this.attackAnimationTicks--;
@@ -160,7 +265,21 @@ public class WormEntity extends Monster implements GeoEntity {
this.updateAnimationState();
}
private void tickGrowth() {
GrowthStage stage = this.getGrowthStage();
if (stage == GrowthStage.LARGE) return;
this.growthAge++;
if (stage == GrowthStage.SMALL && this.growthAge >= SMALL_GROWTH_TICKS) {
this.growthAge = 0;
this.setGrowthStage(GrowthStage.MEDIUM);
} else if (stage == GrowthStage.MEDIUM && this.growthAge >= MEDIUM_GROWTH_TICKS) {
this.growthAge = 0;
this.setGrowthStage(GrowthStage.LARGE);
}
}
private void tickMucusTrail() {
if (this.getGrowthStage() == GrowthStage.SMALL) return;
if (this.mucusTrailCooldown > 0) {
this.mucusTrailCooldown--;
}
@@ -74,6 +74,7 @@ public class DimensionalTearEntity extends Entity implements GeoEntity {
private static final double PORTAL_RADIUS = 1.35D;
private static final double EXIT_OFFSET = 2.4D;
private static final Map<UUID, Long> TELEPORT_COOLDOWNS = new HashMap<>();
private static long lastTeleportCooldownCleanupTick = Long.MIN_VALUE;
private static final Map<UUID, Set<UUID>> QUEEN_TEAR_IDS = new HashMap<>();
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
@@ -149,6 +150,7 @@ public class DimensionalTearEntity extends Entity implements GeoEntity {
}
this.ageTicks++;
cleanupTeleportCooldowns(this.level().getGameTime());
if (this.ageTicks == 1) {
this.level().playSound(null, this.blockPosition(), AntarchySoundEvents.DIMENSIONAL_TEAR_OPEN.get(), SoundSource.HOSTILE, 1.0F, 1.0F);
}
@@ -370,7 +372,7 @@ public class DimensionalTearEntity extends Entity implements GeoEntity {
entity.hasImpulse = true;
if (entity instanceof LivingEntity living) {
int invertedDuration = AntarchySettings.dimensionalTearInvertedDurationTicks();
Antarchy.LOGGER.info(
Antarchy.LOGGER.debug(
"[antarchy-dimensional-tear] applying inverted effect targetType={} uuid={} fromTear={} toTear={} startPos=({}, {}, {}) exitPos=({}, {}, {}) dim={} durationTicks={}",
living.getType(), living.getUUID(), this.getUUID(), destination.getUUID(),
startPos.x, startPos.y, startPos.z,
@@ -382,6 +384,15 @@ public class DimensionalTearEntity extends Entity implements GeoEntity {
this.level().playSound(null, this.blockPosition(), SoundEvents.ENDERMAN_TELEPORT, SoundSource.NEUTRAL, 0.7F, 0.65F + this.random.nextFloat() * 0.2F);
}
private static void cleanupTeleportCooldowns(long gameTime) {
if (gameTime <= lastTeleportCooldownCleanupTick
|| gameTime - lastTeleportCooldownCleanupTick < TELEPORT_COOLDOWN_TICKS * 4L) {
return;
}
lastTeleportCooldownCleanupTick = gameTime;
TELEPORT_COOLDOWNS.entrySet().removeIf(entry -> entry.getValue() <= gameTime);
}
private Vec3 exitPosition() {
Vec3 facing = this.getViewVector(1.0F);
if (facing.lengthSqr() < 1.0E-4D) {
@@ -332,9 +332,12 @@ public class KingEntity extends RoyalBossEntity {
return;
}
this.tickRoyalPunishment();
if (this.iceBuildup.size() > 16) {
this.iceBuildup.clear();
}
long gameTime = this.level().getGameTime();
this.judgmentCooldowns.entrySet().removeIf(entry -> entry.getValue() <= gameTime);
this.lastPlayerDamageTime.entrySet().removeIf(entry -> gameTime - entry.getValue() > 600L);
ServerLevel serverLevel = (ServerLevel) this.level();
this.iceBuildup.entrySet().removeIf(entry -> serverLevel.getEntity(entry.getKey()) == null);
if (this.iceBuildup.size() > 16) this.iceBuildup.clear();
LivingEntity target = this.getTarget();
if (target != null && !this.isDeadOrDying() && this.level() instanceof ServerLevel level) {
this.trackBehavior(target);
@@ -346,6 +349,7 @@ public class KingEntity extends RoyalBossEntity {
if (this.isDeadOrDying() || !(this.level() instanceof ServerLevel)) {
return;
}
this.setRoyalFlying(true);
this.decreeRetreatPressure = false;
if (this.patrolCenter == null || this.position().distanceToSqr(this.patrolCenter) > 64.0D * 64.0D) {
this.patrolCenter = this.position();
@@ -397,7 +401,7 @@ public class KingEntity extends RoyalBossEntity {
away = away.normalize();
this.exileTarget.setDeltaMovement(away.x * 2.6D, 1.35D, away.z * 2.6D);
this.exileTarget.hasImpulse = true;
this.triggerAnim("body_action", "wing_gust");
this.triggerAnim("wing_action", "wing_gust");
this.playRoyalSound(AntarchySoundEvents.KING_WING_FLAP.get(), 0.82F);
}
}
@@ -555,6 +559,7 @@ public class KingEntity extends RoyalBossEntity {
this.activeDecree = decree;
this.activeDecreeTicks = 300;
this.decreeCooldownTicks = 0;
this.projectRoyalSound(AntarchySoundEvents.KING_DECREE.get(), 4.0F, 1.0F, target);
this.sendDecreeTitle(target);
return true;
}
@@ -672,7 +677,7 @@ public class KingEntity extends RoyalBossEntity {
&& this.beginRoyalAttack("wing_gust", RoyalAttackLane.BODY, WING_GUST_COOLDOWN_TICKS,
WING_GUST_WINDUP_TICKS, 1, 16, new com.craisinlord.antarchy.content.entity.royal.attack.RoyalAttackScheduler.Action() {
@Override public void onStart() {
KingEntity.this.triggerAnim("body_action", "wing_gust");
KingEntity.this.triggerAnim("wing_action", "wing_gust");
KingEntity.this.playRoyalSound(AntarchySoundEvents.KING_WING_FLAP.get(), 0.85F);
}
@Override public void onActive(int elapsedTicks) {
@@ -775,6 +780,8 @@ public class KingEntity extends RoyalBossEntity {
new com.craisinlord.antarchy.content.entity.royal.attack.RoyalAttackScheduler.Action() {
@Override public void onStart() {
KingEntity.this.triggerAnim(RoyalHead.Slot.LEFT.controllerName(), "shoot");
KingEntity.this.playRoyalSound(SoundEvents.FIRECHARGE_USE, 0.8F);
KingEntity.this.playRoyalSound(SoundEvents.GHAST_WARN, 1.2F);
}
@Override public void onActive(int elapsedTicks) {
Vec3 origin = KingEntity.this.headAnchor(KingEntity.this.royalHead(RoyalHead.Slot.LEFT));
@@ -891,6 +898,7 @@ public class KingEntity extends RoyalBossEntity {
this.decreeRetreatPressure = false;
this.playRoyalSound(AntarchySoundEvents.KING_DECREE_CAST.get(), 0.9F + this.random.nextFloat() * 0.15F);
this.playRoyalSound(AntarchySoundEvents.KING_ROAR.get(), 0.8F + this.random.nextFloat() * 0.12F);
this.projectRoyalSound(AntarchySoundEvents.KING_DECREE.get(), 4.0F, 1.0F, target);
if (target instanceof ServerPlayer player) this.sendDecreeTitle(player);
}
int countdown = this.activeDecree.countdownTicks(target);
@@ -904,6 +912,7 @@ public class KingEntity extends RoyalBossEntity {
return;
}
if (this.activeDecree != null && --this.activeDecreeTicks <= 0) {
this.projectRoyalSound(AntarchySoundEvents.KING_SUCCESS.get(), 4.0F, 1.0F, target);
this.endDecree(target);
}
}
@@ -955,12 +964,12 @@ public class KingEntity extends RoyalBossEntity {
this.endDecree(target);
}
/** Resolves a decree violation once, using the existing bounded judgment while the new judgment system is pending. */
public void failActiveDecree(@Nullable LivingEntity target) {
if (this.activeDecree == null || target == null || !target.isAlive()) {
return;
}
if (target instanceof ServerPlayer player) {
this.projectRoyalSound(AntarchySoundEvents.KING_JUDGEMENT.get(), 4.0F, 1.0F, target);
this.debugTriggerPunishment(RoyalPunishmentType.ROYAL_EXILE, player);
} else {
this.invokeJudgment(target);
@@ -982,6 +991,7 @@ public class KingEntity extends RoyalBossEntity {
long now = this.level().getGameTime();
if (this.judgmentCooldowns.getOrDefault(target.getUUID(), 0L) <= now) {
this.judgmentCooldowns.put(target.getUUID(), now + 20L);
this.projectRoyalSound(AntarchySoundEvents.KING_JUDGEMENT.get(), 4.0F, 1.0F, target);
target.hurt(this.damageSources().magic(), 6.0F);
}
}
@@ -31,6 +31,15 @@ public class PrinceEntity extends RoyalMountEntity {
};
}
@Override
public RoyalBeamElement getBeamElement() {
return switch (this.getBeamHead()) {
case 0 -> RoyalBeamElement.FIRE;
case 2 -> RoyalBeamElement.ICE;
default -> RoyalBeamElement.LIGHTNING;
};
}
@Override
protected String geoName() {
return "prince";
@@ -7,6 +7,7 @@ import com.craisinlord.antarchy.content.AntarchyTags;
import com.craisinlord.antarchy.content.entity.ManticoreEntity;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.UUID;
import net.minecraft.core.BlockPos;
@@ -21,23 +22,27 @@ import net.minecraft.world.entity.Entity;
import net.minecraft.world.BossEvent;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.ai.attributes.AttributeInstance;
import net.minecraft.world.entity.ai.attributes.AttributeModifier;
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal;
import net.minecraft.world.entity.MobSpawnType;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
import com.craisinlord.antarchy.content.entity.royal.beam.RoyalBeamSettings;
import com.craisinlord.antarchy.content.entity.royal.beam.RoyalBeamTerrainMode;
import com.craisinlord.antarchy.content.entity.royal.beam.RoyalBeamElement;
import com.craisinlord.antarchy.content.entity.royal.attack.RoyalAttackLane;
import com.craisinlord.antarchy.content.entity.royal.attack.RoyalEffectController;
import com.craisinlord.antarchy.content.entity.portal.DimensionalTearEntity;
import com.craisinlord.antarchy.content.time.TimeDilationApi;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.player.Player;
import org.jetbrains.annotations.Nullable;
public class QueenEntity extends RoyalBossEntity {
static final double TIME_FIELD_RADIUS_SCALE = 3.0D;
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;
@@ -47,7 +52,6 @@ public class QueenEntity extends RoyalBossEntity {
private static final DustParticleOptions ACCEL_DUST = new DustParticleOptions(new org.joml.Vector3f(1.0F, 0.15F, 0.15F), 2.0F);
private static final int GRAVITY_STOMP_COOLDOWN = 180;
private static final int TIME_FIELD_COOLDOWN = 260;
private static final int MOMENTUM_LOCK_COOLDOWN = 420;
private static final int MOMENTUM_LOCK_DURATION = 90;
private static final double MOMENTUM_LOCK_RADIUS = 26.0D;
@@ -61,13 +65,12 @@ public class QueenEntity extends RoyalBossEntity {
private int manticoreSummonCooldownTicks;
private int gravityStompCooldownTicks;
private int timeFieldCooldownTicks;
private int momentumLockCooldownTicks;
private int crushingGravityCooldownTicks;
private int accelerationCooldownTicks;
private int blackHoleCooldownTicks;
private final int[] dreamFireballCooldownTicks = new int[3];
private int idleWanderCooldownTicks;
private int finalTimeControlTicks;
private final RoyalEffectController royalEffects = new RoyalEffectController();
private final Map<UUID, Vec3> frozenVelocities = new HashMap<>();
@@ -89,6 +92,52 @@ public class QueenEntity extends RoyalBossEntity {
return true;
}
@Override
protected double groundCombatBias() {
return 0.72D;
}
@Override
protected double biteApproachSpeed() {
return 1.55D;
}
protected void registerRoyalTargetGoals() {
this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, KingEntity.class, true));
this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true));
this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(
this, Mob.class, 10, true, false, this::canAttack));
}
@Override
protected int selectBeamVolleyLimit(Phase phase) {
return switch (phase) {
case ONE -> AntarchySettings.queenBeamPhaseOneCap();
case TWO -> AntarchySettings.queenBeamPhaseTwoCap();
case THREE -> this.random.nextDouble() < AntarchySettings.queenBeamPhaseThreeMaxVolleyChance()
? AntarchySettings.queenBeamPhaseThreeCap()
: Math.min(2, AntarchySettings.queenBeamPhaseThreeCap());
};
}
@Override
protected boolean shouldDistributeHeadTargets(LivingEntity primaryTarget) {
return !(primaryTarget instanceof KingEntity);
}
public boolean canAttack(LivingEntity target) {
return !target.getType().is(AntarchyTags.Entities.QUEEN_DOES_NOT_ATTACK)
&& super.canAttack(target);
}
public boolean canDamageWithRoyalAttack(LivingEntity target) {
return target != this
&& target.isAlive()
&& (!(target instanceof Player player) || !player.isCreative() && !player.isSpectator())
&& !target.getType().is(AntarchyTags.Entities.QUEEN_DOES_NOT_ATTACK)
&& !this.isAlliedTo(target);
}
@Override
protected SoundEvent royalFlyLoopSound() {
return AntarchySoundEvents.QUEEN_FLY_LOOP.get();
@@ -145,9 +194,20 @@ public class QueenEntity extends RoyalBossEntity {
return RoyalBeamTerrainMode.DESTROY;
}
@Override
protected RoyalBeamElement royalBeamElement(@Nullable RoyalHead head) {
if (head == null) {
return RoyalBeamElement.QUEEN_BLACK;
}
return switch (head.slot()) {
case LEFT -> RoyalBeamElement.QUEEN_PURPLE;
case CENTER -> RoyalBeamElement.QUEEN_BLACK;
case RIGHT -> RoyalBeamElement.QUEEN_RED;
};
}
public static boolean isFieldImmune(Entity entity) {
return entity instanceof RoyalBossEntity
|| entity.getType().is(AntarchyTags.Entities.TIME_DILATION_IMMUNE);
return entity.getType().is(AntarchyTags.Entities.QUEEN_DOES_NOT_ATTACK);
}
@Override
@@ -162,10 +222,49 @@ public class QueenEntity extends RoyalBossEntity {
}
this.tickManticoreSummon();
this.tickQueenAbilities();
this.tickFinalTimeControl();
this.tickDreamFireballs();
this.tickFinalAcceleration();
this.royalEffects.tick();
}
private void tickDreamFireballs() {
if (!(this.level() instanceof ServerLevel level)) return;
LivingEntity target = this.getTarget();
if (target == null || !target.isAlive() || !this.canAttack(target)) return;
for (RoyalHead head : new RoyalHead[] {this.royalHead(RoyalHead.Slot.LEFT), this.royalHead(RoyalHead.Slot.CENTER), this.royalHead(RoyalHead.Slot.RIGHT)}) {
int index = head.slot().ordinal();
if (this.dreamFireballCooldownTicks[index] > 0) {
this.dreamFireballCooldownTicks[index]--;
continue;
}
if (!head.readyToAttack() || this.distanceTo(target) < 12.0D || this.distanceTo(target) > 52.0D) continue;
int cooldown = this.cooldown(180);
RoyalHead.Slot slot = head.slot();
if (!this.beginRoyalAttack("dream_fireball_" + slot.name(), this.headLane(head), cooldown, 12, 1, 5,
new com.craisinlord.antarchy.content.entity.royal.attack.RoyalAttackScheduler.Action() {
@Override public void onStart() {
head.startShoot();
QueenEntity.this.triggerAnim(slot.controllerName(), "shoot");
QueenEntity.this.playRoyalSound(net.minecraft.sounds.SoundEvents.FIRECHARGE_USE, 0.75F);
QueenEntity.this.playRoyalSound(net.minecraft.sounds.SoundEvents.GHAST_WARN, 1.35F);
}
@Override public void onActive(int elapsedTicks) {
Vec3 origin = QueenEntity.this.headAnchor(head);
Vec3 direction = target.getEyePosition().subtract(origin).normalize();
RoyalElementalProjectileEntity projectile = RoyalElementalProjectileEntity.create(
level, QueenEntity.this, RoyalBeamElement.DREAM_FIRE, origin, direction);
level.addFreshEntity(projectile);
QueenEntity.this.playRoyalSound(AntarchySoundEvents.QUEEN_BEAM_SHOOT.get(), 0.9F);
}
@Override public void onComplete() {
head.stopShoot();
}
})) {
this.dreamFireballCooldownTicks[index] = cooldown;
}
}
}
private void tickIdleWander() {
if (this.idleWanderCooldownTicks-- > 0) {
return;
@@ -239,7 +338,6 @@ public class QueenEntity extends RoyalBossEntity {
private void tickQueenAbilities() {
if (this.gravityStompCooldownTicks > 0) this.gravityStompCooldownTicks--;
if (this.timeFieldCooldownTicks > 0) this.timeFieldCooldownTicks--;
if (this.momentumLockCooldownTicks > 0) this.momentumLockCooldownTicks--;
if (this.crushingGravityCooldownTicks > 0) this.crushingGravityCooldownTicks--;
if (this.accelerationCooldownTicks > 0) this.accelerationCooldownTicks--;
@@ -257,8 +355,8 @@ public class QueenEntity extends RoyalBossEntity {
boolean majorBusy = this.royalEffects.active("momentum_lock") || this.royalEffects.active("crushing_gravity");
boolean allowOverlap = phase != Phase.ONE;
String[] candidates = phase == Phase.ONE
? new String[] {"gravity_stomp", "time_field", "black_hole"}
: new String[] {"gravity_stomp", "time_field", "black_hole", "momentum_lock", "crushing_gravity", "acceleration"};
? new String[] {"gravity_stomp", "black_hole"}
: new String[] {"gravity_stomp", "black_hole", "momentum_lock", "crushing_gravity", "acceleration"};
String selected = this.attackScheduler.chooseWeighted(candidates, id -> {
if (!this.queenAttackReady(id, phase, majorBusy, allowOverlap)) return 0;
return switch (id) {
@@ -270,36 +368,39 @@ public class QueenEntity extends RoyalBossEntity {
if (selected == null) return;
int cooldown = switch (selected) {
case "gravity_stomp" -> GRAVITY_STOMP_COOLDOWN;
case "time_field" -> TIME_FIELD_COOLDOWN;
case "black_hole" -> BLACK_HOLE_COOLDOWN;
case "momentum_lock" -> MOMENTUM_LOCK_COOLDOWN;
case "crushing_gravity" -> CRUSHING_GRAVITY_COOLDOWN;
default -> ACCELERATION_COOLDOWN;
};
if (!this.beginRoyalAttack(selected, RoyalAttackLane.HAZARD, this.cooldown(cooldown),
RoyalAttackLane lane = "black_hole".equals(selected) ? RoyalAttackLane.CENTER_HEAD : RoyalAttackLane.HAZARD;
if (!this.beginRoyalAttack(selected, lane, this.cooldown(cooldown),
10, 1, 14, new com.craisinlord.antarchy.content.entity.royal.attack.RoyalAttackScheduler.Action() {
@Override public void onStart() {
if ("gravity_stomp".equals(selected)) {
QueenEntity.this.triggerAnim("body_action", "stomp");
} else {
QueenEntity.this.triggerAnim("body_action", "wing_gust");
} else if ("momentum_lock".equals(selected)) {
QueenEntity.this.triggerAnim("wing_action", "wing_gust");
} else if ("black_hole".equals(selected)) {
QueenEntity.this.royalHead(RoyalHead.Slot.CENTER).startShoot();
QueenEntity.this.triggerAnim(RoyalHead.Slot.CENTER.controllerName(), "shoot");
}
}
@Override public void onActive(int elapsedTicks) {
QueenEntity.this.performSelectedQueenAttack(selected, phase, cooldown, level, target);
QueenEntity.this.performSelectedQueenAttack(selected, cooldown, level, target);
}
@Override public void onComplete() {
if ("black_hole".equals(selected)) {
QueenEntity.this.royalHead(RoyalHead.Slot.CENTER).stopShoot();
}
}
})) return;
}
private void performSelectedQueenAttack(String selected, Phase phase, int cooldown,
private void performSelectedQueenAttack(String selected, int cooldown,
ServerLevel level, LivingEntity target) {
switch (selected) {
case "gravity_stomp" -> { this.gravityStompCooldownTicks = cooldown; this.performGravityStomp(level, target); }
case "time_field" -> {
this.timeFieldCooldownTicks = cooldown;
double fieldRate = phase == Phase.THREE ? 0.1D : 0.4D;
TimeDilationApi.createField(level, target.position(), 12.0D, fieldRate, 140 + phase.ordinal() * 40);
}
case "black_hole" -> { this.blackHoleCooldownTicks = cooldown; this.castBlackHole(level, target); }
case "momentum_lock" -> { this.momentumLockCooldownTicks = cooldown; this.startMomentumLock(level); }
case "crushing_gravity" -> {
@@ -316,8 +417,9 @@ public class QueenEntity extends RoyalBossEntity {
if (!allowOverlap && majorBusy) return false;
return switch (id) {
case "gravity_stomp" -> this.gravityStompCooldownTicks <= 0;
case "time_field" -> this.timeFieldCooldownTicks <= 0;
case "black_hole" -> this.blackHoleCooldownTicks <= 0;
case "black_hole" -> this.blackHoleCooldownTicks <= 0
&& this.royalHead(RoyalHead.Slot.CENTER).readyToAttack()
&& !this.attackScheduler.laneBusy(RoyalAttackLane.CENTER_HEAD);
case "momentum_lock" -> !this.royalEffects.active("momentum_lock") && this.momentumLockCooldownTicks <= 0;
case "crushing_gravity" -> phase != Phase.ONE && !this.royalEffects.active("crushing_gravity") && this.crushingGravityCooldownTicks <= 0;
case "acceleration" -> phase == Phase.THREE && !this.royalEffects.active("acceleration") && this.accelerationCooldownTicks <= 0;
@@ -325,21 +427,13 @@ public class QueenEntity extends RoyalBossEntity {
};
}
private void tickFinalTimeControl() {
if (this.phase() != Phase.THREE || !(this.level() instanceof ServerLevel level)) {
private void tickFinalAcceleration() {
if (this.phase() != Phase.THREE) {
return;
}
if (!this.royalEffects.active("acceleration") && this.accelerationCooldownTicks <= 0) {
this.startRoyalAcceleration();
}
if (this.finalTimeControlTicks-- <= 0) {
this.finalTimeControlTicks = AntarchySettings.queenFinalTimeFieldCooldownTicks();
LivingEntity target = this.getTarget();
if (target != null && target.isAlive()) {
TimeDilationApi.createField(level, target.position(), AntarchySettings.queenFinalTimeFieldRadius(),
AntarchySettings.queenFinalTimeFieldRate(), AntarchySettings.queenFinalTimeFieldDurationTicks());
}
}
}
private void performGravityStomp(ServerLevel level, LivingEntity target) {
@@ -347,26 +441,32 @@ public class QueenEntity extends RoyalBossEntity {
this.playRoyalSound(AntarchySoundEvents.QUEEN_ROAR.get(), 0.7F);
DamageSource source = this.damageSources().mobAttack(this);
for (LivingEntity living : level.getEntitiesOfClass(LivingEntity.class,
this.getBoundingBox().inflate(18.0D), entity -> entity instanceof Player && entity.isAlive())) {
this.getBoundingBox().inflate(18.0D), this::canDamageWithRoyalAttack)) {
double distance = Math.max(1.0D, living.distanceTo(this));
living.hurt(source, (float) (12.0D * Math.max(0.25D, 1.0D - distance / 24.0D)));
Vec3 push = living.position().subtract(this.position()).normalize().scale(1.3D);
living.setDeltaMovement(living.getDeltaMovement().add(push.x, 0.65D, push.z));
living.hasImpulse = true;
}
level.sendParticles(ParticleTypes.SONIC_BOOM, this.getX(), this.getY() + 1.0D, this.getZ(), 4, 2.0D, 0.5D, 2.0D, 0.0D);
level.sendParticles(ParticleTypes.EXPLOSION, this.getX(), this.getY() + 1.0D, this.getZ(),
18, 5.0D, 0.6D, 5.0D, 0.08D);
}
private void castBlackHole(ServerLevel level, LivingEntity target) {
double vertical = 2.0D + this.random.nextDouble() * 2.0D;
if (AntarchyGravityApi.isGravityInverted(target)) {
vertical = -vertical;
}
Vec3 anchor = target.position().add(
(this.random.nextDouble() - 0.5D) * 4.0D,
2.0D + this.random.nextDouble() * 2.0D,
vertical,
(this.random.nextDouble() - 0.5D) * 4.0D);
RoyalBlackHoleEntity hole = RoyalBlackHoleEntity.create(level, anchor, this.getUUID());
Vec3 mouth = this.beamAnchor(this.royalHead(RoyalHead.Slot.CENTER));
RoyalBlackHoleEntity hole = RoyalBlackHoleEntity.createProjectile(level, mouth, anchor, this.getUUID());
level.addFreshEntity(hole);
this.triggerAnim("body_action", "wing_gust");
this.playRoyalSound(AntarchySoundEvents.QUEEN_ROAR.get(), 1.0F);
level.sendParticles(ParticleTypes.REVERSE_PORTAL, anchor.x, anchor.y, anchor.z, 60, 1.0D, 1.0D, 1.0D, 0.4D);
level.sendParticles(ParticleTypes.REVERSE_PORTAL, mouth.x, mouth.y, mouth.z,
30, 0.6D, 0.6D, 0.6D, 0.25D);
}
private void startMomentumLock(ServerLevel level) {
@@ -390,7 +490,6 @@ public class QueenEntity extends RoyalBossEntity {
}
this.frozenVelocities.clear();
});
this.triggerAnim("body_action", "wing_gust");
this.playRoyalSound(AntarchySoundEvents.QUEEN_ROAR.get(), 0.5F);
}
@@ -398,9 +497,11 @@ public class QueenEntity extends RoyalBossEntity {
if (!(this.level() instanceof ServerLevel level)) {
return;
}
for (Map.Entry<UUID, Vec3> entry : this.frozenVelocities.entrySet()) {
for (Iterator<Map.Entry<UUID, Vec3>> iterator = this.frozenVelocities.entrySet().iterator(); iterator.hasNext();) {
Map.Entry<UUID, Vec3> entry = iterator.next();
Entity entity = level.getEntity(entry.getKey());
if (entity == null || !entity.isAlive()) {
iterator.remove();
continue;
}
entity.setDeltaMovement(Vec3.ZERO);
@@ -522,7 +623,6 @@ public class QueenEntity extends RoyalBossEntity {
super.addAdditionalSaveData(tag);
tag.putInt(SUMMON_COOLDOWN_KEY, this.manticoreSummonCooldownTicks);
tag.putInt("GravityStompCooldownTicks", this.gravityStompCooldownTicks);
tag.putInt("TimeFieldCooldownTicks", this.timeFieldCooldownTicks);
tag.putInt("AccelerationCooldownTicks", this.accelerationCooldownTicks);
tag.putInt("BlackHoleCooldownTicks", this.blackHoleCooldownTicks);
}
@@ -532,7 +632,6 @@ public class QueenEntity extends RoyalBossEntity {
super.readAdditionalSaveData(tag);
this.manticoreSummonCooldownTicks = Math.max(0, tag.getInt(SUMMON_COOLDOWN_KEY));
this.gravityStompCooldownTicks = Math.max(0, tag.getInt("GravityStompCooldownTicks"));
this.timeFieldCooldownTicks = Math.max(0, tag.getInt("TimeFieldCooldownTicks"));
this.momentumLockCooldownTicks = Math.max(0, tag.getInt("MomentumLockCooldownTicks"));
this.crushingGravityCooldownTicks = Math.max(0, tag.getInt("CrushingGravityCooldownTicks"));
this.accelerationCooldownTicks = Math.max(0, tag.getInt("AccelerationCooldownTicks"));
@@ -2,20 +2,27 @@ package com.craisinlord.antarchy.content.entity.royal;
import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.content.time.TimeDilationApi;
import com.craisinlord.antarchy.content.time.TimeDilationFieldEntity;
import com.craisinlord.antarchy.config.AntarchySettings;
import java.util.UUID;
import net.minecraft.core.BlockPos;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.Mth;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.ExperienceOrb;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.item.FallingBlockEntity;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.level.GameRules;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
@@ -33,21 +40,50 @@ public class RoyalBlackHoleEntity extends Entity implements GeoEntity {
SynchedEntityData.defineId(RoyalBlackHoleEntity.class, EntityDataSerializers.FLOAT);
private static final EntityDataAccessor<Boolean> COLLAPSING =
SynchedEntityData.defineId(RoyalBlackHoleEntity.class, EntityDataSerializers.BOOLEAN);
private static final EntityDataAccessor<Float> VISUAL_GROWTH =
SynchedEntityData.defineId(RoyalBlackHoleEntity.class, EntityDataSerializers.FLOAT);
private static final EntityDataAccessor<Float> EFFECT_RADIUS =
SynchedEntityData.defineId(RoyalBlackHoleEntity.class, EntityDataSerializers.FLOAT);
private static final String OWNER_KEY = "OwnerUuid";
private static final String AGE_KEY = "Age";
private static final String ACTIVE_KEY = "ActiveTicks";
private static final String ASSAILANT_KEY = "AssailantVariant";
private static final String TRAVEL_TICKS_KEY = "TravelTicks";
private static final String DESTINATION_X_KEY = "DestinationX";
private static final String DESTINATION_Y_KEY = "DestinationY";
private static final String DESTINATION_Z_KEY = "DestinationZ";
private static final String ANIMATION_AGE_KEY = "AnimationAge";
private static final String DILATION_FIELD_KEY = "DilationFieldUuid";
private static final String SUCKED_BLOCKS_KEY = "SuckedBlocks";
private static final double DEFAULT_RADIUS = 14.0D;
private static final int DEFAULT_ACTIVE_TICKS = 120;
private static final int COLLAPSE_TICKS = 12;
private static final int QUEEN_ANIMATION_TICKS = 403;
private static final int QUEEN_COLLAPSE_TICKS = 25;
private static final int QUEEN_ACTIVE_TICKS = QUEEN_ANIMATION_TICKS - QUEEN_COLLAPSE_TICKS;
private static final int QUEEN_PROJECTILE_TRAVEL_TICKS = 20;
private static final int BLOCK_SUCTION_INTERVAL = 5;
private static final int BLOCKS_PER_SUCTION = 3;
private static final int BLOCK_SUCTION_ATTEMPTS = 36;
private static final double MODEL_RENDER_SCALE = 1.5D;
private static final double EFFECT_GROWTH_START = 0.45D;
private static final double EFFECT_GROWTH_END = 1.6D;
private static final double EFFECT_GROWTH_FRACTION = 0.6D;
private static final double MODEL_BASELINE_Y = 24.0D;
private static final double CORE_CUBE_CENTER_Y = 146.0D;
private static final double CORE_START_OFFSET_Y = -100.0D;
private static final double CORE_END_OFFSET_Y = 505.0D;
private static final double CORE_RISE_END_SECONDS = 19.125D;
@Nullable
private UUID ownerId;
private int age;
private int activeTicks = DEFAULT_ACTIVE_TICKS;
private boolean assailantVariant;
private int activeTicks = QUEEN_ACTIVE_TICKS;
private int animationAge;
private int suckedBlocks;
private int travelTicksRemaining;
@Nullable
private UUID dilationFieldId;
@Nullable
private Vec3 destination;
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
public RoyalBlackHoleEntity(EntityType<? extends RoyalBlackHoleEntity> entityType, Level level) {
@@ -58,27 +94,37 @@ public class RoyalBlackHoleEntity extends Entity implements GeoEntity {
public static RoyalBlackHoleEntity create(ServerLevel level, Vec3 center, double radius, int activeTicks, @Nullable UUID ownerId) {
RoyalBlackHoleEntity hole = new RoyalBlackHoleEntity(AntarchyObjects.ROYAL_BLACK_HOLE.get(), level);
hole.setPos(center.x, center.y, center.z);
hole.setRadius(radius > 0.0D ? radius : AntarchySettings.queenBlackHoleRadius());
hole.activeTicks = activeTicks > 0 ? Math.max(20, activeTicks) : AntarchySettings.queenBlackHoleActiveTicks();
hole.ownerId = ownerId;
hole.configure(center, radius, activeTicks, ownerId);
return hole;
}
public static RoyalBlackHoleEntity create(ServerLevel level, Vec3 center, @Nullable UUID ownerId) {
return create(level, center, AntarchySettings.queenBlackHoleRadius(), AntarchySettings.queenBlackHoleActiveTicks(), ownerId);
return create(level, center, AntarchySettings.queenBlackHoleRadius(), QUEEN_ACTIVE_TICKS, ownerId);
}
public static RoyalBlackHoleEntity createAssailant(ServerLevel level, Vec3 center, @Nullable UUID ownerId) {
RoyalBlackHoleEntity hole = create(level, center, AntarchySettings.queenBlackHoleRadius(), AntarchySettings.queenBlackHoleActiveTicks(), ownerId);
hole.assailantVariant = true;
public static RoyalBlackHoleEntity createProjectile(ServerLevel level, Vec3 origin, Vec3 destination,
@Nullable UUID ownerId) {
RoyalBlackHoleEntity hole = create(level, origin, ownerId);
hole.activeTicks = QUEEN_ACTIVE_TICKS - QUEEN_PROJECTILE_TRAVEL_TICKS;
hole.destination = destination;
hole.travelTicksRemaining = QUEEN_PROJECTILE_TRAVEL_TICKS;
hole.entityData.set(VISUAL_GROWTH, 0.15F);
return hole;
}
protected final void configure(Vec3 center, double radius, int activeTicks, @Nullable UUID ownerId) {
this.setPos(center.x, center.y, center.z);
this.setRadius(radius > 0.0D ? radius : AntarchySettings.queenBlackHoleRadius());
this.activeTicks = activeTicks > 0 ? Math.max(20, activeTicks) : AntarchySettings.queenBlackHoleActiveTicks();
this.ownerId = ownerId;
}
@Override
protected void defineSynchedData(SynchedEntityData.Builder builder) {
builder.define(RADIUS, (float) DEFAULT_RADIUS);
builder.define(COLLAPSING, false);
builder.define(VISUAL_GROWTH, 1.0F);
builder.define(EFFECT_RADIUS, (float) (DEFAULT_RADIUS * EFFECT_GROWTH_START));
}
public void setRadius(double radius) {
@@ -89,28 +135,48 @@ public class RoyalBlackHoleEntity extends Entity implements GeoEntity {
return this.entityData.get(RADIUS);
}
public double effectRadius() {
return Math.max(1.0D, this.entityData.get(EFFECT_RADIUS));
}
private double computeEffectRadius() {
double progress = this.activeTicks <= 0
? 1.0D
: Mth.clamp(this.age / (this.activeTicks * EFFECT_GROWTH_FRACTION), 0.0D, 1.0D);
return this.radius() * Mth.lerp(progress, EFFECT_GROWTH_START, EFFECT_GROWTH_END);
}
public boolean isCollapsing() {
return this.entityData.get(COLLAPSING);
}
public float visualGrowth() {
return this.entityData.get(VISUAL_GROWTH);
}
@Override
public void tick() {
super.tick();
this.animationAge++;
if (this.level().isClientSide) {
this.spawnClientParticles();
return;
}
if (this.travelTicksRemaining > 0 && this.destination != null) {
this.tickProjectileTravel();
return;
}
this.age++;
if (!this.isCollapsing()) {
if (this.assailantVariant) {
if (this.risesWhileActive()) {
this.setPos(this.getX(), this.getY() + 0.035D, this.getZ());
}
this.entityData.set(EFFECT_RADIUS, (float) this.computeEffectRadius());
this.ensureAttachedDilationField();
this.applyPull();
if (this.age % 10 == 0 && this.level() instanceof ServerLevel level) {
TimeDilationApi.createField(level, this.position(), Math.max(2.0D, this.radius() * 0.55D), 0.18D, 14, this.ownerId, !this.assailantVariant);
if (this.age % 20 == 0 && level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
RoyalBlockDestruction.destroySphere(level, this, this.position(), Math.min(2.0D, this.radius() * 0.2D), 8, 30.0D, 0.0F);
}
if (this.pullsTerrainBlocks() && this.age % BLOCK_SUCTION_INTERVAL == 0
&& this.level() instanceof ServerLevel level) {
this.pullTerrainBlocks(level);
}
if (this.age >= this.activeTicks) {
this.entityData.set(COLLAPSING, true);
@@ -118,22 +184,105 @@ public class RoyalBlackHoleEntity extends Entity implements GeoEntity {
}
return;
}
if (this.age >= COLLAPSE_TICKS) {
if (this.age >= this.collapseDurationTicks()) {
this.collapse();
this.discardAttachedDilationField();
this.discard();
}
}
private void tickProjectileTravel() {
Vec3 remaining = this.destination.subtract(this.position());
double distance = remaining.length();
if (distance <= 0.5D || this.travelTicksRemaining <= 1) {
this.setPos(this.destination.x, this.destination.y, this.destination.z);
this.travelTicksRemaining = 0;
this.destination = null;
this.entityData.set(VISUAL_GROWTH, 1.0F);
return;
}
this.setPos(this.position().add(remaining.scale(1.0D / this.travelTicksRemaining)));
this.travelTicksRemaining--;
float progress = 1.0F - this.travelTicksRemaining / (float) QUEEN_PROJECTILE_TRAVEL_TICKS;
this.entityData.set(VISUAL_GROWTH, Mth.lerp(progress, 0.15F, 1.0F));
}
protected boolean risesWhileActive() {
return false;
}
protected double timeDilationRadius() {
return Math.max(2.0D, this.effectRadius() * 0.55D) * QueenEntity.TIME_FIELD_RADIUS_SCALE;
}
protected boolean hasVisibleTimeDilationField() {
return true;
}
protected int collapseDurationTicks() {
return QUEEN_COLLAPSE_TICKS;
}
protected boolean pullsTerrainBlocks() {
return true;
}
/** Returns the center of the animated black-hole cube in world coordinates. */
public Vec3 effectCenter() {
double animationSeconds = Math.min(CORE_RISE_END_SECONDS, this.animationAge / 20.0D);
double riseProgress = animationSeconds / CORE_RISE_END_SECONDS;
double animatedOffset = Mth.lerp(riseProgress, CORE_START_OFFSET_Y, CORE_END_OFFSET_Y);
double modelY = CORE_CUBE_CENTER_Y + animatedOffset;
double worldOffsetY = (modelY - MODEL_BASELINE_Y) / 16.0D * MODEL_RENDER_SCALE;
return this.position().add(0.0D, worldOffsetY, 0.0D);
}
private void ensureAttachedDilationField() {
if (!(this.level() instanceof ServerLevel level)) {
return;
}
TimeDilationFieldEntity field = this.dilationFieldId != null
&& level.getEntity(this.dilationFieldId) instanceof TimeDilationFieldEntity existing
? existing
: null;
if (field == null || !field.isAlive()) {
int remainingTicks = Math.max(1, this.activeTicks - this.age + this.collapseDurationTicks());
field = TimeDilationApi.createField(level, this.effectCenter(), this.timeDilationRadius(), 0.18D,
remainingTicks, this.ownerId, this.hasVisibleTimeDilationField());
field.attachTo(this);
this.dilationFieldId = field.getUUID();
} else {
Vec3 center = this.effectCenter();
field.setPos(center.x, center.y, center.z);
field.setFieldRadius(this.timeDilationRadius());
}
}
private void discardAttachedDilationField() {
if (this.dilationFieldId != null && this.level() instanceof ServerLevel level
&& level.getEntity(this.dilationFieldId) instanceof TimeDilationFieldEntity field) {
field.discard();
}
this.dilationFieldId = null;
}
private void applyPull() {
Vec3 center = this.position();
double radius = this.radius();
Vec3 center = this.effectCenter();
double radius = this.effectRadius();
for (Entity entity : this.level().getEntitiesOfClass(Entity.class, new AABB(center, center).inflate(radius))) {
if (entity == this || this.isImmune(entity)) {
if (entity == this || entity instanceof TimeDilationFieldEntity || this.isImmune(entity)) {
continue;
}
Vec3 toCenter = center.subtract(entity.position());
double distance = toCenter.length();
if (distance < 0.5D || distance > radius) {
if (distance < 0.75D) {
if (entity instanceof ItemEntity || entity instanceof ExperienceOrb || entity instanceof FallingBlockEntity) {
entity.discard();
}
continue;
}
if (distance > radius) {
continue;
}
double strength = AntarchySettings.queenBlackHolePullStrength() * (1.0D - distance / radius);
@@ -144,12 +293,59 @@ public class RoyalBlackHoleEntity extends Entity implements GeoEntity {
}
}
private void pullTerrainBlocks(ServerLevel level) {
int cap = AntarchySettings.queenBlackHoleBlockSuctionCap();
if (cap <= 0 || this.suckedBlocks >= cap
|| !level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
return;
}
Vec3 center = this.effectCenter();
Vec3 terrainOrigin = this.position();
double radius = this.effectRadius();
int pulledThisTick = 0;
for (int attempt = 0; attempt < BLOCK_SUCTION_ATTEMPTS && pulledThisTick < BLOCKS_PER_SUCTION
&& this.suckedBlocks < cap; attempt++) {
BlockPos pos = BlockPos.containing(
terrainOrigin.x + (this.random.nextDouble() * 2.0D - 1.0D) * radius,
terrainOrigin.y + (this.random.nextDouble() * 2.0D - 1.0D) * radius,
terrainOrigin.z + (this.random.nextDouble() * 2.0D - 1.0D) * radius);
if (pos.distToCenterSqr(terrainOrigin.x, terrainOrigin.y, terrainOrigin.z) > radius * radius
|| pos.getY() < level.getMinBuildHeight()
|| pos.getY() >= level.getMaxBuildHeight()
|| !level.getWorldBorder().isWithinBounds(pos)) {
continue;
}
BlockState state = level.getBlockState(pos);
if (state.isAir() || level.getBlockEntity(pos) != null
|| state.getDestroySpeed(level, pos) < 0.0F
|| state.getBlock().getExplosionResistance() > 30.0F
|| state.getCollisionShape(level, pos).isEmpty()) {
continue;
}
FallingBlockEntity falling = FallingBlockEntity.fall(level, pos, state);
if (falling == null) {
continue;
}
Vec3 towardCenter = center.subtract(falling.position());
if (towardCenter.lengthSqr() > 0.001D) {
falling.setDeltaMovement(towardCenter.normalize().scale(0.28D));
}
falling.hasImpulse = true;
falling.hurtMarked = true;
pulledThisTick++;
this.suckedBlocks++;
}
}
private void collapse() {
if (!(this.level() instanceof ServerLevel level)) {
return;
}
Vec3 center = this.position();
double radius = this.radius();
Vec3 center = this.effectCenter();
double radius = this.effectRadius();
DamageSource source = this.ownerEntity() != null
? this.damageSources().mobAttack(this.ownerEntity())
: this.damageSources().magic();
@@ -172,10 +368,14 @@ public class RoyalBlackHoleEntity extends Entity implements GeoEntity {
}
private boolean isImmune(Entity entity) {
if (entity instanceof RoyalBossEntity) {
if (this.ownerId != null && this.ownerId.equals(entity.getUUID())) {
return true;
}
if (this.ownerId != null && this.ownerId.equals(entity.getUUID())) {
LivingEntity owner = this.ownerEntity();
if (owner instanceof QueenEntity) {
return entity.getType().is(com.craisinlord.antarchy.content.AntarchyTags.Entities.QUEEN_DOES_NOT_ATTACK);
}
if (entity instanceof RoyalBossEntity) {
return true;
}
return entity.getType().is(com.craisinlord.antarchy.content.AntarchyTags.Entities.TIME_DILATION_IMMUNE);
@@ -190,10 +390,10 @@ public class RoyalBlackHoleEntity extends Entity implements GeoEntity {
}
private void spawnClientParticles() {
Vec3 center = this.position();
Vec3 center = this.effectCenter();
for (int i = 0; i < 6; i++) {
double angle = this.random.nextDouble() * Math.PI * 2.0D;
double r = this.radius() * (0.3D + this.random.nextDouble() * 0.7D);
double r = this.effectRadius() * this.visualGrowth() * (0.3D + this.random.nextDouble() * 0.7D);
double px = center.x + Math.cos(angle) * r;
double pz = center.z + Math.sin(angle) * r;
double py = center.y + (this.random.nextDouble() - 0.5D) * 3.0D;
@@ -216,9 +416,19 @@ public class RoyalBlackHoleEntity extends Entity implements GeoEntity {
protected void readAdditionalSaveData(CompoundTag tag) {
this.setRadius(tag.getDouble("Radius"));
this.age = tag.getInt(AGE_KEY);
this.activeTicks = tag.contains(ACTIVE_KEY) ? tag.getInt(ACTIVE_KEY) : DEFAULT_ACTIVE_TICKS;
this.activeTicks = tag.contains(ACTIVE_KEY) ? tag.getInt(ACTIVE_KEY) : QUEEN_ACTIVE_TICKS;
this.animationAge = tag.contains(ANIMATION_AGE_KEY) ? tag.getInt(ANIMATION_AGE_KEY) : this.age;
this.suckedBlocks = tag.getInt(SUCKED_BLOCKS_KEY);
this.entityData.set(EFFECT_RADIUS, (float) this.computeEffectRadius());
this.ownerId = tag.hasUUID(OWNER_KEY) ? tag.getUUID(OWNER_KEY) : null;
this.assailantVariant = tag.getBoolean(ASSAILANT_KEY);
this.dilationFieldId = tag.hasUUID(DILATION_FIELD_KEY) ? tag.getUUID(DILATION_FIELD_KEY) : null;
this.travelTicksRemaining = Math.max(0, tag.getInt(TRAVEL_TICKS_KEY));
if (this.travelTicksRemaining > 0 && tag.contains(DESTINATION_X_KEY)) {
this.destination = new Vec3(tag.getDouble(DESTINATION_X_KEY), tag.getDouble(DESTINATION_Y_KEY),
tag.getDouble(DESTINATION_Z_KEY));
float progress = 1.0F - this.travelTicksRemaining / (float) QUEEN_PROJECTILE_TRAVEL_TICKS;
this.entityData.set(VISUAL_GROWTH, Mth.lerp(progress, 0.15F, 1.0F));
}
}
@Override
@@ -226,10 +436,20 @@ public class RoyalBlackHoleEntity extends Entity implements GeoEntity {
tag.putDouble("Radius", this.radius());
tag.putInt(AGE_KEY, this.age);
tag.putInt(ACTIVE_KEY, this.activeTicks);
tag.putInt(ANIMATION_AGE_KEY, this.animationAge);
tag.putInt(SUCKED_BLOCKS_KEY, this.suckedBlocks);
if (this.ownerId != null) {
tag.putUUID(OWNER_KEY, this.ownerId);
}
tag.putBoolean(ASSAILANT_KEY, this.assailantVariant);
if (this.dilationFieldId != null) {
tag.putUUID(DILATION_FIELD_KEY, this.dilationFieldId);
}
tag.putInt(TRAVEL_TICKS_KEY, this.travelTicksRemaining);
if (this.destination != null) {
tag.putDouble(DESTINATION_X_KEY, this.destination.x);
tag.putDouble(DESTINATION_Y_KEY, this.destination.y);
tag.putDouble(DESTINATION_Z_KEY, this.destination.z);
}
}
@Override
@@ -238,7 +458,7 @@ public class RoyalBlackHoleEntity extends Entity implements GeoEntity {
}
private PlayState blackHoleController(AnimationState<RoyalBlackHoleEntity> state) {
return state.setAndContinue(RawAnimation.begin().thenLoop("animation"));
return state.setAndContinue(RawAnimation.begin().thenPlay("animation"));
}
@Override
@@ -78,7 +78,7 @@ public class RoyalBoltEntity extends AbstractHurtingProjectile {
case FIRE -> ParticleTypes.FLAME;
case LIGHTNING -> ParticleTypes.ELECTRIC_SPARK;
case ICE -> ParticleTypes.SNOWFLAKE;
case GENERIC -> ParticleTypes.WITCH;
default -> ParticleTypes.WITCH;
};
}
@@ -12,8 +12,10 @@ import com.craisinlord.antarchy.content.entity.royal.attack.RoyalAttackLane;
import com.craisinlord.antarchy.content.entity.royal.attack.RoyalAttackScheduler;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi;
import com.craisinlord.antarchy.content.worldgen.thoraxis.ThoraxisUndersideManager;
import java.util.Arrays;
import java.util.List;
import net.minecraft.network.chat.Component;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
@@ -34,12 +36,14 @@ import net.minecraft.world.item.ItemStack;
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.ai.control.FlyingMoveControl;
import net.minecraft.world.entity.ai.control.MoveControl;
import net.minecraft.world.entity.ai.goal.FloatGoal;
import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal;
import net.minecraft.world.entity.ai.goal.RandomLookAroundGoal;
import net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal;
import net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal;
import net.minecraft.world.entity.ai.navigation.FlyingPathNavigation;
import net.minecraft.world.entity.ai.navigation.GroundPathNavigation;
import net.minecraft.world.entity.ai.navigation.PathNavigation;
import net.minecraft.world.entity.monster.Monster;
import net.minecraft.world.entity.player.Player;
@@ -88,6 +92,7 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
private static final int BITE_DURATION_TICKS = 16;
private static final int BITE_HIT_TICK = 6;
private static final int HEAD_ATTACK_ANIMATION_TICKS = 25;
private static final int DEATH_TICKS = 60;
private static final double CRUSH_RADIUS = 3.5D;
private static final int CRUSH_MAX_BLOCKS = 24;
@@ -98,9 +103,6 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
private static final int OBSTRUCTION_CLEAR_MAX_BLOCKS = 24;
private static final double OBSTRUCTION_CLEAR_MAX_RESISTANCE = 60.0D;
private static final float OBSTRUCTION_CLEAR_DROP_CHANCE = 0.1F;
// Keep visual muzzle points separate from the centered multipart hitboxes.
// Geo mouth fronts are near z=-219, with side heads centered at x=+/-21. At 2x render scale,
// one model pixel is 1/8 block.
private static final double BEAM_MUZZLE_FORWARD = 27.375D;
private static final double BEAM_MUZZLE_CENTER_Y = 11.5D;
private static final double BEAM_MUZZLE_SIDE_Y = 10.25D;
@@ -110,22 +112,40 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
protected static final double FLYING_PREFERRED_HOVER = 6.0D;
protected static final double FLYING_MAX_HOVER_ABOVE_GROUND = 24.0D;
private static final EntityDataAccessor<Boolean> FIRING =
SynchedEntityData.defineId(RoyalBossEntity.class, EntityDataSerializers.BOOLEAN);
private static final EntityDataAccessor<Boolean> HAS_BEAM_END =
SynchedEntityData.defineId(RoyalBossEntity.class, EntityDataSerializers.BOOLEAN);
@SuppressWarnings("unchecked")
private static final EntityDataAccessor<Boolean>[] BEAM_ACTIVE = new EntityDataAccessor[] {
SynchedEntityData.defineId(RoyalBossEntity.class, EntityDataSerializers.BOOLEAN),
SynchedEntityData.defineId(RoyalBossEntity.class, EntityDataSerializers.BOOLEAN),
SynchedEntityData.defineId(RoyalBossEntity.class, EntityDataSerializers.BOOLEAN)
};
private static final EntityDataAccessor<Boolean> ACCELERATED =
SynchedEntityData.defineId(RoyalBossEntity.class, EntityDataSerializers.BOOLEAN);
private static final EntityDataAccessor<Integer> BEAM_ELEMENT =
SynchedEntityData.defineId(RoyalBossEntity.class, EntityDataSerializers.INT);
private static final EntityDataAccessor<Integer> BEAM_HEAD_SLOT =
SynchedEntityData.defineId(RoyalBossEntity.class, EntityDataSerializers.INT);
private static final EntityDataAccessor<Float> BEAM_END_X =
SynchedEntityData.defineId(RoyalBossEntity.class, EntityDataSerializers.FLOAT);
private static final EntityDataAccessor<Float> BEAM_END_Y =
SynchedEntityData.defineId(RoyalBossEntity.class, EntityDataSerializers.FLOAT);
private static final EntityDataAccessor<Float> BEAM_END_Z =
SynchedEntityData.defineId(RoyalBossEntity.class, EntityDataSerializers.FLOAT);
private static final EntityDataAccessor<Boolean> ROYAL_FLYING =
SynchedEntityData.defineId(RoyalBossEntity.class, EntityDataSerializers.BOOLEAN);
@SuppressWarnings("unchecked")
private static final EntityDataAccessor<Integer>[] BEAM_ELEMENT = new EntityDataAccessor[] {
SynchedEntityData.defineId(RoyalBossEntity.class, EntityDataSerializers.INT),
SynchedEntityData.defineId(RoyalBossEntity.class, EntityDataSerializers.INT),
SynchedEntityData.defineId(RoyalBossEntity.class, EntityDataSerializers.INT)
};
@SuppressWarnings("unchecked")
private static final EntityDataAccessor<Float>[] BEAM_END_X = new EntityDataAccessor[] {
SynchedEntityData.defineId(RoyalBossEntity.class, EntityDataSerializers.FLOAT),
SynchedEntityData.defineId(RoyalBossEntity.class, EntityDataSerializers.FLOAT),
SynchedEntityData.defineId(RoyalBossEntity.class, EntityDataSerializers.FLOAT)
};
@SuppressWarnings("unchecked")
private static final EntityDataAccessor<Float>[] BEAM_END_Y = new EntityDataAccessor[] {
SynchedEntityData.defineId(RoyalBossEntity.class, EntityDataSerializers.FLOAT),
SynchedEntityData.defineId(RoyalBossEntity.class, EntityDataSerializers.FLOAT),
SynchedEntityData.defineId(RoyalBossEntity.class, EntityDataSerializers.FLOAT)
};
@SuppressWarnings("unchecked")
private static final EntityDataAccessor<Float>[] BEAM_END_Z = new EntityDataAccessor[] {
SynchedEntityData.defineId(RoyalBossEntity.class, EntityDataSerializers.FLOAT),
SynchedEntityData.defineId(RoyalBossEntity.class, EntityDataSerializers.FLOAT),
SynchedEntityData.defineId(RoyalBossEntity.class, EntityDataSerializers.FLOAT)
};
private static final RawAnimation IDLE_ANIM = RawAnimation.begin().thenLoop("idle");
private static final RawAnimation WALK_ANIM = RawAnimation.begin().thenLoop("walk");
@@ -133,7 +153,7 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
private static final RawAnimation DEATH_ANIM = RawAnimation.begin().thenPlayAndHold("death");
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
protected final RoyalBeamController beamController = new RoyalBeamController(this);
protected final RoyalBeamController[] beamControllers = new RoyalBeamController[3];
protected final RoyalAttackScheduler attackScheduler = new RoyalAttackScheduler();
private final ServerBossEvent bossEvent;
private final RoyalHead[] heads = {
@@ -141,21 +161,38 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
new RoyalHead(RoyalHead.Slot.CENTER),
new RoyalHead(RoyalHead.Slot.RIGHT)
};
private int beamCooldownTicks;
private final int[] beamLoopSoundTicks = new int[3];
private final int[] assignedHeadCounts = new int[3];
private int beamVolleyLimit;
@Nullable
private RoyalHead activeBeamHead;
private int beamLoopSoundTicks;
private int beamHeadCursor;
private boolean beamStarted;
private Phase beamVolleyPhase;
private final MoveControl groundMoveControl;
private final FlyingMoveControl flyingMoveControl;
private final PathNavigation groundNavigation;
private final PathNavigation flyingNavigation;
private int locomotionDecisionTicks = 80;
private boolean landingForCombat;
@Nullable
private Entity[] multipartParts;
protected RoyalBossEntity(EntityType<? extends RoyalBossEntity> entityType, Level level) {
super(entityType, level);
for (int i = 0; i < this.beamControllers.length; i++) {
this.beamControllers[i] = new RoyalBeamController(this);
}
this.xpReward = 500;
this.groundMoveControl = new MoveControl(this);
this.flyingMoveControl = new FlyingMoveControl(this, 20, true);
this.groundNavigation = new GroundPathNavigation(this, level);
FlyingPathNavigation flyingNavigation = new FlyingPathNavigation(this, level);
flyingNavigation.setCanFloat(true);
this.flyingNavigation = flyingNavigation;
if (this.isFlyingBoss()) {
this.moveControl = new FlyingMoveControl(this, 20, true);
this.moveControl = this.flyingMoveControl;
this.navigation = this.flyingNavigation;
} else {
this.navigation = this.groundNavigation;
}
this.bossEvent = new com.craisinlord.antarchy.content.boss.EntityLinkedServerBossEvent(
this.getUUID(),
@@ -222,6 +259,14 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
protected abstract boolean isFlyingBoss();
protected double groundCombatBias() {
return 0.35D;
}
protected double biteApproachSpeed() {
return 1.3D;
}
public String geoNameForRender() {
return this.geoName();
}
@@ -258,21 +303,33 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
this.goalSelector.addGoal(0, new FloatGoal(this));
this.goalSelector.addGoal(7, new LookAtPlayerGoal(this, Player.class, 48.0F));
this.goalSelector.addGoal(8, new RandomLookAroundGoal(this));
this.registerRoyalTargetGoals();
}
protected void registerRoyalTargetGoals() {
this.targetSelector.addGoal(1, new HurtByTargetGoal(this));
this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true));
}
public boolean canDamageWithRoyalAttack(LivingEntity target) {
return target != this
&& !(target instanceof RoyalBossEntity)
&& target.getType() != this.getType()
&& !this.isAlliedTo(target);
}
@Override
protected void defineSynchedData(SynchedEntityData.Builder builder) {
super.defineSynchedData(builder);
builder.define(FIRING, false);
builder.define(HAS_BEAM_END, false);
builder.define(ACCELERATED, false);
builder.define(BEAM_ELEMENT, RoyalBeamElement.GENERIC.ordinal());
builder.define(BEAM_HEAD_SLOT, -1);
builder.define(BEAM_END_X, 0.0F);
builder.define(BEAM_END_Y, 0.0F);
builder.define(BEAM_END_Z, 0.0F);
builder.define(ROYAL_FLYING, true);
for (int i = 0; i < 3; i++) {
builder.define(BEAM_ACTIVE[i], false);
builder.define(BEAM_ELEMENT[i], RoyalBeamElement.GENERIC.ordinal());
builder.define(BEAM_END_X[i], 0.0F);
builder.define(BEAM_END_Y[i], 0.0F);
builder.define(BEAM_END_Z[i], 0.0F);
}
}
@Override
@@ -308,9 +365,27 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
this.playSound(sound, (float) AntarchySettings.royalBossSoundVolume(), pitch);
}
protected void projectRoyalSound(SoundEvent sound, float volume, float pitch, @Nullable LivingEntity focus) {
if (this.level().isClientSide) {
return;
}
java.util.Set<ServerPlayer> recipients = new java.util.LinkedHashSet<>(this.bossEvent.getPlayers());
if (focus instanceof ServerPlayer focusPlayer) {
recipients.add(focusPlayer);
}
for (ServerPlayer player : recipients) {
player.connection.send(new net.minecraft.network.protocol.game.ClientboundSoundPacket(
net.minecraft.core.Holder.direct(sound),
net.minecraft.sounds.SoundSource.HOSTILE,
player.getX(), player.getY(), player.getZ(),
volume, pitch,
this.random.nextLong()));
}
}
@Override
public void travel(Vec3 travelVector) {
if (this.isFlyingBoss() && this.isEffectiveAi()) {
if (this.isRoyalFlying() && this.isEffectiveAi()) {
this.moveRelative(this.getSpeed(), travelVector);
this.move(MoverType.SELF, this.getDeltaMovement());
this.setDeltaMovement(this.getDeltaMovement().scale(0.91D));
@@ -336,19 +411,24 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
OBSTRUCTION_CLEAR_DROP_CHANCE);
}
if (this.isFlyingBoss()) {
this.moveControl = this.isRoyalFlying() ? this.flyingMoveControl : this.groundMoveControl;
this.navigation = this.isRoyalFlying() ? this.flyingNavigation : this.groundNavigation;
this.setNoGravity(!AntarchyGravityApi.isGravityInverted(this));
if (!this.isRoyalFlying()) {
this.setNoGravity(false);
}
}
if (this.level().isClientSide) {
return;
}
if (this.isFlyingBoss() && !this.isDeadOrDying()) {
if (this.isRoyalFlying() && !this.isDeadOrDying() && !this.landingForCombat) {
this.tickFlyingAltitude();
}
this.bossEvent.setProgress(this.getHealth() / this.getMaxHealth());
this.attackScheduler.tick();
if (this.isFlyingBoss() && this.tickCount % 40 == 0 && this.royalFlyLoopSound() != null) {
if (this.isRoyalFlying() && this.tickCount % 40 == 0 && this.royalFlyLoopSound() != null) {
this.playRoyalSound(this.royalFlyLoopSound(), 0.92F + this.random.nextFloat() * 0.12F);
}
@@ -364,6 +444,7 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
return;
}
this.tickCombatLocomotionMode(primaryTarget);
this.getLookControl().setLookAt(primaryTarget, 30.0F, 30.0F);
this.assignHeadTargets(primaryTarget);
this.tickRoyalBeam(primaryTarget);
@@ -400,45 +481,49 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
}
private void tickRoyalBeam(LivingEntity primaryTarget) {
if (this.beamCooldownTicks > 0 || this.attackScheduler.laneBusy(this.beamLane())) {
this.beamCooldownTicks--;
}
if (this.activeBeamHead != null) {
LivingEntity beamTarget = this.activeBeamHead.target(this.level());
if (!this.activeBeamHead.shooting() || beamTarget == null || !beamTarget.isAlive()
|| this.distanceTo(beamTarget) < this.royalBeamMinimumRange()) {
this.stopRoyalBeam();
} else if (!this.beamStarted) {
return;
} else {
if (this.beamLoopSoundTicks-- <= 0) {
this.beamLoopSoundTicks = 20;
this.playRoyalSound(this.royalBeamLoopSound(), 1.0F);
}
this.beamController.tick(
this.beamAnchor(this.activeBeamHead),
beamTarget,
this.royalBeamSettings(),
this.royalBeamTerrainMode(this.activeBeamHead),
this::setRoyalBeamEndPosition);
Vec3 beamEnd = this.beamController.beamEndPosition();
if (beamEnd != null) {
this.tickRoyalBeamEffects(this.activeBeamHead, this.beamAnchor(this.activeBeamHead), beamEnd);
}
if (!this.beamController.isFiring()) {
this.stopRoyalBeam();
}
return;
Phase phase = this.phase();
int activeBeams = 0;
for (RoyalHead head : this.heads) {
if (head.beamActive()) {
activeBeams++;
}
}
if (this.beamCooldownTicks > 0) {
return;
if (activeBeams == 0 || this.beamVolleyPhase != phase) {
this.beamVolleyPhase = phase;
this.beamVolleyLimit = Math.max(1, this.selectBeamVolleyLimit(phase));
}
Phase phase = this.phase();
for (int offset = 0; offset < this.heads.length; offset++) {
RoyalHead head = this.heads[(this.beamHeadCursor + offset) % this.heads.length];
for (RoyalHead head : this.heads) {
int index = head.slot().ordinal();
RoyalBeamController controller = this.beamControllers[index];
if (controller.isFiring()) {
LivingEntity beamTarget = head.target(this.level());
if (!head.shooting() || beamTarget == null || !beamTarget.isAlive()
|| this.distanceTo(beamTarget) < this.royalBeamMinimumRange()) {
this.stopRoyalBeam(head);
continue;
}
if (this.beamLoopSoundTicks[index]-- <= 0) {
this.beamLoopSoundTicks[index] = 20;
this.playRoyalSound(this.royalBeamLoopSound(), 1.0F);
}
controller.tick(
this.beamAnchor(head),
beamTarget,
this.royalBeamSettings(),
this.royalBeamTerrainMode(head),
end -> this.setRoyalBeamEndPosition(head.slot(), end));
Vec3 beamEnd = controller.beamEndPosition();
if (beamEnd != null) {
this.tickRoyalBeamEffects(head, this.beamAnchor(head), beamEnd);
}
if (!controller.isFiring()) {
this.stopRoyalBeam(head);
}
continue;
}
if (activeBeams >= this.beamVolleyLimit) {
continue;
}
LivingEntity target = head.target(this.level());
if (!head.readyToAttack() || this.attackScheduler.laneBusy(this.headLane(head))
|| target == null || !this.canAttack(target)
@@ -454,62 +539,53 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
windup = Math.max(0, AntarchySettings.kingBeamWindupTicks());
}
LivingEntity committedTarget = target;
if (!this.attackScheduler.start("beam_" + head.slot().name(), this.beamLane(), cooldown,
if (!this.beginRoyalAttack("beam_" + head.slot().name(), this.headLane(head), cooldown,
windup, duration, 8, new RoyalAttackScheduler.Action() {
@Override
public void onStart() {
head.startShoot();
RoyalBossEntity.this.triggerAnim(head.slot().controllerName(), "shoot");
}
@Override
public void onActive(int elapsedTicks) {
if (elapsedTicks != 0 || RoyalBossEntity.this.beamStarted) {
if (elapsedTicks != 0 || RoyalBossEntity.this.beamControllers[index].isFiring()) {
return;
}
RoyalBossEntity.this.beamStarted = true;
RoyalBossEntity.this.beamController.start(
RoyalBossEntity.this.beamControllers[index].start(
RoyalBossEntity.this.beamAnchor(head), committedTarget.getEyePosition(), duration);
RoyalBossEntity.this.entityData.set(BEAM_ELEMENT, RoyalBossEntity.this.royalBeamElement(head).ordinal());
RoyalBossEntity.this.entityData.set(BEAM_HEAD_SLOT, head.slot().ordinal());
RoyalBossEntity.this.setFiringRoyalBeam(true);
RoyalBossEntity.this.triggerAnim(head.slot().controllerName(), "shoot");
RoyalBossEntity.this.beamLoopSoundTicks = 20;
RoyalBossEntity.this.entityData.set(BEAM_ELEMENT[index], RoyalBossEntity.this.royalBeamElement(head).ordinal());
RoyalBossEntity.this.entityData.set(BEAM_ACTIVE[index], true);
RoyalBossEntity.this.beamLoopSoundTicks[index] = 20;
RoyalBossEntity.this.playRoyalSound(RoyalBossEntity.this.royalBeamStartSound(head), 1.0F);
RoyalBossEntity.this.playRoyalSound(RoyalBossEntity.this.royalBeamShootSound(), 1.0F);
}
@Override
public void onComplete() {
if (RoyalBossEntity.this.activeBeamHead == head) {
RoyalBossEntity.this.stopRoyalBeam();
}
RoyalBossEntity.this.stopRoyalBeam(head);
}
})) {
continue;
}
head.setBeamActive(true);
this.activeBeamHead = head;
this.beamHeadCursor = (head.slot().partIndex()) % this.heads.length;
this.beamCooldownTicks = cooldown;
this.beamStarted = false;
return;
activeBeams++;
}
}
private void stopRoyalBeam() {
if (this.activeBeamHead != null) {
this.activeBeamHead.setBeamActive(false);
this.activeBeamHead.stopShoot();
}
this.activeBeamHead = null;
this.beamStarted = false;
this.attackScheduler.cancel(this.beamLane());
this.beamController.stop();
this.entityData.set(BEAM_ELEMENT, RoyalBeamElement.GENERIC.ordinal());
this.entityData.set(BEAM_HEAD_SLOT, -1);
protected int selectBeamVolleyLimit(Phase phase) {
return phase.maxConcurrentHeadAttacks();
}
private void stopRoyalBeam(RoyalHead head) {
int index = head.slot().ordinal();
head.setBeamActive(false);
head.stopShoot();
this.beamControllers[index].stop();
this.entityData.set(BEAM_ACTIVE[index], false);
this.entityData.set(BEAM_ELEMENT[index], RoyalBeamElement.GENERIC.ordinal());
this.playRoyalSound(this.royalBeamEndSound(), 1.0F);
this.setFiringRoyalBeam(false);
this.setRoyalBeamEndPosition(null);
this.setRoyalBeamEndPosition(head.slot(), null);
}
public Phase phase() {
@@ -524,6 +600,12 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
}
private void assignHeadTargets(LivingEntity fallback) {
if (!this.shouldDistributeHeadTargets(fallback)) {
for (RoyalHead head : this.heads) {
head.setTarget(fallback);
}
return;
}
List<Player> candidates = this.level().getEntitiesOfClass(
Player.class,
this.getBoundingBox().inflate(AntarchySettings.royalBossFollowRange()),
@@ -535,7 +617,7 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
return;
}
int[] assignedCounts = new int[candidates.size()];
Arrays.fill(this.assignedHeadCounts, 0);
for (RoyalHead head : this.heads) {
Vec3 anchor = this.headAnchor(head);
int bestIndex = 0;
@@ -543,47 +625,113 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
double bestDistance = Double.MAX_VALUE;
for (int i = 0; i < candidates.size(); i++) {
double distance = candidates.get(i).position().distanceToSqr(anchor);
if (assignedCounts[i] < bestCount || (assignedCounts[i] == bestCount && distance < bestDistance)) {
if (this.assignedHeadCounts[i] < bestCount || (this.assignedHeadCounts[i] == bestCount && distance < bestDistance)) {
bestIndex = i;
bestCount = assignedCounts[i];
bestCount = this.assignedHeadCounts[i];
bestDistance = distance;
}
}
assignedCounts[bestIndex]++;
this.assignedHeadCounts[bestIndex]++;
head.setTarget(candidates.get(bestIndex));
}
}
protected boolean shouldDistributeHeadTargets(LivingEntity primaryTarget) {
return true;
}
private void steerTowardTarget(LivingEntity target) {
double reach = this.getBbWidth() * 0.5D + this.biteReach() * 0.5D;
double dx = target.getX() - this.getX();
double dz = target.getZ() - this.getZ();
boolean closeHorizontally = (dx * dx + dz * dz) <= reach * reach;
double horizontalDistance = Math.sqrt(dx * dx + dz * dz);
Vec3 awayFromTarget = horizontalDistance > 1.0E-4D
? new Vec3(-dx / horizontalDistance, 0.0D, -dz / horizontalDistance)
: this.getViewVector(1.0F).multiply(-1.0D, 0.0D, -1.0D).normalize();
double standoff = Math.max(this.getBbWidth() * 0.55D,
BEAM_MUZZLE_FORWARD - this.biteReach() * 0.65D);
Vec3 wanted = target.position().add(awayFromTarget.scale(standoff));
boolean atStandoff = this.position().multiply(1.0D, 0.0D, 1.0D)
.distanceToSqr(wanted.multiply(1.0D, 0.0D, 1.0D)) <= 4.0D;
boolean anyHeadInBiteRange = Arrays.stream(this.heads)
.anyMatch(head -> this.headWithinBiteReach(head, target));
double approachSpeed = anyHeadInBiteRange ? 1.0D : this.biteApproachSpeed();
if (!this.isFlyingBoss()) {
if (closeHorizontally) {
if (!this.isRoyalFlying()) {
if (atStandoff) {
this.getNavigation().stop();
} else {
this.getMoveControl().setWantedPosition(target.getX(), target.getY(), target.getZ(), 1.0D);
this.getMoveControl().setWantedPosition(wanted.x, target.getY(), wanted.z, approachSpeed);
}
return;
}
boolean inverted = AntarchyGravityApi.isGravityInverted(this);
double groundBelowTarget = this.groundYBelow(target.getX(), target.getZ());
double wantedY = inverted
double groundBelowTarget = this.groundYBelow(wanted.x, wanted.z);
double wantedY = this.landingForCombat
? (inverted ? target.getY() + target.getBbHeight() - this.getBbHeight() : groundBelowTarget)
: inverted
? target.getY() - 3.0D
: Mth.clamp(target.getY() + 3.0D,
groundBelowTarget + FLYING_MIN_HOVER,
groundBelowTarget + FLYING_MAX_HOVER_ABOVE_GROUND);
boolean closeVertically = Math.abs(wantedY - this.getY()) <= 2.5D;
if (closeHorizontally && closeVertically) {
if (atStandoff && closeVertically) {
this.getNavigation().stop();
} else {
this.getMoveControl().setWantedPosition(target.getX(), wantedY, target.getZ(), 1.1D);
this.getMoveControl().setWantedPosition(wanted.x, wantedY, wanted.z, approachSpeed);
}
}
private void tickCombatLocomotionMode(LivingEntity target) {
if (!this.isFlyingBoss()) {
return;
}
boolean inverted = AntarchyGravityApi.isGravityInverted(this);
double groundY = this.groundYBelow(this.getX(), this.getZ());
if (this.landingForCombat) {
if (!target.onGround()) {
this.landingForCombat = false;
this.locomotionDecisionTicks = 60;
} else if (this.onGround() || !inverted && this.getY() <= groundY + 1.0D) {
this.landingForCombat = false;
this.setRoyalFlying(false);
this.locomotionDecisionTicks = 100 + this.random.nextInt(100);
}
return;
}
if (--this.locomotionDecisionTicks > 0) {
return;
}
if (this.isRoyalFlying()) {
this.locomotionDecisionTicks = 60 + this.random.nextInt(80);
if (target.onGround() && this.random.nextDouble() < this.groundCombatBias()) {
this.landingForCombat = true;
}
} else if (!target.onGround() || this.random.nextDouble() >= this.groundCombatBias()) {
this.setRoyalFlying(true);
this.setDeltaMovement(this.getDeltaMovement().add(0.0D, 0.35D, 0.0D));
this.locomotionDecisionTicks = 80 + this.random.nextInt(100);
} else {
this.locomotionDecisionTicks = 80 + this.random.nextInt(80);
}
}
public boolean isRoyalFlying() {
return this.isFlyingBoss() && this.entityData.get(ROYAL_FLYING);
}
protected final void setRoyalFlying(boolean flying) {
if (this.entityData.get(ROYAL_FLYING) == flying) {
return;
}
this.navigation.stop();
this.entityData.set(ROYAL_FLYING, flying);
this.moveControl = flying ? this.flyingMoveControl : this.groundMoveControl;
this.navigation = flying ? this.flyingNavigation : this.groundNavigation;
this.setNoGravity(flying && !AntarchyGravityApi.isGravityInverted(this));
}
protected double groundYBelow(double x, double z) {
return this.level().getHeight(net.minecraft.world.level.levelgen.Heightmap.Types.MOTION_BLOCKING_NO_LEAVES,
Mth.floor(x), Mth.floor(z));
@@ -619,14 +767,14 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
Vec3 forward = new Vec3(-Math.sin(yaw), 0.0D, Math.cos(yaw));
Vec3 center = new Vec3(
this.getX() + forward.x * (this.getBbWidth() * 0.45D),
this.getY() + (this.isFlyingBoss() ? 0.0D : 1.0D),
this.getY() + (this.isRoyalFlying() ? 0.0D : 1.0D),
this.getZ() + forward.z * (this.getBbWidth() * 0.45D));
RoyalBlockDestruction.destroySphere(serverLevel, this, center, CRUSH_RADIUS, CRUSH_MAX_BLOCKS, CRUSH_MAX_RESISTANCE, CRUSH_DROP_CHANCE);
}
private void startBite(RoyalHead head, Phase phase) {
int cooldown = Math.max(10, Mth.floor(AntarchySettings.royalBossBiteCooldownTicks() * phase.cooldownScale()));
if (!this.attackScheduler.start("bite_" + head.slot().name(), headLane(head), cooldown,
if (!this.beginRoyalAttack("bite_" + head.slot().name(), headLane(head), cooldown,
BITE_HIT_TICK, 1, BITE_DURATION_TICKS - BITE_HIT_TICK,
new RoyalAttackScheduler.Action() {
@Override
@@ -663,7 +811,12 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
protected boolean beginRoyalAttack(String id, RoyalAttackLane lane, int cooldownTicks, int windupTicks,
int activeTicks, int recoveryTicks, RoyalAttackScheduler.Action action) {
return this.attackScheduler.start(id, lane, cooldownTicks, windupTicks, activeTicks, recoveryTicks, action);
int protectedRecovery = recoveryTicks;
if (lane == RoyalAttackLane.LEFT_HEAD || lane == RoyalAttackLane.CENTER_HEAD || lane == RoyalAttackLane.RIGHT_HEAD) {
protectedRecovery = Math.max(recoveryTicks,
HEAD_ATTACK_ANIMATION_TICKS - Math.max(0, windupTicks) - Math.max(1, activeTicks));
}
return this.attackScheduler.start(id, lane, cooldownTicks, windupTicks, activeTicks, protectedRecovery, action);
}
private void applyBiteHit(RoyalHead head) {
@@ -676,7 +829,7 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
DamageSource damageSource = this.damageSources().mobAttack(this);
AABB box = new AABB(anchor, anchor).inflate(reach);
for (LivingEntity living : this.level().getEntitiesOfClass(LivingEntity.class, box, entity -> entity.isAlive() && entity != this)) {
if (living instanceof RoyalBossEntity || living.getType() == this.getType()) {
if (!this.canDamageWithRoyalAttack(living)) {
continue;
}
if (living.position().distanceToSqr(anchor) > reach * reach) {
@@ -753,12 +906,22 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
@Override
public void die(DamageSource damageSource) {
if (!this.level().isClientSide) {
this.stopTriggeredAnim("wing_action", null);
this.stopTriggeredAnim("head_left", null);
this.stopTriggeredAnim("head_center", null);
this.stopTriggeredAnim("head_right", null);
this.stopTriggeredAnim("body_action", null);
for (RoyalHead head : this.heads) {
head.reset();
}
this.beamController.stop();
for (RoyalBeamController controller : this.beamControllers) {
controller.stop();
}
this.setFiringRoyalBeam(false);
this.setRoyalBeamEndPosition(null);
for (RoyalHead head : this.heads) {
this.stopRoyalBeam(head);
}
this.setRoyalFlying(false);
this.getNavigation().stop();
this.setDeltaMovement(Vec3.ZERO);
}
@@ -791,6 +954,13 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
@Override
protected void tickDeath() {
this.deathTime++;
this.setNoGravity(false);
Vec3 movement = this.getDeltaMovement();
if (this.onGround()) {
this.setDeltaMovement(movement.x * 0.6D, 0.0D, movement.z * 0.6D);
} else {
this.setDeltaMovement(movement.x * 0.8D, Math.min(-0.12D, movement.y), movement.z * 0.8D);
}
if (this.deathTime == 20 && !this.level().isClientSide()) {
this.level().broadcastEntityEvent(this, (byte) 60);
}
@@ -835,71 +1005,85 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
}
public boolean isFiringRoyalBeam() {
return this.entityData.get(FIRING);
for (RoyalHead.Slot slot : RoyalHead.Slot.values()) {
if (this.isFiringRoyalBeam(slot)) return true;
}
return false;
}
public void setFiringRoyalBeam(boolean firing) {
this.entityData.set(FIRING, firing);
}
public RoyalBeamController beamController() {
return this.beamController;
return this.beamControllers[0];
}
public void setRoyalBeamEndPosition(@Nullable Vec3 position) {
this.entityData.set(HAS_BEAM_END, position != null);
this.setRoyalBeamEndPosition(RoyalHead.Slot.CENTER, position);
}
public void setRoyalBeamEndPosition(RoyalHead.Slot slot, @Nullable Vec3 position) {
int index = slot.ordinal();
this.entityData.set(BEAM_ACTIVE[index], position != null || this.beamControllers[index].isFiring());
if (position != null) {
this.entityData.set(BEAM_END_X, (float) position.x);
this.entityData.set(BEAM_END_Y, (float) position.y);
this.entityData.set(BEAM_END_Z, (float) position.z);
this.entityData.set(BEAM_END_X[index], (float) position.x);
this.entityData.set(BEAM_END_Y[index], (float) position.y);
this.entityData.set(BEAM_END_Z[index], (float) position.z);
}
}
@Nullable
public Vec3 getRoyalBeamEndPosition() {
if (!this.entityData.get(HAS_BEAM_END)) {
return null;
}
return new Vec3(this.entityData.get(BEAM_END_X), this.entityData.get(BEAM_END_Y), this.entityData.get(BEAM_END_Z));
return this.getRoyalBeamEndPosition(RoyalHead.Slot.CENTER);
}
public Vec3 getRoyalBeamShootFrom(float partialTicks) {
int slotIndex = this.activeBeamHead != null
? this.activeBeamHead.slot().ordinal()
: this.entityData.get(BEAM_HEAD_SLOT);
if (slotIndex >= 0 && slotIndex < RoyalHead.Slot.values().length) {
double x = Mth.lerp(partialTicks, this.xo, this.getX());
double y = Mth.lerp(partialTicks, this.yo, this.getY());
double z = Mth.lerp(partialTicks, this.zo, this.getZ());
float yaw = Mth.rotLerp(partialTicks, this.yRotO, this.getYRot());
return this.beamAnchor(RoyalHead.Slot.values()[slotIndex], x, y, z, yaw);
}
return this.getRoyalBeamShootFrom(RoyalHead.Slot.CENTER, partialTicks);
}
public Vec3 getRoyalBeamShootFrom(RoyalHead.Slot slot, float partialTicks) {
double x = Mth.lerp(partialTicks, this.xo, this.getX());
double y = Mth.lerp(partialTicks, this.yo, this.getY()) + this.getBbHeight() * 0.72D;
double y = Mth.lerp(partialTicks, this.yo, this.getY());
double z = Mth.lerp(partialTicks, this.zo, this.getZ());
return new Vec3(x, y, z);
float yaw = Mth.rotLerp(partialTicks, this.yRotO, this.getYRot());
return this.beamAnchor(slot, x, y, z, yaw);
}
public RoyalBeamElement getRoyalBeamElement() {
int ordinal = this.entityData.get(BEAM_ELEMENT);
return this.getRoyalBeamElement(RoyalHead.Slot.CENTER);
}
public RoyalBeamElement getRoyalBeamElement(RoyalHead.Slot slot) {
int ordinal = this.entityData.get(BEAM_ELEMENT[slot.ordinal()]);
return ordinal >= 0 && ordinal < RoyalBeamElement.values().length
? RoyalBeamElement.values()[ordinal]
: RoyalBeamElement.GENERIC;
}
public boolean isFiringRoyalBeam(RoyalHead.Slot slot) {
return this.entityData.get(BEAM_ACTIVE[slot.ordinal()]);
}
@Nullable
public Vec3 getRoyalBeamEndPosition(RoyalHead.Slot slot) {
int index = slot.ordinal();
if (!this.entityData.get(BEAM_ACTIVE[index])) return null;
return new Vec3(this.entityData.get(BEAM_END_X[index]), this.entityData.get(BEAM_END_Y[index]), this.entityData.get(BEAM_END_Z[index]));
}
@Nullable
public RoyalHead.Slot getRoyalBeamHeadSlot() {
int slotIndex = this.activeBeamHead != null
? this.activeBeamHead.slot().ordinal()
: this.entityData.get(BEAM_HEAD_SLOT);
return slotIndex >= 0 && slotIndex < RoyalHead.Slot.values().length
? RoyalHead.Slot.values()[slotIndex]
: null;
for (RoyalHead.Slot slot : RoyalHead.Slot.values()) {
if (this.isFiringRoyalBeam(slot)) return slot;
}
return null;
}
@Override
public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
controllers.add(new AnimationController<>(this, "locomotion", 4, this::locomotionPredicate));
controllers.add(new AnimationController<>(this, "wing_action", 0, RoyalBossEntity::headIdle)
.triggerableAnim("wing_gust", RawAnimation.begin().thenPlay("wing_gust")));
controllers.add(new AnimationController<>(this, "head_left", 0, RoyalBossEntity::headIdle)
.triggerableAnim("bite", RawAnimation.begin().thenPlay("bite_3"))
.triggerableAnim("shoot", RawAnimation.begin().thenPlay("shoot_3")));
@@ -911,7 +1095,6 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
.triggerableAnim("shoot", RawAnimation.begin().thenPlay("shoot_2")));
controllers.add(new AnimationController<>(this, "body_action", 0, this::bodyActionPredicate)
.triggerableAnim("stomp", RawAnimation.begin().thenPlay("stomp"))
.triggerableAnim("wing_gust", RawAnimation.begin().thenPlay("wing_gust"))
.triggerableAnim("minion_spawn", RawAnimation.begin().thenPlay("minion_spawn")));
}
@@ -919,7 +1102,7 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
if (this.isDeadOrDying()) {
return PlayState.STOP;
}
if (this.isFlyingBoss()) {
if (this.isRoyalFlying()) {
state.setAndContinue(FLY_ANIM);
} else if (state.isMoving()) {
state.setAndContinue(WALK_ANIM);
@@ -940,6 +1123,18 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
return PlayState.STOP;
}
@Override
public void addAdditionalSaveData(CompoundTag tag) {
super.addAdditionalSaveData(tag);
tag.putBoolean("RoyalFlying", this.isRoyalFlying());
}
@Override
public void readAdditionalSaveData(CompoundTag tag) {
super.readAdditionalSaveData(tag);
this.setRoyalFlying(!tag.contains("RoyalFlying") || tag.getBoolean("RoyalFlying"));
}
@Override
public AnimatableInstanceCache getAnimatableInstanceCache() {
return this.geoCache;
@@ -5,6 +5,7 @@ import com.craisinlord.antarchy.content.entity.royal.beam.RoyalBeamElement;
import com.craisinlord.antarchy.config.AntarchySettings;
import net.minecraft.core.BlockPos;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
@@ -32,12 +33,12 @@ import software.bernie.geckolib.animation.PlayState;
import software.bernie.geckolib.animation.RawAnimation;
import software.bernie.geckolib.util.GeckoLibUtil;
/** Shared travelling shell for the King's fireball and iceball. Impact rules stay element-specific. */
public class RoyalElementalProjectileEntity extends ThrowableProjectile implements GeoEntity {
private static final EntityDataAccessor<Integer> ATTACK_TYPE =
SynchedEntityData.defineId(RoyalElementalProjectileEntity.class, EntityDataSerializers.INT);
private static final int FIREBALL = 0;
private static final int ICEBALL = 1;
private static final int DREAM_FIREBALL = 2;
private static final int MAX_LIFETIME = 100;
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
@@ -52,7 +53,8 @@ public class RoyalElementalProjectileEntity extends ThrowableProjectile implemen
AntarchyObjects.ROYAL_ELEMENTAL_PROJECTILE.get(), level);
projectile.setOwner(owner);
projectile.setPos(position.x, position.y, position.z);
projectile.setAttackType(element == RoyalBeamElement.ICE ? ICEBALL : FIREBALL);
projectile.setAttackType(element == RoyalBeamElement.ICE ? ICEBALL
: element == RoyalBeamElement.DREAM_FIRE ? DREAM_FIREBALL : FIREBALL);
projectile.shoot(direction.x, direction.y, direction.z, element == RoyalBeamElement.ICE ? 0.8F : 1.05F, 0.0F);
return projectile;
}
@@ -66,6 +68,10 @@ public class RoyalElementalProjectileEntity extends ThrowableProjectile implemen
return this.entityData.get(ATTACK_TYPE) == ICEBALL;
}
public boolean isDreamFireball() {
return this.entityData.get(ATTACK_TYPE) == DREAM_FIREBALL;
}
private void setAttackType(int type) {
this.entityData.set(ATTACK_TYPE, type);
}
@@ -87,7 +93,7 @@ public class RoyalElementalProjectileEntity extends ThrowableProjectile implemen
}
super.tick();
if (this.level().isClientSide) {
this.level().addParticle(this.isIceball() ? ParticleTypes.SNOWFLAKE : ParticleTypes.FLAME,
this.level().addParticle(this.getTrailParticle(),
this.getX(), this.getY(), this.getZ(), 0.0D, 0.0D, 0.0D);
}
}
@@ -127,20 +133,43 @@ public class RoyalElementalProjectileEntity extends ThrowableProjectile implemen
private void impactFire(ServerLevel level, Vec3 center) {
DamageSource source = this.damageSources().mobProjectile(this, this.getOwner() instanceof LivingEntity living ? living : null);
for (LivingEntity living : level.getEntitiesOfClass(LivingEntity.class, new AABB(center, center).inflate(5.0D),
entity -> entity.isAlive() && entity != this.getOwner())) {
this::canDamageWithImpact)) {
double distance = Math.max(0.5D, living.position().distanceTo(center));
float falloff = (float) Math.max(0.25D, 1.0D - distance / 5.0D);
living.hurt(source, (float) (AntarchySettings.kingFireballDamage() * falloff));
living.setRemainingFireTicks(Math.max(living.getRemainingFireTicks(), 120));
}
RoyalBlockDestruction.destroySphere(level, this, center, AntarchySettings.kingFireballRadius(), AntarchySettings.kingElementalTerrainCap(), 80.0D, 0.15F);
igniteImpact(level, center, this.isDreamFireball());
level.sendParticles(ParticleTypes.EXPLOSION_EMITTER, center.x, center.y, center.z, 4, 1.5D, 1.5D, 1.5D, 0.0D);
}
private void igniteImpact(ServerLevel level, Vec3 center, boolean dream) {
double radius = AntarchySettings.kingFireballRadius();
int minX = (int) Math.floor(center.x - radius);
int minY = (int) Math.floor(center.y - radius);
int minZ = (int) Math.floor(center.z - radius);
int maxX = (int) Math.floor(center.x + radius);
int maxY = (int) Math.floor(center.y + radius);
int maxZ = (int) Math.floor(center.z + radius);
var fire = dream
? BuiltInRegistries.BLOCK.getOptional(net.minecraft.resources.ResourceLocation.fromNamespaceAndPath(
"antarchy", "dream_fire")).orElse(Blocks.FIRE)
: Blocks.FIRE;
for (BlockPos pos : BlockPos.betweenClosed(minX, minY, minZ, maxX, maxY, maxZ)) {
if (pos.distToCenterSqr(center.x, center.y, center.z) > radius * radius || !level.isEmptyBlock(pos)) {
continue;
}
BlockState state = fire.defaultBlockState();
if (state.canSurvive(level, pos)) {
level.setBlockAndUpdate(pos, state);
}
}
}
private void impactIce(ServerLevel level, Vec3 center) {
DamageSource source = this.damageSources().mobProjectile(this, this.getOwner() instanceof LivingEntity living ? living : null);
for (LivingEntity living : level.getEntitiesOfClass(LivingEntity.class, new AABB(center, center).inflate(5.0D),
entity -> entity.isAlive() && entity != this.getOwner())) {
this::canDamageWithImpact)) {
living.hurt(source, (float) AntarchySettings.kingIceballDamage());
living.setTicksFrozen(Math.min(living.getTicksRequiredToFreeze() + 80, living.getTicksFrozen() + 180));
living.addEffect(new net.minecraft.world.effect.MobEffectInstance(net.minecraft.world.effect.MobEffects.MOVEMENT_SLOWDOWN, 120, 3));
@@ -149,6 +178,14 @@ public class RoyalElementalProjectileEntity extends ThrowableProjectile implemen
level.sendParticles(ParticleTypes.SNOWFLAKE, center.x, center.y, center.z, 80, 4.0D, 2.0D, 4.0D, 0.08D);
}
private boolean canDamageWithImpact(LivingEntity target) {
Entity owner = this.getOwner();
if (!target.isAlive() || target == owner) {
return false;
}
return !(owner instanceof RoyalBossEntity royalBoss) || royalBoss.canDamageWithRoyalAttack(target);
}
private void coverInIce(ServerLevel level, Vec3 center, double radius, int cap) {
int changed = 0;
int minX = (int) Math.floor(center.x - radius);
@@ -168,7 +205,15 @@ public class RoyalElementalProjectileEntity extends ThrowableProjectile implemen
}
protected net.minecraft.core.particles.ParticleOptions getTrailParticle() {
return this.isIceball() ? ParticleTypes.SNOWFLAKE : ParticleTypes.FLAME;
if (this.isIceball()) {
return ParticleTypes.SNOWFLAKE;
}
if (this.isDreamFireball()
&& BuiltInRegistries.PARTICLE_TYPE.get(net.minecraft.resources.ResourceLocation.fromNamespaceAndPath(
"antarchy", "dream_fire_flame")) instanceof net.minecraft.core.particles.ParticleOptions dreamFlame) {
return dreamFlame;
}
return ParticleTypes.FLAME;
}
@Override
@@ -22,7 +22,6 @@ import software.bernie.geckolib.animation.RawAnimation;
import software.bernie.geckolib.util.GeckoLibUtil;
public class RoyalIceSpikeEntity extends Entity implements GeoEntity {
// The exported asset contains one coordinated animation for all four spike groups.
private static final RawAnimation SPIKE_ANIMATION = RawAnimation.begin().thenPlay("animation");
private static final String ANIMATION_CONTROLLER = "ice_spike";
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
@@ -4,6 +4,12 @@ import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.AntarchySoundEvents;
import com.craisinlord.antarchy.content.AntarchyTags;
import com.craisinlord.antarchy.content.entity.royal.beam.RoyalBeamElement;
import com.craisinlord.antarchy.content.entity.royal.beam.RoyalBeamController;
import com.craisinlord.antarchy.content.entity.royal.beam.RoyalBeamSettings;
import com.craisinlord.antarchy.content.entity.royal.beam.RoyalBeamTerrainMode;
import com.craisinlord.antarchy.content.effect.RoyalEffectEligibility;
import com.craisinlord.antarchy.content.effect.RoyalEffectHooks;
import com.craisinlord.antarchy.content.time.TimeDilationApi;
import java.util.UUID;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
@@ -20,10 +26,12 @@ import net.minecraft.world.InteractionResult;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.AgeableMob;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityDimensions;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.MoverType;
import net.minecraft.world.entity.Pose;
import net.minecraft.world.entity.TamableAnimal;
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
import net.minecraft.world.entity.ai.attributes.Attributes;
@@ -40,14 +48,18 @@ import net.minecraft.world.entity.ai.goal.target.OwnerHurtTargetGoal;
import net.minecraft.world.entity.ai.navigation.FlyingPathNavigation;
import net.minecraft.world.entity.ai.navigation.GroundPathNavigation;
import net.minecraft.world.entity.ai.navigation.PathNavigation;
import net.minecraft.world.entity.monster.Enemy;
import net.minecraft.world.entity.animal.FlyingAnimal;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.Level;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.pathfinder.PathType;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.core.particles.ParticleTypes;
import org.jetbrains.annotations.Nullable;
import software.bernie.geckolib.animatable.GeoEntity;
import software.bernie.geckolib.animatable.instance.AnimatableInstanceCache;
@@ -73,6 +85,20 @@ public abstract class RoyalMountEntity extends TamableAnimal implements GeoEntit
SynchedEntityData.defineId(RoyalMountEntity.class, EntityDataSerializers.BOOLEAN);
private static final EntityDataAccessor<Boolean> SADDLED =
SynchedEntityData.defineId(RoyalMountEntity.class, EntityDataSerializers.BOOLEAN);
private static final EntityDataAccessor<Boolean> BEAM_ACTIVE =
SynchedEntityData.defineId(RoyalMountEntity.class, EntityDataSerializers.BOOLEAN);
private static final EntityDataAccessor<Integer> BEAM_HEAD =
SynchedEntityData.defineId(RoyalMountEntity.class, EntityDataSerializers.INT);
private static final EntityDataAccessor<Boolean> HAS_BEAM_END =
SynchedEntityData.defineId(RoyalMountEntity.class, EntityDataSerializers.BOOLEAN);
private static final EntityDataAccessor<Float> BEAM_END_X =
SynchedEntityData.defineId(RoyalMountEntity.class, EntityDataSerializers.FLOAT);
private static final EntityDataAccessor<Float> BEAM_END_Y =
SynchedEntityData.defineId(RoyalMountEntity.class, EntityDataSerializers.FLOAT);
private static final EntityDataAccessor<Float> BEAM_END_Z =
SynchedEntityData.defineId(RoyalMountEntity.class, EntityDataSerializers.FLOAT);
private static final EntityDataAccessor<Float> GROWTH_PROGRESS =
SynchedEntityData.defineId(RoyalMountEntity.class, EntityDataSerializers.FLOAT);
private static final RawAnimation IDLE_ANIM = RawAnimation.begin().thenLoop("idle");
private static final RawAnimation QUIRK_ANIM = RawAnimation.begin().thenLoop("quirk");
@@ -85,7 +111,11 @@ public abstract class RoyalMountEntity extends TamableAnimal implements GeoEntit
private static final int ACTION_TICKS = 12;
private static final int BITE_HIT_TICK = 6;
private static final int GROW_TICKS = 24000;
private static final int BEAM_WINDUP_TICKS = 24;
private static final int BEAM_COOLDOWN_TICKS = 240;
private static final int BEAM_DURATION_TICKS = 12;
private static final int COMMAND_COOLDOWN_TICKS = 360;
private static final int TIME_FIELD_COOLDOWN_TICKS = 260;
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
private final MoveControl groundMoveControl;
@@ -100,6 +130,12 @@ public abstract class RoyalMountEntity extends TamableAnimal implements GeoEntit
private int flapSoundTimer;
private boolean riderAscendPressed;
private boolean riderDescendPressed;
private final RoyalBeamController beamController = new RoyalBeamController(this);
private int beamWindupTicks;
private int beamCooldownTicks;
private int beamHead;
private int commandCooldownTicks;
private int timeFieldCooldownTicks;
protected RoyalMountEntity(EntityType<? extends RoyalMountEntity> entityType, Level level) {
super(entityType, level);
@@ -167,7 +203,7 @@ public abstract class RoyalMountEntity extends TamableAnimal implements GeoEntit
this.goalSelector.addGoal(1, new SitWhenOrderedToGoal(this) {
@Override
public boolean canUse() {
return !RoyalMountEntity.this.isBaby() && super.canUse();
return super.canUse();
}
});
this.goalSelector.addGoal(2, new RoyalMountSpitGoal(this));
@@ -182,14 +218,14 @@ public abstract class RoyalMountEntity extends TamableAnimal implements GeoEntit
@Override
public boolean canUse() {
return !RoyalMountEntity.this.isBaby() && !RoyalMountEntity.this.isVehicle() && super.canUse();
return !RoyalMountEntity.this.isVehicle() && super.canUse();
}
});
this.goalSelector.addGoal(4, new RoyalMountFollowOwnerGoal(this));
this.goalSelector.addGoal(6, new WaterAvoidingRandomStrollGoal(this, 0.8D) {
@Override
public boolean canUse() {
return !RoyalMountEntity.this.isBaby() && !RoyalMountEntity.this.isVehicle()
return !RoyalMountEntity.this.isVehicle()
&& !RoyalMountEntity.this.isOrderedToSit() && super.canUse();
}
});
@@ -197,25 +233,25 @@ public abstract class RoyalMountEntity extends TamableAnimal implements GeoEntit
this.goalSelector.addGoal(8, new RandomLookAroundGoal(this) {
@Override
public boolean canUse() {
return !RoyalMountEntity.this.isBaby() && super.canUse();
return super.canUse();
}
});
this.targetSelector.addGoal(1, new OwnerHurtByTargetGoal(this) {
@Override
public boolean canUse() {
return !RoyalMountEntity.this.isBaby() && super.canUse();
return super.canUse();
}
});
this.targetSelector.addGoal(2, new OwnerHurtTargetGoal(this) {
@Override
public boolean canUse() {
return !RoyalMountEntity.this.isBaby() && super.canUse();
return super.canUse();
}
});
this.targetSelector.addGoal(3, new HurtByTargetGoal(this) {
@Override
public boolean canUse() {
return !RoyalMountEntity.this.isBaby() && super.canUse();
return super.canUse();
}
}.setAlertOthers());
}
@@ -226,6 +262,13 @@ public abstract class RoyalMountEntity extends TamableAnimal implements GeoEntit
builder.define(ANIM_STATE, ANIM_IDLE);
builder.define(FLYING, false);
builder.define(SADDLED, false);
builder.define(BEAM_ACTIVE, false);
builder.define(BEAM_HEAD, -1);
builder.define(HAS_BEAM_END, false);
builder.define(BEAM_END_X, 0.0F);
builder.define(BEAM_END_Y, 0.0F);
builder.define(BEAM_END_Z, 0.0F);
builder.define(GROWTH_PROGRESS, 0.0F);
}
@Override
@@ -233,6 +276,10 @@ public abstract class RoyalMountEntity extends TamableAnimal implements GeoEntit
super.addAdditionalSaveData(tag);
tag.putBoolean("Saddled", this.isSaddled());
tag.putBoolean("Flying", this.isFlying());
tag.putInt("BeamCooldown", this.beamCooldownTicks);
tag.putInt("CommandCooldown", this.commandCooldownTicks);
tag.putInt("TimeFieldCooldown", this.timeFieldCooldownTicks);
tag.putFloat("GrowthProgress", this.getGrowthProgress());
}
@Override
@@ -240,12 +287,30 @@ public abstract class RoyalMountEntity extends TamableAnimal implements GeoEntit
super.readAdditionalSaveData(tag);
this.setSaddled(tag.getBoolean("Saddled"));
this.setFlying(tag.getBoolean("Flying"));
this.actionTicks = 0;
this.actionHit = false;
this.spitCooldown = 0;
this.beamCooldownTicks = Math.max(0, tag.getInt("BeamCooldown"));
this.commandCooldownTicks = Math.max(0, tag.getInt("CommandCooldown"));
this.timeFieldCooldownTicks = Math.max(0, tag.getInt("TimeFieldCooldown"));
this.setGrowthProgress(tag.contains("GrowthProgress") ? tag.getFloat("GrowthProgress") : 0.0F);
}
public boolean isFlying() {
return this.entityData.get(FLYING);
}
@Override
public void onSyncedDataUpdated(EntityDataAccessor<?> key) {
super.onSyncedDataUpdated(key);
if (FLYING.equals(key) && this.groundMoveControl != null) {
boolean flying = this.entityData.get(FLYING);
this.moveControl = flying ? this.flyingMoveControl : this.groundMoveControl;
this.navigation = flying ? this.flyingNavigation : this.groundNavigation;
this.setNoGravity(flying);
}
}
public void setFlying(boolean flying) {
if (this.entityData.get(FLYING) != flying) {
this.entityData.set(FLYING, flying);
@@ -274,12 +339,25 @@ public abstract class RoyalMountEntity extends TamableAnimal implements GeoEntit
this.entityData.set(ANIM_STATE, state);
}
public float getAgeScale() {
if (!this.isBaby()) {
return 1.0F;
public float getGrowthScale() {
return 1.0F + 14.0F * this.getGrowthProgress();
}
public float getGrowthProgress() {
return this.entityData.get(GROWTH_PROGRESS);
}
public void setGrowthProgress(float progress) {
float clamped = Mth.clamp(progress, 0.0F, 1.0F);
if (this.getGrowthProgress() != clamped) {
this.entityData.set(GROWTH_PROGRESS, clamped);
this.refreshDimensions();
}
float progress = 1.0F - Mth.clamp(-this.getAge() / (float) GROW_TICKS, 0.0F, 1.0F);
return 0.35F + 0.65F * progress;
}
@Override
public EntityDimensions getDefaultDimensions(Pose pose) {
return super.getDefaultDimensions(pose).scale(this.getGrowthScale());
}
@Override
@@ -297,9 +375,7 @@ public abstract class RoyalMountEntity extends TamableAnimal implements GeoEntit
stack.shrink(1);
}
this.heal(8.0F);
if (this.isBaby()) {
this.ageUp((int) (-this.getAge() * 0.1F), true);
}
this.setGrowthProgress(this.getGrowthProgress() + (1.0F - this.getGrowthProgress()) * 0.1F);
if (!this.isTame() && this.random.nextInt(3) == 0) {
this.tame(player);
this.level().broadcastEntityEvent(this, (byte) 7);
@@ -337,7 +413,7 @@ public abstract class RoyalMountEntity extends TamableAnimal implements GeoEntit
return InteractionResult.sidedSuccess(this.level().isClientSide);
}
if (stack.isEmpty() && !this.isBaby() && !player.isPassenger()) {
if (stack.isEmpty() && !player.isPassenger()) {
if (!this.level().isClientSide) {
this.setOrderedToSit(false);
this.setInSittingPose(false);
@@ -365,10 +441,6 @@ public abstract class RoyalMountEntity extends TamableAnimal implements GeoEntit
public void tick() {
super.tick();
if (this.isBaby()) {
this.refreshDimensions();
}
if (this.level().isClientSide) {
return;
}
@@ -376,6 +448,7 @@ public abstract class RoyalMountEntity extends TamableAnimal implements GeoEntit
if (this.spitCooldown > 0) {
this.spitCooldown--;
}
this.tickRoyalAbilities();
if ((this.isFlying() || !this.onGround()) && --this.flapSoundTimer <= 0) {
this.flapSoundTimer = 24 + this.random.nextInt(12);
@@ -406,6 +479,110 @@ public abstract class RoyalMountEntity extends TamableAnimal implements GeoEntit
}
}
private void tickRoyalAbilities() {
if (this.beamCooldownTicks > 0) this.beamCooldownTicks--;
if (this.commandCooldownTicks > 0) this.commandCooldownTicks--;
if (this.timeFieldCooldownTicks > 0) this.timeFieldCooldownTicks--;
if (this.beamWindupTicks > 0) {
this.beamWindupTicks--;
if (this.beamWindupTicks == 0) {
this.beamController.start(this.beamShootFrom(), this.beamTargetPosition(), BEAM_DURATION_TICKS);
this.entityData.set(BEAM_ACTIVE, true);
}
}
if (this.beamController.isFiring()) {
this.beamController.tick(this.beamShootFrom(), this.getTarget(), this.beamSettings(), RoyalBeamTerrainMode.NONE, this::setBeamEndPosition);
if (!this.beamController.isFiring()) {
this.entityData.set(BEAM_ACTIVE, false);
this.entityData.set(BEAM_HEAD, -1);
this.beamCooldownTicks = BEAM_COOLDOWN_TICKS;
}
return;
}
if (this.isOrderedToSit()) return;
LivingEntity target = this.getTarget();
if (target == null && this.getControllingPassenger() instanceof Player) {
target = this.frontTarget();
if (target != null) this.setTarget(target);
}
if (target == null || !target.isAlive() || !this.hasLineOfSight(target)) return;
double targetDistance = this.distanceTo(target);
if (this.beamCooldownTicks <= 0 && this.beamWindupTicks <= 0 && targetDistance > 7.0D && targetDistance < 28.0D && this.tickCount % 20 == 0) {
this.beamHead = (this.beamHead + 1) % 3;
this.entityData.set(BEAM_HEAD, this.beamHead);
this.beamWindupTicks = BEAM_WINDUP_TICKS;
this.setAnimState(this.isFlying() ? ANIM_FLY_SHOOT : ANIM_SHOOT);
this.triggerAnim("main", this.isFlying() ? "fly_shoot" : "shoot");
this.playSound(this.shootSound(), 0.9F, 0.75F);
}
if (this instanceof PrinceEntity && this.commandCooldownTicks <= 0 && this.tickCount % 20 == 0) {
this.performPrinceCommand();
}
if (this instanceof PrincessEntity && this.timeFieldCooldownTicks <= 0 && this.tickCount % 20 == 0) {
this.performPrincessTimeField(target);
}
}
private void performPrinceCommand() {
if (!(this.level() instanceof ServerLevel level) || RoyalEffectHooks.commandedHolder() == null) return;
int applied = 0;
for (Mob mob : level.getEntitiesOfClass(Mob.class, this.getBoundingBox().inflate(16.0D), mob ->
mob != this && mob.isAlive() && mob instanceof Enemy && !mob.isAlliedTo(this)
&& !(mob instanceof RoyalBossEntity) && RoyalEffectEligibility.canApplyCommanded(mob))) {
if (applied >= 5) break;
if (mob.addEffect(new MobEffectInstance(RoyalEffectHooks.commandedHolder(), 300), this)) {
applied++;
level.sendParticles(ParticleTypes.HAPPY_VILLAGER, mob.getX(), mob.getY() + mob.getBbHeight(), mob.getZ(), 4, 0.2D, 0.2D, 0.2D, 0.02D);
}
}
if (applied > 0) {
this.commandCooldownTicks = COMMAND_COOLDOWN_TICKS;
this.playSound(this.shootSound(), 1.0F, 0.65F);
}
}
private void performPrincessTimeField(@Nullable LivingEntity target) {
if (!(this.level() instanceof ServerLevel level)) return;
Vec3 center = target != null && target.isAlive() ? target.position() : this.position();
TimeDilationApi.createField(level, center, 8.0D, 0.45D, 100, this.getUUID());
this.timeFieldCooldownTicks = TIME_FIELD_COOLDOWN_TICKS;
level.sendParticles(ParticleTypes.END_ROD, center.x, center.y + 0.5D, center.z, 28, 1.2D, 0.6D, 1.2D, 0.03D);
}
private RoyalBeamSettings beamSettings() {
float damage = this instanceof PrincessEntity ? 10.0F : 12.0F;
return new RoyalBeamSettings(28.0D, 0.08D, 4.0D, BEAM_DURATION_TICKS, 6, BEAM_COOLDOWN_TICKS,
0.35F, 0.45F, damage, 0.45F, 3, 0.0D, 0.0F, 0.0F, 0, 0.0F, 0.0F, 0.0F, false, true);
}
private Vec3 beamTargetPosition() {
LivingEntity target = this.getTarget();
return target != null ? target.getEyePosition() : this.getEyePosition().add(this.getLookAngle().scale(20.0D));
}
public Vec3 beamShootFrom() {
float scale = this.getGrowthScale();
Vec3 forward = Vec3.directionFromRotation(0.0F, this.getYRot());
Vec3 side = new Vec3(-forward.z, 0.0D, forward.x);
return this.position().add(0.0D, 1.0D * scale, 0.0D).add(forward.scale(1.05D * scale)).add(side.scale((this.getBeamHead() - 1) * 0.72D * scale));
}
public int getBeamHead() { return this.entityData.get(BEAM_HEAD); }
public boolean isFiringBeam() { return this.entityData.get(BEAM_ACTIVE); }
public Vec3 getBeamEndPosition() {
return this.entityData.get(HAS_BEAM_END) ? new Vec3(this.entityData.get(BEAM_END_X), this.entityData.get(BEAM_END_Y), this.entityData.get(BEAM_END_Z)) : null;
}
public RoyalBeamElement getBeamElement() { return RoyalBeamElement.GENERIC; }
public RoyalBeamController beamController() { return this.beamController; }
private void setBeamEndPosition(@Nullable Vec3 position) {
this.entityData.set(HAS_BEAM_END, position != null);
if (position != null) {
this.entityData.set(BEAM_END_X, (float) position.x);
this.entityData.set(BEAM_END_Y, (float) position.y);
this.entityData.set(BEAM_END_Z, (float) position.z);
}
}
private void tickFlightState() {
if (this.isVehicle()) {
return;
@@ -540,6 +717,11 @@ public abstract class RoyalMountEntity extends TamableAnimal implements GeoEntit
public void setRiderAscend(boolean pressed) {
this.riderAscendPressed = pressed;
if (pressed && !this.isFlying() && this.getControllingPassenger() != null) {
this.setFlying(true);
this.setDeltaMovement(this.getDeltaMovement().add(0.0D, 0.35D, 0.0D));
this.hasImpulse = true;
}
}
public void setRiderDescend(boolean pressed) {
@@ -566,8 +748,8 @@ public abstract class RoyalMountEntity extends TamableAnimal implements GeoEntit
if (!this.hasPassenger(passenger)) {
return;
}
double yOffset = (this.isFlying() ? 1.55D : 1.35D) * this.getAgeScale();
Vec3 back = this.getLookAngle().scale(-0.35D * this.getAgeScale());
double yOffset = (this.isFlying() ? 1.55D : 1.35D) * this.getGrowthScale() - 1.0D;
Vec3 back = this.getLookAngle().scale(-0.35D * this.getGrowthScale());
moveFunction.accept(passenger, this.getX() + back.x, this.getY() + yOffset, this.getZ() + back.z);
}
@@ -1,5 +1,6 @@
package com.craisinlord.antarchy.content.entity.royal;
import net.minecraft.core.BlockPos;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.goal.Goal;
import net.minecraft.world.phys.Vec3;
@@ -58,7 +59,7 @@ public class RoyalMountFollowOwnerGoal extends Goal {
double distanceSqr = this.mount.distanceToSqr(this.owner);
if (distanceSqr > TELEPORT_DISTANCE * TELEPORT_DISTANCE) {
this.mount.teleportTo(this.owner.getX(), this.owner.getY(), this.owner.getZ());
this.trySafeTeleport();
return;
}
@@ -81,4 +82,29 @@ public class RoyalMountFollowOwnerGoal extends Goal {
public boolean requiresUpdateEveryTick() {
return true;
}
private void trySafeTeleport() {
Vec3 ownerPosition = this.owner.position();
BlockPos ownerBlock = this.owner.blockPosition();
for (int radius = 0; radius <= 2; radius++) {
for (int dx = -radius; dx <= radius; dx++) {
for (int dz = -radius; dz <= radius; dz++) {
for (int dy = -1; dy <= 2; dy++) {
double x = ownerPosition.x + dx + 0.5D;
double y = ownerBlock.getY() + dy;
double z = ownerPosition.z + dz + 0.5D;
BlockPos floor = BlockPos.containing(x, y - 0.1D, z);
var destination = this.mount.getBoundingBox().move(x - this.mount.getX(), y - this.mount.getY(), z - this.mount.getZ());
if (!this.mount.level().noCollision(this.mount, destination)) {
continue;
}
if (this.mount.isFlying() || !this.mount.level().getBlockState(floor).getCollisionShape(this.mount.level(), floor).isEmpty()) {
this.mount.teleportTo(x, y, z);
return;
}
}
}
}
}
}
}
@@ -13,7 +13,7 @@ public class RoyalMountSpitGoal extends Goal {
@Override
public boolean canUse() {
if (this.mount.isBaby() || this.mount.isVehicle() || this.mount.isOrderedToSit()) {
if (this.mount.isVehicle() || this.mount.isOrderedToSit()) {
return false;
}
LivingEntity target = this.mount.getTarget();
@@ -8,7 +8,6 @@ import java.util.Map;
import java.util.function.ToIntFunction;
import net.minecraft.util.RandomSource;
/** Shared server-side arbitration for Royal attack lifecycles. */
public final class RoyalAttackScheduler {
public interface Action {
default void onStart() {}
@@ -3,7 +3,6 @@ package com.craisinlord.antarchy.content.entity.royal.attack;
import java.util.HashMap;
import java.util.Map;
/** Owns persistent per-tick effects started by a Royal attack. */
public final class RoyalEffectController {
private record Effect(Runnable tick, Runnable end, int duration, int age) {
private Effect advance() {
@@ -1,5 +1,6 @@
package com.craisinlord.antarchy.content.entity.royal.beam;
import com.craisinlord.antarchy.content.entity.royal.RoyalBossEntity;
import java.util.List;
import java.util.HashSet;
import java.util.Set;
@@ -34,6 +35,7 @@ public final class RoyalBeamController {
private int beamAgeTicks;
private int trackingDelayTicks;
private int terrainMutationsThisTick;
private final Set<Integer> damagedThisTick = new HashSet<>();
private static final int ICE_MUTATIONS_PER_TICK = 3;
private static final float ICE_IMPACT_RADIUS = 2.0F;
@@ -137,17 +139,17 @@ public final class RoyalBeamController {
Vec3 direction = beamEnd.subtract(shootFrom).normalize();
double distance = shootFrom.distanceTo(beamEnd);
DamageSource damageSource = this.owner.damageSources().mobAttack(this.owner);
Set<Integer> damagedThisTick = new HashSet<>();
this.damagedThisTick.clear();
boolean pathMutates = terrainMode == RoyalBeamTerrainMode.DESTROY;
for (double walked = settings.pathStep(); walked < Math.min(distance, settings.range()); walked += settings.pathStep()) {
Vec3 sample = shootFrom.add(direction.scale(walked));
hurtEntitiesAround(sample, settings.pathDamageRadius(), settings.damage(), settings.knockback(), damageSource, settings.requireLineOfSightForDamage(), damagedThisTick);
hurtEntitiesAround(sample, settings.pathDamageRadius(), settings.damage(), settings.knockback(), damageSource, settings.requireLineOfSightForDamage(), this.damagedThisTick);
if (pathMutates && shouldMutateTerrain(settings, walked)) {
mutateTerrainAround(sample, settings.pathTerrainRadius(), settings, terrainMode);
}
}
hurtEntitiesAround(beamEnd, settings.impactDamageRadius(), settings.damage(), settings.knockback(), damageSource, settings.requireLineOfSightForDamage(), damagedThisTick);
hurtEntitiesAround(beamEnd, settings.impactDamageRadius(), settings.damage(), settings.knockback(), damageSource, settings.requireLineOfSightForDamage(), this.damagedThisTick);
if (!shouldMutateTerrain(settings, distance)) {
return;
@@ -193,6 +195,7 @@ public final class RoyalBeamController {
if (living.is(this.owner)
|| this.owner.isAlliedTo(living)
|| living.getType() == this.owner.getType()
|| this.owner instanceof RoyalBossEntity royalBoss && !royalBoss.canDamageWithRoyalAttack(living)
|| living.distanceToSqr(center) > radius * radius
|| damagedThisTick.contains(living.getId())
|| requireLineOfSight && !canEntityBeHurtFrom(center, living)) {
@@ -4,5 +4,9 @@ public enum RoyalBeamElement {
GENERIC,
FIRE,
LIGHTNING,
ICE
ICE,
QUEEN_PURPLE,
QUEEN_RED,
QUEEN_BLACK,
DREAM_FIRE
}
@@ -7,7 +7,7 @@ public final class AdvanceDecree implements RoyalDecree {
public String translationKey() { return "decree.antarchy.advance"; }
public void apply(ServerLevel level, KingEntity king, LivingEntity target) { king.setDecreeRetreatPressure(true); }
@Override public Evaluation evaluate(ServerLevel level, KingEntity king, LivingEntity target) {
return target.distanceTo(king) > REQUIRED_RANGE ? Evaluation.VIOLATED : Evaluation.COMPLIANT;
return target.distanceToSqr(king) > REQUIRED_RANGE * REQUIRED_RANGE ? Evaluation.VIOLATED : Evaluation.COMPLIANT;
}
@Override
public int contextWeight(KingEntity king, LivingEntity target) {
@@ -6,7 +6,7 @@ public final class ComeNoCloserDecree implements RoyalDecree {
public String translationKey() { return "decree.antarchy.come_no_closer"; }
public void apply(ServerLevel level, KingEntity king, LivingEntity target) { }
@Override public Evaluation evaluate(ServerLevel level, KingEntity king, LivingEntity target) {
return target.distanceTo(king) <= 16.0D ? Evaluation.VIOLATED : Evaluation.COMPLIANT;
return target.distanceToSqr(king) <= 256.0D ? Evaluation.VIOLATED : Evaluation.COMPLIANT;
}
@Override
public int contextWeight(KingEntity king, LivingEntity target) {
@@ -7,7 +7,7 @@ public final class FightMeCowardDecree implements RoyalDecree {
public String name() { return "FIGHT ME, COWARD!"; }
public String translationKey() { return "decree.antarchy.fight_me_coward"; }
public void apply(ServerLevel level, KingEntity king, LivingEntity target) {
if (target.distanceTo(king) > 28.0D) {
if (target.distanceToSqr(king) > 784.0D) {
Vec3 pull = king.position().subtract(target.position()).normalize().scale(0.35D);
target.setDeltaMovement(target.getDeltaMovement().add(pull.x, 0.05D, pull.z));
target.hasImpulse = true;
@@ -15,6 +15,6 @@ public final class FightMeCowardDecree implements RoyalDecree {
}
@Override
public Evaluation evaluate(ServerLevel level, KingEntity king, LivingEntity target) {
return target.distanceTo(king) > 28.0D ? Evaluation.VIOLATED : Evaluation.COMPLIANT;
return target.distanceToSqr(king) > 784.0D ? Evaluation.VIOLATED : Evaluation.COMPLIANT;
}
}
@@ -7,7 +7,7 @@ public final class KeepYourDistanceDecree implements RoyalDecree {
public String translationKey() { return "decree.antarchy.keep_your_distance"; }
public void apply(ServerLevel level, KingEntity king, LivingEntity target) { }
@Override public Evaluation evaluate(ServerLevel level, KingEntity king, LivingEntity target) {
return target.distanceTo(king) <= INNER_RADIUS ? Evaluation.VIOLATED : Evaluation.COMPLIANT;
return target.distanceToSqr(king) <= INNER_RADIUS * INNER_RADIUS ? Evaluation.VIOLATED : Evaluation.COMPLIANT;
}
@Override
public int contextWeight(KingEntity king, LivingEntity target) {
@@ -6,7 +6,7 @@ public final class NoRetreatDecree implements RoyalDecree {
private static final double ALLOWED_RANGE = 26.0D;
public String translationKey() { return "decree.antarchy.no_retreat"; }
public void apply(ServerLevel level, KingEntity king, LivingEntity target) {
if (target.distanceTo(king) > ALLOWED_RANGE) {
if (target.distanceToSqr(king) > ALLOWED_RANGE * ALLOWED_RANGE) {
king.invokeJudgment(target);
}
}
@@ -2,15 +2,10 @@ package com.craisinlord.antarchy.content.item;
import com.craisinlord.antarchy.content.block.AntimetalScaffoldingBlock;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi;
import java.util.List;
import net.minecraft.ChatFormatting;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.network.chat.Component;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.ScaffoldingBlockItem;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
@@ -22,12 +17,6 @@ public class AntimetalScaffoldingItem extends ScaffoldingBlockItem {
super(block, properties);
}
// @Override
// public void appendHoverText(ItemStack stack, Item.TooltipContext context, List<Component> tooltipComponents, TooltipFlag tooltipFlag) {
// tooltipComponents.add(Component.translatable("tooltip.antarchy.antimetal_scaffolding").withStyle(ChatFormatting.DARK_AQUA));
// super.appendHoverText(stack, context, tooltipComponents, tooltipFlag);
// }
@Nullable
@Override
public BlockPlaceContext updatePlacementContext(BlockPlaceContext context) {
@@ -6,9 +6,6 @@ import com.craisinlord.antarchy.content.client.model.ResourceBackedGeoItemModel;
import com.craisinlord.antarchy.content.client.renderer.AnimatedHeldItemRenderer;
import com.craisinlord.antarchy.content.entity.vortex.WindVortexEntity;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import net.minecraft.ChatFormatting;
import net.minecraft.core.Direction;
@@ -46,8 +43,6 @@ public class EyeOfTheStormItem extends Item implements GeoItem {
private static final ResourceLocation ANIMATION_LOCATION = ResourceLocation.fromNamespaceAndPath("antarchy", "animations/eye_of_the_storm.animation.json");
private static final String IDLE_CONTROLLER = "idle_controller";
private static final String IDLE_ANIMATION = "spinning_idle";
private static final Map<UUID, Long> LAST_UPDRAFT_TICK = new ConcurrentHashMap<>();
private static final Map<UUID, Long> LAST_SURGE_TICK = new ConcurrentHashMap<>();
private static final float SURGE_PROJECTILE_SPEED = 0.65F;
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
@@ -67,12 +62,9 @@ public class EyeOfTheStormItem extends Item implements GeoItem {
return InteractionResultHolder.consume(stack);
}
long now = level.getGameTime();
Long last = LAST_UPDRAFT_TICK.get(player.getUUID());
if (last != null && now - last < AntarchySettings.eyeOfTheStormUpdraftCooldownTicks()) {
if (player.getCooldowns().isOnCooldown(this)) {
return InteractionResultHolder.fail(stack);
}
LAST_UPDRAFT_TICK.put(player.getUUID(), now);
ServerLevel serverLevel = (ServerLevel) level;
WindVortexEntity vortex = WindVortexEntity.create(
@@ -92,6 +84,7 @@ public class EyeOfTheStormItem extends Item implements GeoItem {
player.setDeltaMovement(player.getDeltaMovement().add(0.0D, AntarchySettings.eyeOfTheStormUpdraftLaunchStrength(), 0.0D));
player.hasImpulse = true;
player.resetFallDistance();
player.getCooldowns().addCooldown(this, AntarchySettings.eyeOfTheStormUpdraftCooldownTicks());
if (player instanceof ServerPlayer serverPlayer) {
serverPlayer.connection.send(new ClientboundSetEntityMotionPacket(serverPlayer));
}
@@ -109,15 +102,13 @@ public class EyeOfTheStormItem extends Item implements GeoItem {
return;
}
long now = level.getGameTime();
Long last = LAST_SURGE_TICK.get(player.getUUID());
if (last != null && now - last < AntarchySettings.eyeOfTheStormSurgeCooldownTicks()) {
if (player.getCooldowns().isOnCooldown(this)) {
return;
}
LAST_SURGE_TICK.put(player.getUUID(), now);
Vec3 look = player.getViewVector(1.0F);
launchStormVortex(level, player.getEyePosition().add(look), look, player);
player.getCooldowns().addCooldown(this, AntarchySettings.eyeOfTheStormSurgeCooldownTicks());
player.awardStat(Stats.ITEM_USED.get(this));
stack.hurtAndBreak(1, player, EquipmentSlot.MAINHAND);
@@ -1,13 +1,6 @@
package com.craisinlord.antarchy.content.item;
import java.util.List;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.level.block.Block;
public class MucusBlockItem extends BlockItem {
@@ -15,9 +8,4 @@ public class MucusBlockItem extends BlockItem {
super(block, properties);
}
@Override
public void appendHoverText(ItemStack stack, Item.TooltipContext context, List<Component> tooltipComponents, TooltipFlag tooltipFlag) {
// tooltipComponents.add(Component.translatable("tooltip.antarchy.mucus_slippery").withStyle(ChatFormatting.DARK_AQUA));
super.appendHoverText(stack, context, tooltipComponents, tooltipFlag);
}
}
@@ -1,6 +1,5 @@
package com.craisinlord.antarchy.content.item;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.content.item.royal.RoyalGearHelper;
@@ -143,18 +142,8 @@ public class RoyalAssailantArmorItem extends ArmorItem {
}
MobEffectInstance existing = player.getEffect(AntarchyObjects.INVERTED_EFFECT.get());
if (existing != null && existing.isInfiniteDuration()) {
Antarchy.LOGGER.info(
"[antarchy-gravity] royal chestplate toggling inversion off player={} uuid={} pos=({}, {}, {}) dim={} existingDuration={} gameTime={}",
player.getGameProfile().getName(), player.getUUID(), player.getX(), player.getY(), player.getZ(),
player.level().dimension().location(), existing.getDuration(), player.level().getGameTime()
);
player.removeEffect(AntarchyObjects.INVERTED_EFFECT.get());
} else {
Antarchy.LOGGER.info(
"[antarchy-gravity] royal chestplate toggling inversion on player={} uuid={} pos=({}, {}, {}) dim={} existingDuration={} gameTime={}",
player.getGameProfile().getName(), player.getUUID(), player.getX(), player.getY(), player.getZ(),
player.level().dimension().location(), existing != null ? existing.getDuration() : -1, player.level().getGameTime()
);
player.addEffect(new MobEffectInstance(AntarchyObjects.INVERTED_EFFECT.get(), -1, 0, false, false, true));
}
}
@@ -1,7 +1,7 @@
package com.craisinlord.antarchy.content.item;
import com.craisinlord.antarchy.content.effect.RoyalEffectHooks;
import com.craisinlord.antarchy.content.entity.royal.RoyalBlackHoleEntity;
import com.craisinlord.antarchy.content.entity.royal.RoyalAssailantBlackHoleEntity;
import com.craisinlord.antarchy.config.AntarchySettings;
import java.util.List;
import net.minecraft.ChatFormatting;
@@ -81,7 +81,7 @@ public class RoyalAssailantBattleAxeItem extends AxeItem {
return InteractionResultHolder.sidedSuccess(stack, true);
}
if (level instanceof ServerLevel serverLevel) {
RoyalBlackHoleEntity blackHole = RoyalBlackHoleEntity.createAssailant(
RoyalAssailantBlackHoleEntity blackHole = RoyalAssailantBlackHoleEntity.create(
serverLevel,
player.position().add(player.getLookAngle().normalize().scale(2.0D)).add(0.0D, 2.0D, 0.0D),
player.getUUID());
@@ -13,15 +13,6 @@ import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.phys.Vec3;
/**
* Server-authoritative swing physics for the worm hook: pulls the holding player taut against
* a rope anchored to a stuck {@link WormHookProjectileEntity}, wrapping the rope around block
* corners via {@link WormHookRope} so the swing radius shortens realistically when the player
* swings around an obstacle.
*
* A tether entry is created the moment the hook is thrown (so the rope can be rendered following
* the flying projectile) and only gains real swing physics once the hook sticks into a block.
*/
public final class WormHookTetherManager {
private static final double MIN_LEN = 2.0D;
private static final double REEL_SPEED = 0.12D;
@@ -51,14 +42,12 @@ public final class WormHookTetherManager {
return hook instanceof WormHookProjectileEntity wormHook ? wormHook : null;
}
/** Registers the hook the moment it's thrown, purely so the rope can be rendered following it. */
public static void launch(ServerPlayer player, WormHookProjectileEntity hook) {
clear(player);
TETHERS.put(player.getUUID(), new TetherState(hook.getId()));
WormHookTetherSync.send(player, hook.getId());
}
/** Called once the hook sticks into a block; turns the cosmetic tether into real swing physics. */
public static boolean attach(ServerPlayer player, WormHookProjectileEntity hook) {
TetherState state = TETHERS.get(player.getUUID());
if (state == null || state.hookEntityId != hook.getId()) {
@@ -108,7 +97,6 @@ public final class WormHookTetherManager {
}
if (state.rope == null) {
// Hook is still flying; nothing to simulate yet, just tracking it for the render sync.
return;
}
@@ -135,8 +123,6 @@ public final class WormHookTetherManager {
Vec3 towardPivot = toPlayer.scale(1.0D / dist);
// Sneaking actively winds the player in toward the anchor, not just shortening
// the max length (which alone has no effect until the rope goes taut).
if (reeling && dist > MIN_LEN) {
Vec3 pull = towardPivot.scale(-REEL_PULL_STRENGTH);
Vec3 reelMotion = player.getDeltaMovement().add(pull);
@@ -63,7 +63,7 @@ public final class PermanentPortalTeleporter {
return active.center();
}
Vec3 safe = findSafeArrivalPosition(entity, destination, preferredPos);
Vec3 safe = findSafeArrivalPosition(entity, destination, preferredPos, type);
if (safe != null) {
PermanentPortalShape nearbyActive = findActiveNearby(destination, BlockPos.containing(safe), type);
if (nearbyActive != null) {
@@ -77,7 +77,11 @@ public final class PermanentPortalTeleporter {
return createFallbackPortal(destination, BlockPos.containing(safe), type);
}
return createFallbackPortal(destination, preferredPos, type);
BlockPos fallbackPos = preferredPos;
if (type == PermanentPortalType.ELYTHIA && destination.hasChunkAt(preferredPos)) {
fallbackPos = destination.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, preferredPos);
}
return createFallbackPortal(destination, fallbackPos, type);
}
@Nullable
@@ -108,14 +112,16 @@ public final class PermanentPortalTeleporter {
}
@Nullable
private static Vec3 findSafeArrivalPosition(Entity entity, ServerLevel destination, BlockPos preferredPos) {
int[] yRange = getDimensionYRange(destination);
private static Vec3 findSafeArrivalPosition(Entity entity, ServerLevel destination, BlockPos preferredPos, PermanentPortalType type) {
int[] yRange = getDimensionYRange(destination, type);
if (yRange != null) {
return findSafeArrivalPositionInYRange(entity, destination, preferredPos, yRange[0], yRange[1]);
}
Set<BlockPos> candidates = new LinkedHashSet<>();
addCandidate(candidates, preferredPos);
if (type != PermanentPortalType.ELYTHIA) {
addCandidate(candidates, preferredPos);
}
for (int radius = 0; radius <= SEARCH_RADIUS; radius++) {
for (int xOff = -radius; xOff <= radius; xOff++) {
@@ -139,6 +145,10 @@ public final class PermanentPortalTeleporter {
}
}
if (type == PermanentPortalType.ELYTHIA) {
addCandidate(candidates, preferredPos);
}
for (BlockPos candidate : candidates) {
Vec3 safe = tryFindSafePosition(entity, destination, candidate);
if (safe != null) {
@@ -277,12 +287,12 @@ public final class PermanentPortalTeleporter {
}
@Nullable
private static int[] getDimensionYRange(ServerLevel destination) {
private static int[] getDimensionYRange(ServerLevel destination, PermanentPortalType type) {
ResourceKey<Level> dim = destination.dimension();
if (dim == AntarchySettings.termiteDestinationDimension()) {
return new int[]{100, 200};
}
if (dim == AntarchySettings.brownAntDestinationDimension()) {
if (type != PermanentPortalType.ELYTHIA && dim == AntarchySettings.brownAntDestinationDimension()) {
return new int[]{75, 120};
}
return null;
@@ -97,7 +97,6 @@ public final class TimeDilationApi {
SYNCED_CLIENT_RATES.put(entityUuid, clampedRate);
}
/** Clears both the authoritative entity state and any client-side UUID cache. */
public static void clearRate(Entity entity) {
if (entity instanceof TimeDilationEntityAccess access) {
boolean wasDilated = Math.abs(access.antarchy$getTimeDilationRate() - TimeDilationMath.NORMAL_RATE) > 0.001D;
@@ -1,6 +1,10 @@
package com.craisinlord.antarchy.content.time;
import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.content.AntarchyTags;
import com.craisinlord.antarchy.content.entity.royal.QueenEntity;
import com.craisinlord.antarchy.content.entity.royal.RoyalBlackHoleEntity;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
@@ -25,6 +29,8 @@ 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;
public TimeDilationFieldEntity(EntityType<? extends TimeDilationFieldEntity> entityType, Level level) {
@@ -56,6 +62,23 @@ 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 boolean affects(Entity entity) {
if (entity instanceof RoyalBlackHoleEntity || this.isOwnedBy(entity)
|| this.anchorId != null && this.anchorId.equals(entity.getUUID())) {
return false;
}
if (this.ownerId != null && this.level() instanceof ServerLevel level
&& level.getEntity(this.ownerId) instanceof QueenEntity) {
return !entity.getType().is(AntarchyTags.Entities.QUEEN_DOES_NOT_ATTACK);
}
return true;
}
public boolean isVisual() {
return this.visual;
}
@@ -72,6 +95,19 @@ public class TimeDilationFieldEntity extends Entity {
if (this.level().isClientSide) {
return;
}
if (this.anchorId != null && this.level() instanceof ServerLevel level) {
Entity anchor = level.getEntity(this.anchorId);
if (anchor != null && anchor.isAlive()) {
Vec3 center = anchor instanceof RoyalBlackHoleEntity blackHole
? blackHole.effectCenter()
: anchor.position();
this.setPos(center.x, center.y, center.z);
this.missingAnchorTicks = 0;
} else if (++this.missingAnchorTicks > 20) {
this.discard();
return;
}
}
this.age++;
if (this.durationTicks >= 0 && this.age >= this.durationTicks) {
this.discard();
@@ -127,6 +163,7 @@ 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");
}
@@ -139,6 +176,9 @@ 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);
}
}
@@ -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();
@@ -1,7 +1,12 @@
package com.craisinlord.antarchy.content.time;
import java.util.ArrayList;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
@@ -9,33 +14,57 @@ import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.projectile.Projectile;
import com.craisinlord.antarchy.content.effect.DilatedMobEffect;
import com.craisinlord.antarchy.content.effect.ContractedMobEffect;
import com.craisinlord.antarchy.content.effect.ContractedMobEffect;
import com.craisinlord.antarchy.content.effect.ContractedMobEffect;
import com.craisinlord.antarchy.content.effect.RoyalEffectEligibility;
import com.craisinlord.antarchy.content.effect.RoyalEffectHooks;
public final class TimeDilationManager {
private static final Map<ServerLevel, TrackingState> TRACKING = new WeakHashMap<>();
private TimeDilationManager() {
}
public static void tickServer(MinecraftServer server) {
for (ServerLevel level : server.getAllLevels()) {
List<TimeDilationFieldEntity> fields = collectFields(level);
syncFieldSnapshots(level, fields);
updateEntities(level, fields);
TrackingState tracking = TRACKING.computeIfAbsent(level, ignored -> new TrackingState());
discoverFieldEntities(level, fields, tracking);
syncFieldSnapshots(level, fields, tracking);
updateEntities(level, fields, tracking);
if (fields.isEmpty() && tracking.entities.isEmpty()) {
TRACKING.remove(level);
}
if (!fields.isEmpty()) {
TimeDilationParticles.spawnFieldBorders(level, fields.stream().filter(TimeDilationFieldEntity::isVisual).toList());
}
}
}
private static void syncFieldSnapshots(ServerLevel level, List<TimeDilationFieldEntity> fields) {
public static void trackPotentiallyAffected(Entity entity) {
if (!(entity.level() instanceof ServerLevel level) || entity instanceof TimeDilationFieldEntity) {
return;
}
if (entity instanceof TimeDilationEntityAccess access
&& Math.abs(access.antarchy$getTimeDilationRate() - TimeDilationMath.NORMAL_RATE) < 0.001D
&& Math.abs(access.antarchy$getInheritedTimeDilationRate() - TimeDilationMath.NORMAL_RATE) < 0.001D
&& (!(entity instanceof net.minecraft.world.entity.LivingEntity living)
|| ((RoyalEffectHooks.dilatedHolder() == null || !living.hasEffect(RoyalEffectHooks.dilatedHolder()))
&& (RoyalEffectHooks.contractedHolder() == null || !living.hasEffect(RoyalEffectHooks.contractedHolder()))))) {
return;
}
TRACKING.computeIfAbsent(level, ignored -> new TrackingState()).entities.add(entity);
}
private static void syncFieldSnapshots(ServerLevel level, List<TimeDilationFieldEntity> fields, TrackingState tracking) {
List<TimeDilationFieldSnapshot> snapshots = fields.stream()
.map(field -> new TimeDilationFieldSnapshot(
field.getX(), field.getY(), field.getZ(), field.fieldRadius(), field.fieldRate(),
field.fieldAge(), field.fieldDurationTicks()))
.limit(128)
.toList();
if (level.getGameTime() % 4L != 0L && snapshots.size() == tracking.lastSnapshots.size()) {
return;
}
tracking.lastSnapshots = snapshots;
for (ServerPlayer player : level.players()) {
TimeDilationApi.syncFields(player, snapshots);
}
@@ -51,8 +80,23 @@ public final class TimeDilationManager {
return fields;
}
private static void updateEntities(ServerLevel level, List<TimeDilationFieldEntity> fields) {
for (Entity entity : level.getAllEntities()) {
private static void discoverFieldEntities(ServerLevel level, List<TimeDilationFieldEntity> fields, TrackingState tracking) {
for (TimeDilationFieldEntity field : fields) {
double radius = field.fieldRadius();
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);
tracking.entities.addAll(level.getEntities(field, area,
entity -> entity.isAlive() && !(entity instanceof TimeDilationFieldEntity)
&& entity instanceof TimeDilationEntityAccess));
}
}
private static void updateEntities(ServerLevel level, List<TimeDilationFieldEntity> fields, TrackingState tracking) {
var iterator = tracking.entities.iterator();
while (iterator.hasNext()) {
Entity entity = iterator.next();
if (!(entity instanceof TimeDilationEntityAccess access) || entity instanceof TimeDilationFieldEntity) {
continue;
}
@@ -62,6 +106,7 @@ public final class TimeDilationManager {
living.removeEffect(RoyalEffectHooks.dilatedHolder());
}
TimeDilationApi.clearRate(entity);
iterator.remove();
continue;
}
double previousRate = access.antarchy$getTimeDilationRate();
@@ -116,7 +161,23 @@ public final class TimeDilationManager {
if (Math.abs(previousRate - rate) > 0.001D) {
TimeDilationApi.syncEntityRate(entity, rate);
}
boolean hasDilationEffect = entity instanceof net.minecraft.world.entity.LivingEntity living
&& ((RoyalEffectHooks.dilatedHolder() != null && living.hasEffect(RoyalEffectHooks.dilatedHolder()))
|| (RoyalEffectHooks.contractedHolder() != null && living.hasEffect(RoyalEffectHooks.contractedHolder())));
boolean hasInheritedRate = Math.abs(access.antarchy$getInheritedTimeDilationRate() - TimeDilationMath.NORMAL_RATE) >= 0.001D;
boolean hasActiveRate = Math.abs(access.antarchy$getTimeDilationRate() - TimeDilationMath.NORMAL_RATE) >= 0.001D;
if (!hasDilationEffect && !hasInheritedRate && !hasActiveRate
&& Math.abs(targetRate - TimeDilationMath.NORMAL_RATE) < 0.001D) {
iterator.remove();
}
}
tracking.entities.removeIf(entity -> !entity.isAlive() || entity.isRemoved());
}
private static final class TrackingState {
private final Set<Entity> entities = Collections.newSetFromMap(new IdentityHashMap<>());
private List<TimeDilationFieldSnapshot> lastSnapshots = List.of();
}
}
@@ -18,7 +18,6 @@ public final class TimeDilationMath {
return Math.max(MIN_RATE, Math.min(MAX_RATE, rate));
}
/** Moves an entity's effective rate toward a new effect/field target without a hard step. */
public static double transitionRate(double currentRate, double targetRate) {
double current = clampRate(currentRate);
double target = clampRate(targetRate);
@@ -9,8 +9,10 @@ import com.mojang.serialization.Codec;
import java.util.ArrayList;
import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
@@ -33,6 +35,13 @@ import net.minecraft.world.level.block.state.properties.IntegerProperty;
import org.jetbrains.annotations.Nullable;
public class OuranwoodTreeFeature extends Feature<OuranwoodTreeConfiguration> {
private static final ThreadLocal<TreePlacement> ACTIVE_PLACEMENT = new ThreadLocal<>();
private static final class TreePlacement {
private final Set<BlockPos> logPositions = new LinkedHashSet<>();
private final Set<BlockPos> leafPositions = new LinkedHashSet<>();
}
public OuranwoodTreeFeature(Codec<OuranwoodTreeConfiguration> codec) {
super(codec);
}
@@ -44,9 +53,12 @@ public class OuranwoodTreeFeature extends Feature<OuranwoodTreeConfiguration> {
OuranwoodTreeConfiguration config = context.config();
RandomSource random = context.random();
if (!canGrowOn(level, origin.below())) {
return false;
}
TreePlacement placement = new TreePlacement();
ACTIVE_PLACEMENT.set(placement);
try {
if (!canGrowOn(level, origin.below())) {
return false;
}
int height = config.height().sample(random);
int trunkRadius = config.trunkRadius().sample(random);
@@ -86,7 +98,10 @@ public class OuranwoodTreeFeature extends Feature<OuranwoodTreeConfiguration> {
this.placeVinesAndGlowBerries(level, canopyCenter, config, random, canopyRadius, canopyDepth, height);
this.placeFlyingSquirrelNest(level, origin, config, random, trunkRadius, height);
this.placeCaterpillarChrysalis(level, origin, config, random, canopyCenter, canopyRadius, canopyDepth);
return true;
return true;
} finally {
ACTIVE_PLACEMENT.remove();
}
}
protected BlockPos resolveCanopyCenter(BlockPos origin, int height, int canopyDepth) {
@@ -1044,6 +1059,28 @@ public class OuranwoodTreeFeature extends Feature<OuranwoodTreeConfiguration> {
}
protected void pruneExposedTopLogs(WorldGenLevel level, BlockPos origin, BlockPos canopyCenter, OuranwoodTreeConfiguration config, RandomSource random, int trunkRadius, int canopyRadius, int canopyDepth, int height) {
TreePlacement placement = ACTIVE_PLACEMENT.get();
if (placement != null) {
for (BlockPos pos : placement.logPositions) {
if (this.isWithinTrunkCore(origin, pos, trunkRadius, canopyCenter.getY())) {
continue;
}
BlockState state = level.getBlockState(pos);
if (!state.is(BlockTags.LOGS)) {
continue;
}
int leafNeighbors = this.countLeafNeighbors(level, pos);
int openFaces = this.countOpenFaces(level, pos);
boolean topExposed = level.getBlockState(pos.above()).isAir();
boolean lowerCanopyStray = pos.getY() <= canopyCenter.getY()
&& leafNeighbors >= 3
&& openFaces >= 2;
if ((topExposed && leafNeighbors >= 1) || lowerCanopyStray) {
this.setLeaf(level, pos, config, random);
}
}
return;
}
int horizontalRadius = canopyRadius + 6;
int minY = canopyCenter.getY() - Math.max(4, canopyDepth / 2 + 1);
int maxY = origin.getY() + height + canopyDepth + 6;
@@ -1077,6 +1114,23 @@ public class OuranwoodTreeFeature extends Feature<OuranwoodTreeConfiguration> {
}
protected void coverExposedBranchSides(WorldGenLevel level, BlockPos origin, BlockPos canopyCenter, OuranwoodTreeConfiguration config, RandomSource random, int trunkRadius, int canopyRadius, int canopyDepth, int height) {
TreePlacement placement = ACTIVE_PLACEMENT.get();
if (placement != null) {
for (BlockPos pos : placement.logPositions) {
if (!level.getBlockState(pos).is(BlockTags.LOGS)
|| this.isWithinTrunkCore(origin, pos, trunkRadius, canopyCenter.getY())
|| this.countLeafNeighbors(level, pos) < 1) {
continue;
}
for (Direction direction : Direction.Plane.HORIZONTAL) {
BlockPos sidePos = pos.relative(direction);
if (level.getBlockState(sidePos).isAir() && this.canWrapBranchSide(level, pos, direction)) {
this.tryPlaceLeaf(level, sidePos, config, random);
}
}
}
return;
}
int horizontalRadius = canopyRadius + 6;
int minY = Math.max(origin.getY() + Math.max(4, height / 4), canopyCenter.getY() - canopyDepth - 3);
int maxY = origin.getY() + height + canopyDepth + 4;
@@ -1112,6 +1166,11 @@ public class OuranwoodTreeFeature extends Feature<OuranwoodTreeConfiguration> {
}
protected void updateLeafDistances(WorldGenLevel level, BlockPos origin, int height, int canopyRadius, int canopyDepth) {
TreePlacement placement = ACTIVE_PLACEMENT.get();
if (placement != null) {
this.updateLeafDistances(level, placement.leafPositions);
return;
}
int horizontalRadius = canopyRadius + 2;
int minY = Math.max(level.getMinBuildHeight(), origin.getY() - 2);
int maxY = Math.min(level.getMaxBuildHeight() - 1, origin.getY() + height + canopyDepth + 8);
@@ -1200,6 +1259,78 @@ public class OuranwoodTreeFeature extends Feature<OuranwoodTreeConfiguration> {
}
}
private void updateLeafDistances(WorldGenLevel level, Set<BlockPos> leafPositions) {
Map<BlockPos, Integer> distances = new HashMap<>();
ArrayDeque<BlockPos> queue = new ArrayDeque<>();
for (BlockPos pos : leafPositions) {
BlockState state = level.getBlockState(pos);
IntegerProperty property = this.getLeafDistanceProperty(state);
if (property != null) {
distances.put(pos, this.getLeafDistanceLimit(state));
}
}
for (BlockPos pos : leafPositions) {
BlockState state = level.getBlockState(pos);
IntegerProperty property = this.getLeafDistanceProperty(state);
if (property == null) {
continue;
}
int bestDistance = this.getLeafDistanceLimit(state);
for (Direction direction : Direction.values()) {
BlockPos neighborPos = pos.relative(direction);
BlockState neighbor = level.getBlockState(neighborPos);
if (neighbor.is(BlockTags.LOGS)) {
bestDistance = 1;
break;
}
IntegerProperty neighborProperty = this.getLeafDistanceProperty(neighbor);
if (neighborProperty != null && !distances.containsKey(neighborPos)) {
bestDistance = Math.min(bestDistance, neighbor.getValue(neighborProperty) + 1);
}
}
if (bestDistance < distances.get(pos)) {
distances.put(pos, bestDistance);
queue.add(pos);
}
}
while (!queue.isEmpty()) {
BlockPos pos = queue.removeFirst();
int distance = distances.get(pos);
for (Direction direction : Direction.values()) {
BlockPos neighborPos = pos.relative(direction);
Integer neighborDistance = distances.get(neighborPos);
if (neighborDistance == null) {
continue;
}
BlockState neighborState = level.getBlockState(neighborPos);
int candidate = Math.min(this.getLeafDistanceLimit(neighborState), distance + 1);
if (candidate < neighborDistance) {
distances.put(neighborPos, candidate);
queue.addLast(neighborPos);
}
}
}
for (BlockPos pos : leafPositions) {
BlockState state = level.getBlockState(pos);
IntegerProperty property = this.getLeafDistanceProperty(state);
if (property == null) {
continue;
}
int computedDistance = distances.get(pos);
if (computedDistance == state.getValue(property)) {
continue;
}
BlockState updatedState = state.getBlock() instanceof OuranwoodLeavesBlock
? OuranwoodLeavesBlock.setOuranwoodDistanceForWorldgen(state, computedDistance)
: state.setValue(property, computedDistance);
setBlock(level, pos, updatedState);
}
}
protected void placeHangingAcorns(WorldGenLevel level, BlockPos canopyCenter, OuranwoodTreeConfiguration config, RandomSource random, int canopyRadius, int canopyDepth) {
int maxAcorns = config.juvenile() ? 12 + random.nextInt(6) : 40 + random.nextInt(20);
int attempts = maxAcorns * 12;
@@ -1530,6 +1661,15 @@ public class OuranwoodTreeFeature extends Feature<OuranwoodTreeConfiguration> {
protected void setBlock(WorldGenLevel level, BlockPos pos, BlockState state) {
if (canWriteAt(level, pos)) {
setBlock((LevelWriter) level, pos, state);
TreePlacement placement = ACTIVE_PLACEMENT.get();
if (placement != null) {
BlockPos immutablePos = pos.immutable();
if (state.is(BlockTags.LOGS)) {
placement.logPositions.add(immutablePos);
} else if (state.is(BlockTags.LEAVES)) {
placement.leafPositions.add(immutablePos);
}
}
}
}
@@ -2,6 +2,7 @@ package com.craisinlord.antarchy.content.worldgen.thoraxis;
import com.craisinlord.antarchy.content.AntarchyObjects;
import com.mojang.serialization.Codec;
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
import net.minecraft.core.BlockPos;
import net.minecraft.util.Mth;
import net.minecraft.util.RandomSource;
@@ -34,6 +35,7 @@ public final class LucidAntiwaterPoolFeature extends Feature<ThoraxisAntiwaterPo
int sourceCount = Math.max(2, randomBetween(random, 2, 4));
BlockState antiwaterSourceState = config.state().getState(random, origin);
BlockState basaltState = AntarchyObjects.ANTIMETAL.get().defaultBlockState();
Long2IntOpenHashMap ceilingCache = new Long2IntOpenHashMap();
int floorY = findFloorY(level, origin.getX(), origin.getZ());
if (floorY <= level.getMinBuildHeight()) {
@@ -55,7 +57,7 @@ public final class LucidAntiwaterPoolFeature extends Feature<ThoraxisAntiwaterPo
int ventBudget = sourceCount + random.nextInt(2);
BasinProfile profile = BasinProfile.create(random);
placedAny |= placeCeilingBasin(level, random, offsetX, floorY, offsetZ, lobeRadius, ceilingY, profile, basaltState, antiwaterSourceState);
placedAny |= placeCeilingBasin(level, random, offsetX, floorY, offsetZ, lobeRadius, ceilingY, profile, basaltState, antiwaterSourceState, ceilingCache);
placedAny |= placeAntiwaterSources(level, random, offsetX, floorY, offsetZ, Math.max(2, lobeRadius - 1), ventBudget, antiwaterSourceState);
}
@@ -72,7 +74,8 @@ public final class LucidAntiwaterPoolFeature extends Feature<ThoraxisAntiwaterPo
int maxCeilingY,
BasinProfile profile,
BlockState basaltState,
BlockState antiwaterSourceState
BlockState antiwaterSourceState,
Long2IntOpenHashMap ceilingCache
) {
boolean placedAny = false;
BlockPos.MutableBlockPos mutable = new BlockPos.MutableBlockPos();
@@ -87,7 +90,7 @@ public final class LucidAntiwaterPoolFeature extends Feature<ThoraxisAntiwaterPo
int worldX = centerX + dx;
int worldZ = centerZ + dz;
int localCeilingY = findCeilingY(level, worldX, worldZ, floorY);
int localCeilingY = cachedCeilingY(level, worldX, worldZ, floorY, ceilingCache);
if (localCeilingY <= floorY + MIN_CAVITY_HEIGHT || localCeilingY > maxCeilingY + 3) {
continue;
}
@@ -113,7 +116,7 @@ public final class LucidAntiwaterPoolFeature extends Feature<ThoraxisAntiwaterPo
placedAny = true;
}
placedAny |= placeOuterLip(level, random, worldX, worldZ, floorY, basinTopY, profile, dx, dz, radius, basaltState);
placedAny |= placeOuterLip(level, random, worldX, worldZ, floorY, basinTopY, profile, dx, dz, radius, basaltState, ceilingCache);
continue;
}
@@ -145,7 +148,8 @@ public final class LucidAntiwaterPoolFeature extends Feature<ThoraxisAntiwaterPo
int dx,
int dz,
int radius,
BlockState basaltState
BlockState basaltState,
Long2IntOpenHashMap ceilingCache
) {
boolean placedAny = false;
BlockPos.MutableBlockPos mutable = new BlockPos.MutableBlockPos();
@@ -166,7 +170,7 @@ public final class LucidAntiwaterPoolFeature extends Feature<ThoraxisAntiwaterPo
int x = centerX + stepX;
int z = centerZ + stepZ;
int localCeilingY = findCeilingY(level, x, z, floorY);
int localCeilingY = cachedCeilingY(level, x, z, floorY, ceilingCache);
if (localCeilingY <= floorY + MIN_CAVITY_HEIGHT) {
continue;
}
@@ -300,6 +304,23 @@ public final class LucidAntiwaterPoolFeature extends Feature<ThoraxisAntiwaterPo
return Integer.MIN_VALUE;
}
private static int cachedCeilingY(
WorldGenLevel level,
int x,
int z,
int floorY,
Long2IntOpenHashMap cache
) {
long key = BlockPos.asLong(x, 0, z);
if (cache.containsKey(key)) {
return cache.get(key);
}
int ceilingY = findCeilingY(level, x, z, floorY);
cache.put(key, ceilingY);
return ceilingY;
}
private static boolean isSolidSurface(BlockState state) {
return !state.isAir() && state.blocksMotion() && state.getFluidState().isEmpty();
}
@@ -16,7 +16,6 @@ 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;
/** Places the complete natural-grown shape so every segment gets the body state. */
public final class NaturalDirectionalVineFeature extends Feature<NoneFeatureConfiguration> {
private final ResourceLocation blockId;
private final Direction direction;
@@ -11,10 +11,6 @@ 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;
/**
* Fills the low, open main-side Thoraxis basin with source ichor during chunk generation.
* The feature works one chunk at a time so it does not depend on runtime fluid spreading.
*/
public final class ThoraxisIchorLakeFeature extends Feature<NoneFeatureConfiguration> {
private static final int LAKE_FLOOR_Y = 1;
private static final int LAKE_SURFACE_Y = 8;
@@ -71,6 +71,7 @@ public final class ThoraxisUndersideManager {
int refreshedLiving = 0;
int flippedItems = 0;
int restoredItems = 0;
Set<Entity> evicted = Collections.newSetFromMap(new IdentityHashMap<>());
for (Entity entity : tracking.entities) {
if (!entity.isAlive() || entity.isSpectator()) {
continue;
@@ -82,6 +83,8 @@ public final class ThoraxisUndersideManager {
if (living.getY() < threshold) {
refreshInvertedEffect(living);
refreshedLiving++;
} else if (!hasEffect) {
evicted.add(entity);
}
continue;
}
@@ -122,9 +125,12 @@ public final class ThoraxisUndersideManager {
AntarchyGravityApi.setGravityDirection(entity, AntarchyGravityDirection.DOWN, false, TRANSITION);
restoredItems++;
}
evicted.add(entity);
}
}
tracking.entities.removeAll(evicted);
tracking.forcedItems.removeAll(evicted);
tracking.entities.removeIf(entity -> !entity.isAlive() || entity.isRemoved());
tracking.forcedItems.removeIf(entity -> !entity.isAlive() || entity.isRemoved());
tracking.lastFlipTick.entrySet().removeIf(entry -> now - entry.getValue() > FLIP_COOLDOWN_TICKS * 4L);
@@ -222,4 +228,11 @@ public final class ThoraxisUndersideManager {
public static boolean isAboveUndersideExit(Entity entity) {
return isThoraxis(entity.level()) && entity.getY() >= EXIT_UNDERSIDE_Y;
}
/** True while an entity is in the region where the underside gravity rule applies. */
public static boolean shouldInvertInUnderside(Entity entity) {
return AntarchyGravityApi.isGravityInverted(entity)
&& isThoraxis(entity.level())
&& entity.getY() < GRAVITY_FLIP_Y;
}
}
@@ -1,16 +1,15 @@
package com.craisinlord.antarchy.mixins.gravity;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityDirection;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityRotationUtil;
import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.worldgen.thoraxis.ThoraxisUndersideManager;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.projectile.AbstractArrow;
import net.minecraft.world.phys.Vec3;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
@Mixin(AbstractArrow.class)
/*
@@ -18,20 +17,24 @@ import org.spongepowered.asm.mixin.injection.ModifyVariable;
*/
public abstract class AbstractArrowGravityMixin {
@ModifyVariable(method = "tick", at = @At(value = "STORE"), ordinal = 0)
private Vec3 antarchy$fixArrowGravityDroop(Vec3 velocity) {
AbstractArrow self = (AbstractArrow) (Object) this;
@WrapOperation(method = "tick", at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/entity/projectile/AbstractArrow;applyGravity()V"))
private void antarchy$applyArrowGravityUpward(AbstractArrow self, Operation<Void> original) {
Entity owner = self.getOwner();
if (!(owner instanceof LivingEntity living)) return velocity;
if (owner instanceof net.minecraft.world.entity.player.Player && !AntarchySettings.invertProjectilesFromInvertedPlayers()) {
return velocity;
if (!(owner instanceof LivingEntity living)) {
original.call(self);
return;
}
AntarchyGravityDirection direction = AntarchyGravityApi.getGravityDirection(living);
if (!direction.isInverted()) return velocity;
velocity = new Vec3(velocity.x, velocity.y + 0.05D, velocity.z);
velocity = AntarchyGravityRotationUtil.vecWorldToPlayer(velocity, direction);
velocity = new Vec3(velocity.x, velocity.y - 0.05D, velocity.z);
velocity = AntarchyGravityRotationUtil.vecPlayerToWorld(velocity, direction);
return velocity;
if (owner instanceof net.minecraft.world.entity.player.Player && !AntarchySettings.invertProjectilesFromInvertedPlayers()) {
original.call(self);
return;
}
if (!ThoraxisUndersideManager.shouldInvertInUnderside(living)) {
original.call(self);
return;
}
double gravity = self.getGravity();
self.setDeltaMovement(self.getDeltaMovement().add(0.0D, gravity, 0.0D));
}
}
@@ -68,8 +68,6 @@ public abstract class BehaviorUtilsGravityMixin {
}
private static boolean antarchy$shouldInvertThrownItem(LivingEntity entity) {
return AntarchyGravityApi.isGravityInverted(entity)
&& ThoraxisUndersideManager.isThoraxis(entity.level())
&& entity.getY() < ThoraxisUndersideManager.GRAVITY_FLIP_Y;
return ThoraxisUndersideManager.shouldInvertInUnderside(entity);
}
}
@@ -54,10 +54,6 @@ public abstract class BucketItemGravityMixin {
}
Direction face = blockHitResult.getDirection();
if (face.getAxis() != Direction.Axis.Y) {
return;
}
BlockPos clickedPos = blockHitResult.getBlockPos();
BlockState clickedState = level.getBlockState(clickedPos);
Block clickedBlock = clickedState.getBlock();
@@ -4,6 +4,7 @@ import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityDirection;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityRotationUtil;
import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.worldgen.thoraxis.ThoraxisUndersideManager;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import net.minecraft.world.entity.LivingEntity;
@@ -54,7 +55,8 @@ public abstract class CrossbowItemGravityMixin {
}
private static boolean antarchy$shouldFixProjectileSpawn(LivingEntity shooter) {
return !(shooter instanceof net.minecraft.world.entity.player.Player)
|| AntarchySettings.invertProjectilesFromInvertedPlayers();
return ThoraxisUndersideManager.shouldInvertInUnderside(shooter)
&& (!(shooter instanceof net.minecraft.world.entity.player.Player)
|| AntarchySettings.invertProjectilesFromInvertedPlayers());
}
}
@@ -1,19 +1,41 @@
package com.craisinlord.antarchy.mixins.gravity;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityDirection;
import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.worldgen.thoraxis.ThoraxisUndersideManager;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.projectile.ThrowableProjectile;
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(ThrowableProjectile.class)
/*
* Flips thrown projectile gravity for inverted shooters.
*/
public abstract class ThrowableProjectileGravityMixin {
@WrapOperation(method = "tick", at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/entity/projectile/ThrowableProjectile;applyGravity()V"))
private void antarchy$applyThrownProjectileGravityUpward(ThrowableProjectile self, Operation<Void> original) {
Entity owner = self.getOwner();
if (!(owner instanceof LivingEntity living)) {
original.call(self);
return;
}
if (owner instanceof Player && !AntarchySettings.invertProjectilesFromInvertedPlayers()) {
original.call(self);
return;
}
if (!ThoraxisUndersideManager.shouldInvertInUnderside(living)) {
original.call(self);
return;
}
double gravity = self.getGravity();
self.setDeltaMovement(self.getDeltaMovement().add(0.0D, gravity, 0.0D));
}
}
@@ -21,7 +21,7 @@ public abstract class EntityDeltaMovementTimeDilationMixin {
}
double rate = TimeDilationApi.getRate(entity);
if (Math.abs(rate - TimeDilationMath.NORMAL_RATE) < 0.001D) {
if (rate >= TimeDilationMath.NORMAL_RATE - 0.001D) {
return nextMovement;
}
@@ -58,6 +58,13 @@ public abstract class LivingEntityRendererAnimationTimeDilationMixin {
return entity == null ? amount : ClientTimeDilationTicker.dilateWalkAmount(entity, amount);
}
@Redirect(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/WalkAnimationState;position(F)F"))
private float antarchy$scaleWalkPosition(net.minecraft.world.entity.WalkAnimationState state, float partialTick) {
LivingEntity entity = antarchy$renderingEntity.get();
float position = state.position(partialTick);
return entity == null ? position : ClientTimeDilationTicker.dilateWalkPosition(entity, position);
}
@org.spongepowered.asm.mixin.injection.Inject(method = "getOverlayCoords", at = @At("HEAD"), cancellable = true)
private static void antarchy$slowHurtOverlay(LivingEntity entity, float partialTick, CallbackInfoReturnable<Integer> cir) {
if (com.craisinlord.antarchy.content.time.TimeDilationApi.getRate(entity) < 1.0D
@@ -1,6 +1,7 @@
package com.craisinlord.antarchy.mixins.time;
import com.craisinlord.antarchy.content.time.TimeDilationApi;
import com.craisinlord.antarchy.content.time.TimeDilationManager;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
@@ -11,6 +12,11 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(LivingEntity.class)
public abstract class LivingEntityTimeDilationMixin {
@Inject(method = "tickEffects", at = @At("HEAD"))
private void antarchy$trackTemporalEffects(CallbackInfo ci) {
TimeDilationManager.trackPotentiallyAffected((Entity) (Object) this);
}
@Inject(method = "updateSwingTime", at = @At("HEAD"), cancellable = true)
private void antarchy$slowSwingTime(CallbackInfo ci) {
Entity entity = (Entity) (Object) this;
@@ -543,7 +543,7 @@
"item.antarchy.royal_assailant_battleaxe": "Royal Assailant Battle Axe",
"item.antarchy.royal_assailant_staff": "Royal Assailant Staff",
"tooltip.antarchy.royal_assailant_staff": "Opens paired tears that summon four allied Manticores for 30 seconds.",
"tooltip.antarchy.royal_assailant_battleaxe.black_hole": "Use to create a Royal Black Hole",
"tooltip.antarchy.royal_assailant_battleaxe.black_hole": "Use to create a Black Hole",
"tooltip.antarchy.royal_assailant_battleaxe.dilation": "Hits apply Temporal Dilation",
"tooltip.antarchy.royal_guardian_shield.command": "Use to command the nearest mob",
"tooltip.antarchy.royal_assailant_chestplate.invert": "Press the inversion key to toggle inverted gravity on and off.",
@@ -1047,6 +1047,9 @@
"subtitles.antarchy.king_beam_shoot": "The King fires a beam",
"subtitles.antarchy.king_fireball_shoot": "The King breathes fire",
"subtitles.antarchy.king_decree_cast": "The King pronounces a Decree",
"subtitles.antarchy.king_decree": "The King issues a Decree",
"subtitles.antarchy.king_success": "The King acknowledges compliance",
"subtitles.antarchy.king_judgement": "The King passes Judgement",
"subtitles.antarchy.queen_idle": "The Queen murmurs",
"subtitles.antarchy.queen_walk": "The Queen's footfalls",
"subtitles.antarchy.queen_fly_loop": "The Queen's wings beat",
@@ -2825,6 +2825,30 @@
}
]
},
"king_decree": {
"subtitle": "subtitles.antarchy.king_decree",
"sounds": [
{
"name": "antarchy:mob/king/decree"
}
]
},
"king_success": {
"subtitle": "subtitles.antarchy.king_success",
"sounds": [
{
"name": "antarchy:mob/king/success"
}
]
},
"king_judgement": {
"subtitle": "subtitles.antarchy.king_judgement",
"sounds": [
{
"name": "antarchy:mob/king/judgement"
}
]
},
"queen_idle": {
"subtitle": "subtitles.antarchy.queen_idle",
"sounds": [
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 314 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 393 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 B

@@ -7,6 +7,15 @@
{
"type": "minecraft:item",
"name": "antarchy:worm_tooth",
"conditions": [
{
"condition": "minecraft:entity_properties",
"entity": "this",
"predicate": {
"nbt": "{GrowthStage:2}"
}
}
],
"functions": [
{
"function": "minecraft:set_count",
@@ -5,7 +5,18 @@
"minecraft:enchanted_golden_apple",
"minecraft:golden_carrot",
"minecraft:glistering_melon_slice",
"antarchy:king_scale",
"antarchy:queen_scale"
"minecraft:gold_ingot",
"minecraft:gold_nugget",
"minecraft:gold_block",
"minecraft:golden_sword",
"minecraft:golden_pickaxe",
"minecraft:golden_axe",
"minecraft:golden_shovel",
"minecraft:golden_hoe",
"minecraft:golden_helmet",
"minecraft:golden_chestplate",
"minecraft:golden_leggings",
"minecraft:golden_boots",
"minecraft:golden_horse_armor"
]
}
@@ -8,6 +8,7 @@
"location": "antarchy:kings_tree/kings_tree1",
"processors": "antarchy:generic_processor",
"projection": "rigid",
"ignore_bounds": true,
"enhanced_terrain_adaptation": {
"type": "integrated_api:custom",
"carves": true,
@@ -8,6 +8,7 @@
"location": "antarchy:kings_tree/kings_tree2",
"processors": "antarchy:generic_processor",
"projection": "rigid",
"ignore_bounds": true,
"enhanced_terrain_adaptation": {
"type": "integrated_api:custom",
"carves": true,
@@ -8,6 +8,7 @@
"location": "antarchy:kings_tree/kings_tree3",
"processors": "antarchy:generic_processor",
"projection": "rigid",
"ignore_bounds": true,
"enhanced_terrain_adaptation": {
"type": "integrated_api:custom",
"carves": true,
@@ -8,6 +8,7 @@
"location": "antarchy:kings_tree/kings_tree4",
"processors": "antarchy:generic_processor",
"projection": "rigid",
"ignore_bounds": true,
"enhanced_terrain_adaptation": {
"type": "integrated_api:custom",
"carves": true,
@@ -8,10 +8,11 @@
"location": "antarchy:kings_tree/kings_tree_bottom",
"processors": "antarchy:generic_processor",
"projection": "rigid",
"ignore_bounds": true,
"enhanced_terrain_adaptation": {
"type": "integrated_api:custom",
"carves": true,
"beards": true,
"carves": false,
"beards": false,
"kernel_size": 35,
"kernel_distance": 35
},