undertrial sounds

inverted squidzooka, rpo
king and queen tweaks
fix attidue adjustor bug
This commit is contained in:
craisinlord committed 2026-09-16 00:05:20 -05:00
1 parent aefbfb9f32
commit 82cea4c293
8 files changed
+655 -66

No files matched your search

@@ -67,7 +67,9 @@ import com.craisinlord.antarchy.content.entity.royal.attack.RoyalAttackLane;
import java.util.EnumMap;
import java.util.List;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
public class KingEntity extends RoyalBossEntity {
@@ -102,6 +104,9 @@ public class KingEntity extends RoyalBossEntity {
private static final int GROUND_ASSAULT_TICKS = 200;
private static final int GROUND_ASSAULT_COOLDOWN_MIN = 600;
private static final int GROUND_ASSAULT_COOLDOWN_VARIANCE = 300;
private static final int LANDING_TELEGRAPH_TICKS = 24;
private static final int LANDING_IMPACT_TICKS = 18;
private static final int TAKEOFF_TICKS = 30;
private static final int COMBAT_WAYPOINT_MIN_TICKS = 45;
private static final int COMBAT_WAYPOINT_VARIANCE = 35;
@@ -130,12 +135,13 @@ public class KingEntity extends RoyalBossEntity {
private boolean decreeRetreatPressure;
@Nullable
private String lastDecreeKey;
private boolean closeQuartersEntered;
private int closeQuartersOutsideTicks;
private final Set<UUID> closeQuartersEntered = new HashSet<>();
private final Map<UUID, Integer> closeQuartersOutsideTicks = new HashMap<>();
private final Set<UUID> decreeRecipients = new HashSet<>();
private int projectilePressure;
private int groundAssaultCooldownTicks = 400;
private int groundAssaultTicks;
private boolean kingLandingForCombat;
private KingLandingStage kingLandingStage = KingLandingStage.AERIAL;
private int kingLandingStageTicks;
@Nullable
private Vec3 kingLandingWaypoint;
@Nullable
@@ -174,6 +180,15 @@ public class KingEntity extends RoyalBossEntity {
new StandTallDecree(), new StandYourGroundDecree(),
new HandsOffTheCrownDecree(), new ShowNoMercyDecree());
private enum KingLandingStage {
AERIAL,
TELEGRAPH,
DESCENT,
IMPACT,
GROUND_COMBAT,
TAKEOFF
}
public KingEntity(EntityType<? extends KingEntity> entityType, Level level) {
super(entityType, level);
}
@@ -188,6 +203,60 @@ public class KingEntity extends RoyalBossEntity {
return phase.maxConcurrentHeadAttacks();
}
@Override
protected boolean blocksRoyalAttacksForMovement() {
return this.kingLandingStage == KingLandingStage.TELEGRAPH
|| this.kingLandingStage == KingLandingStage.DESCENT
|| this.kingLandingStage == KingLandingStage.IMPACT
|| this.kingLandingStage == KingLandingStage.TAKEOFF;
}
@Override
protected void onRoyalPhaseTransitionStarted(Phase previousPhase, Phase nextPhase) {
if (this.activeDecree != null) {
this.endDecree(null);
}
this.resetKingCombatMovement();
this.setRoyalFlying(true);
this.setDeltaMovement(this.getDeltaMovement().add(0.0D, 0.25D, 0.0D));
this.getNavigation().stop();
this.setDeltaMovement(this.getDeltaMovement().scale(0.35D));
this.playRoyalSound(AntarchySoundEvents.KING_ROAR.get(), nextPhase == Phase.THREE ? 0.72F : 0.88F);
if (this.level() instanceof ServerLevel level) {
Component title = Component.translatable(nextPhase == Phase.TWO
? "boss.antarchy.king.phase_two" : "boss.antarchy.king.phase_three")
.withStyle(style -> style.withColor(nextPhase == Phase.TWO
? ChatFormatting.GOLD : ChatFormatting.DARK_PURPLE).withBold(true));
for (ServerPlayer player : this.royalEncounterPlayers(level)) {
player.connection.send(new ClientboundSetTitlesAnimationPacket(5, 45, 10));
player.connection.send(new ClientboundSetTitleTextPacket(title));
}
}
}
@Override
protected void tickRoyalPhaseTransition(Phase nextPhase, int elapsedTicks, int remainingTicks) {
this.getNavigation().stop();
this.setDeltaMovement(this.getDeltaMovement().scale(0.7D));
if (this.level() instanceof ServerLevel level && elapsedTicks % 3 == 0) {
level.sendParticles(nextPhase == Phase.THREE ? ParticleTypes.WITCH : ParticleTypes.ENCHANT,
this.getX(), this.getY() + this.getBbHeight() * 0.55D, this.getZ(),
18, this.getBbWidth() * 0.25D, this.getBbHeight() * 0.25D,
this.getBbWidth() * 0.25D, 0.08D);
}
}
@Override
protected void onRoyalPhaseTransitionCompleted(Phase nextPhase) {
this.decreeCooldownTicks = 0;
this.groundAssaultCooldownTicks = nextPhase == Phase.THREE ? 0 : Math.min(this.groundAssaultCooldownTicks, 100);
if (nextPhase == Phase.THREE) {
this.fireballCooldownTicks = 0;
this.iceballCooldownTicks = 0;
this.iceSpikeCooldownTicks = 0;
}
}
@Override
protected boolean managesOwnCombatLocomotion() {
return true;
@@ -432,12 +501,17 @@ public class KingEntity extends RoyalBossEntity {
if (this.tickCount % 20 == 0 && this.getArrowCount() > 6) {
this.setArrowCount(6);
}
if (this.isRoyalPhaseTransitionActive()) {
return;
}
LivingEntity target = this.getTarget();
if (target != null && !this.isDeadOrDying() && this.level() instanceof ServerLevel level) {
this.trackBehavior(target);
this.tickDecree(level, target);
this.tickKingWholeBody(level, target);
this.tickKingCombatMovement(target);
if (!this.blocksRoyalAttacksForMovement()) {
this.tickKingWholeBody(level, target);
}
this.stabilizeKingRotation();
return;
}
@@ -627,54 +701,83 @@ public class KingEntity extends RoyalBossEntity {
private void tickKingCombatMovement(LivingEntity target) {
boolean closeQuarters = this.activeDecree instanceof CloseQuartersDecree;
if (closeQuarters && this.isRoyalFlying() && !this.kingLandingForCombat) {
if (closeQuarters && this.isRoyalFlying() && this.kingLandingStage == KingLandingStage.AERIAL) {
this.beginKingLanding(target);
} else if (!closeQuarters && this.isRoyalFlying() && !this.kingLandingForCombat
} else if (!closeQuarters && this.isRoyalFlying() && this.kingLandingStage == KingLandingStage.AERIAL
&& this.groundAssaultCooldownTicks-- <= 0 && target.onGround()) {
this.beginKingLanding(target);
}
if (this.kingLandingForCombat) {
if (!target.onGround() && !closeQuarters) {
this.kingLandingForCombat = false;
this.kingLandingWaypoint = null;
this.groundAssaultCooldownTicks = 160;
switch (this.kingLandingStage) {
case TELEGRAPH -> {
this.getNavigation().stop();
this.setDeltaMovement(this.getDeltaMovement().scale(0.72D));
this.tickKingLandingIndicator();
if (--this.kingLandingStageTicks <= 0) {
this.kingLandingStage = KingLandingStage.DESCENT;
this.playRoyalSound(AntarchySoundEvents.KING_WING_FLAP.get(), 0.72F);
}
return;
}
if (this.kingLandingWaypoint == null) {
this.kingLandingWaypoint = this.findKingLandingPoint(target);
}
case DESCENT -> {
Vec3 landing = this.kingLandingWaypoint;
if (landing != null) {
if (landing == null) {
this.resetKingCombatMovement();
return;
}
this.setRoyalFlying(true);
this.getMoveControl().setWantedPosition(landing.x, landing.y, landing.z, 1.35D);
if (this.onGround() || this.getY() <= landing.y + 1.25D) {
this.tickKingLandingIndicator();
boolean reachedLanding = this.onGround()
|| this.position().distanceToSqr(landing) <= 2.25D
|| this.getY() <= landing.y + 1.25D;
if (reachedLanding) {
this.setRoyalFlying(false);
this.kingLandingForCombat = false;
this.kingLandingWaypoint = null;
this.groundAssaultTicks = closeQuarters
? Math.max(GROUND_ASSAULT_TICKS, this.activeDecreeTicks + 20)
: GROUND_ASSAULT_TICKS;
this.setPos(this.getX(), landing.y, this.getZ());
this.kingLandingStage = KingLandingStage.IMPACT;
this.kingLandingStageTicks = LANDING_IMPACT_TICKS;
this.triggerAnim("body_action", "stomp");
this.playRoyalSound(AntarchySoundEvents.KING_ROAR.get(), 0.68F);
if (this.level() instanceof ServerLevel level) {
this.performStomp(level, target);
}
}
return;
}
if (!this.isRoyalFlying()) {
case IMPACT -> {
this.getNavigation().stop();
this.setDeltaMovement(Vec3.ZERO);
if (--this.kingLandingStageTicks <= 0) {
this.kingLandingStage = KingLandingStage.GROUND_COMBAT;
this.kingLandingStageTicks = closeQuarters
? Math.max(GROUND_ASSAULT_TICKS, this.activeDecreeTicks + 20)
: GROUND_ASSAULT_TICKS;
this.kingLandingWaypoint = null;
}
return;
}
case GROUND_COMBAT -> {
if (closeQuarters) {
this.groundAssaultTicks = Math.max(this.groundAssaultTicks, this.activeDecreeTicks + 20);
this.kingLandingStageTicks = Math.max(this.kingLandingStageTicks, this.activeDecreeTicks + 20);
}
this.getMoveControl().setWantedPosition(target.getX(), target.getY(), target.getZ(), 1.25D);
if (--this.groundAssaultTicks <= 0 && !closeQuarters) {
this.setRoyalFlying(true);
this.setDeltaMovement(this.getDeltaMovement().add(0.0D, 0.42D, 0.0D));
this.hasImpulse = true;
if (--this.kingLandingStageTicks <= 0 && !closeQuarters) {
this.beginKingTakeoff(target);
}
return;
}
case TAKEOFF -> {
if (--this.kingLandingStageTicks <= 0) {
this.kingLandingStage = KingLandingStage.AERIAL;
this.groundAssaultCooldownTicks = GROUND_ASSAULT_COOLDOWN_MIN
+ this.random.nextInt(GROUND_ASSAULT_COOLDOWN_VARIANCE + 1);
this.kingCombatWaypoint = null;
}
return;
}
case AERIAL -> {
}
}
if (this.kingCombatWaypoint != null && ++this.kingCombatProgressTicks >= 40) {
if (this.kingCombatProgressPosition != null
@@ -700,13 +803,46 @@ public class KingEntity extends RoyalBossEntity {
private void beginKingLanding(LivingEntity target) {
this.kingLandingWaypoint = this.findKingLandingPoint(target);
if (this.kingLandingWaypoint != null) {
this.kingLandingForCombat = true;
this.kingLandingStage = KingLandingStage.TELEGRAPH;
this.kingLandingStageTicks = LANDING_TELEGRAPH_TICKS;
this.kingCombatWaypoint = null;
this.playRoyalSound(AntarchySoundEvents.KING_ROAR.get(), 1.12F);
} else {
this.groundAssaultCooldownTicks = 100;
}
}
private void beginKingTakeoff(LivingEntity target) {
this.kingLandingStage = KingLandingStage.TAKEOFF;
this.kingLandingStageTicks = TAKEOFF_TICKS;
this.triggerAnim("wing_action", "wing_gust");
this.playRoyalSound(AntarchySoundEvents.KING_WING_FLAP.get(), 0.92F);
if (this.level() instanceof ServerLevel level) {
this.performWingGust(level, target);
}
this.setRoyalFlying(true);
this.setDeltaMovement(this.getDeltaMovement().add(0.0D, 0.42D, 0.0D));
this.hasImpulse = true;
}
private void tickKingLandingIndicator() {
if (!(this.level() instanceof ServerLevel level) || this.kingLandingWaypoint == null
|| this.tickCount % 2 != 0) {
return;
}
double progress = this.kingLandingStage == KingLandingStage.TELEGRAPH
? 1.0D - this.kingLandingStageTicks / (double) LANDING_TELEGRAPH_TICKS : 1.0D;
double radius = Mth.lerp(progress, 4.0D, STOMP_RADIUS);
for (int index = 0; index < 36; index++) {
double angle = Mth.TWO_PI * index / 36.0D;
level.sendParticles(ParticleTypes.ENCHANT,
this.kingLandingWaypoint.x + Math.cos(angle) * radius,
this.kingLandingWaypoint.y + 0.2D,
this.kingLandingWaypoint.z + Math.sin(angle) * radius,
1, 0.0D, 0.02D, 0.0D, 0.0D);
}
}
@Nullable
private Vec3 findKingLandingPoint(LivingEntity target) {
Vec3 away = this.position().subtract(target.position()).multiply(1.0D, 0.0D, 1.0D);
@@ -765,36 +901,39 @@ public class KingEntity extends RoyalBossEntity {
}
private void resetKingCombatMovement() {
this.kingLandingForCombat = false;
this.kingLandingStage = KingLandingStage.AERIAL;
this.kingLandingStageTicks = 0;
this.kingLandingWaypoint = null;
this.kingCombatWaypoint = null;
this.kingCombatProgressPosition = null;
this.kingCombatWaypointTicks = 0;
this.kingCombatProgressTicks = 0;
this.groundAssaultTicks = 0;
if (this.groundAssaultCooldownTicks <= 0) {
this.groundAssaultCooldownTicks = 400;
}
}
public void requestCloseQuartersAssault(LivingEntity target) {
if (target != null && target.isAlive() && this.isRoyalFlying() && !this.kingLandingForCombat) {
if (target != null && target.isAlive() && this.isRoyalFlying()
&& this.kingLandingStage == KingLandingStage.AERIAL) {
this.beginKingLanding(target);
}
}
public RoyalDecree.Evaluation evaluateCloseQuarters(LivingEntity target) {
UUID targetId = target.getUUID();
double distanceSquared = target.distanceToSqr(this);
if (distanceSquared <= CLOSE_QUARTERS_RADIUS * CLOSE_QUARTERS_RADIUS) {
this.closeQuartersEntered = true;
this.closeQuartersOutsideTicks = 0;
this.closeQuartersEntered.add(targetId);
this.closeQuartersOutsideTicks.remove(targetId);
return RoyalDecree.Evaluation.COMPLIANT;
}
if (!this.closeQuartersEntered) {
if (!this.closeQuartersEntered.contains(targetId)) {
return 300 - this.activeDecreeTicks >= CLOSE_QUARTERS_APPROACH_TICKS
? RoyalDecree.Evaluation.VIOLATED : RoyalDecree.Evaluation.COMPLIANT;
}
return ++this.closeQuartersOutsideTicks > CLOSE_QUARTERS_EXIT_GRACE_TICKS
int outsideTicks = this.closeQuartersOutsideTicks.merge(targetId, 1, Integer::sum);
return outsideTicks > CLOSE_QUARTERS_EXIT_GRACE_TICKS
? RoyalDecree.Evaluation.VIOLATED : RoyalDecree.Evaluation.COMPLIANT;
}
@@ -1009,10 +1148,11 @@ public class KingEntity extends RoyalBossEntity {
this.activeDecree = decree;
this.activeDecreeTicks = 300;
this.decreeCooldownTicks = 0;
this.closeQuartersEntered = false;
this.closeQuartersOutsideTicks = 0;
this.closeQuartersEntered.clear();
this.closeQuartersOutsideTicks.clear();
this.decreeRecipients.clear();
this.projectRoyalSound(AntarchySoundEvents.KING_DECREE.get(), 4.0F, 1.0F, target);
this.sendDecreeTitle(target);
this.broadcastActiveDecree((ServerLevel) this.level());
return true;
}
}
@@ -1342,7 +1482,8 @@ public class KingEntity extends RoyalBossEntity {
if (this.isRoyalRecoveryActive() && this.activeDecree == null) {
return;
}
if (!(target instanceof ServerPlayer player) || !target.isAlive() || target.level() != level) {
List<ServerPlayer> participants = this.royalEncounterPlayers(level);
if (participants.isEmpty()) {
if (this.activeDecree != null) {
this.endDecree(target);
}
@@ -1355,28 +1496,49 @@ public class KingEntity extends RoyalBossEntity {
if (this.decreeCooldownTicks-- > 0) {
return;
}
int decreeCooldown = this.decreeCooldownForPhase();
if (!this.beginRoyalAttack("decree", RoyalAttackLane.DECREE,
AntarchySettings.royalDecreeCooldownTicks(), 300)) {
decreeCooldown, 300)) {
return;
}
this.activeDecreeTicks = 300;
this.activeDecree = this.pickDecree(target);
this.decreeRetreatPressure = false;
this.closeQuartersEntered = false;
this.closeQuartersOutsideTicks = 0;
this.closeQuartersEntered.clear();
this.closeQuartersOutsideTicks.clear();
this.decreeRecipients.clear();
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);
this.sendDecreeTitle(player);
this.broadcastActiveDecree(level);
}
int countdown = this.activeDecree.countdownTicks(target);
this.broadcastActiveDecree(level);
boolean hasCompletableParticipants = false;
boolean everyParticipantComplete = true;
for (ServerPlayer participant : participants) {
int countdown = this.activeDecree.countdownTicks(participant);
if (countdown > 0 && countdown <= 100 && this.tickCount % 20 == 0) {
level.playSound(null, target.blockPosition(), SoundEvents.BELL_BLOCK, SoundSource.HOSTILE, 0.8F, 1.0F + countdown / 500.0F);
level.playSound(null, participant.blockPosition(), SoundEvents.BELL_BLOCK,
SoundSource.HOSTILE, 0.8F, 1.0F + countdown / 500.0F);
}
this.activeDecree.apply(level, this, target);
if (this.activeDecree != null
&& this.activeDecree.evaluate(level, this, target) == RoyalDecree.Evaluation.VIOLATED) {
this.failActiveDecree(target);
this.activeDecree.apply(level, this, participant);
if (this.activeDecree == null) {
return;
}
RoyalDecree.Evaluation evaluation = this.activeDecree.evaluate(level, this, participant);
if (evaluation == RoyalDecree.Evaluation.VIOLATED) {
this.failActiveDecree(participant);
return;
}
if (evaluation == RoyalDecree.Evaluation.COMPLETE) {
hasCompletableParticipants = true;
} else {
everyParticipantComplete = false;
}
}
if (this.activeDecree != null && hasCompletableParticipants && everyParticipantComplete) {
this.projectRoyalSound(AntarchySoundEvents.KING_SUCCESS.get(), 4.0F, 1.0F, target);
this.endDecree(target);
return;
}
if (this.activeDecree != null && --this.activeDecreeTicks <= 0) {
@@ -1420,9 +1582,34 @@ public class KingEntity extends RoyalBossEntity {
.withStyle(style -> style.withColor(ChatFormatting.WHITE))));
}
private void broadcastActiveDecree(ServerLevel level) {
if (this.activeDecree == null) {
return;
}
for (ServerPlayer participant : this.royalEncounterPlayers(level)) {
if (this.decreeRecipients.add(participant.getUUID())) {
this.sendDecreeTitle(participant);
}
}
}
private int decreeCooldownForPhase() {
double scale = switch (this.phase()) {
case ONE -> 1.0D;
case TWO -> 0.82D;
case THREE -> 0.65D;
};
return Math.max(200, Mth.floor(AntarchySettings.royalDecreeCooldownTicks() * scale));
}
private void endDecree(@Nullable LivingEntity target) {
if (target instanceof ServerPlayer player) {
player.connection.send(new ClientboundClearTitlesPacket(false));
if (this.level() instanceof ServerLevel level) {
for (UUID recipientId : this.decreeRecipients) {
Player recipientPlayer = level.getPlayerByUUID(recipientId);
if (recipientPlayer instanceof ServerPlayer recipient) {
recipient.connection.send(new ClientboundClearTitlesPacket(false));
}
}
}
if (this.activeDecree != null) {
this.lastDecreeKey = this.activeDecree.translationKey();
@@ -1431,9 +1618,10 @@ public class KingEntity extends RoyalBossEntity {
this.activeDecree = null;
this.activeDecreeTicks = 0;
this.decreeRetreatPressure = false;
this.closeQuartersEntered = false;
this.closeQuartersOutsideTicks = 0;
this.decreeCooldownTicks = AntarchySettings.royalDecreeCooldownTicks();
this.closeQuartersEntered.clear();
this.closeQuartersOutsideTicks.clear();
this.decreeRecipients.clear();
this.decreeCooldownTicks = this.decreeCooldownForPhase();
this.startRoyalRecovery(40);
}
@@ -1544,6 +1732,9 @@ public class KingEntity extends RoyalBossEntity {
}
}
}
boolean decreeApplies = attacker instanceof ServerPlayer
&& this.decreeRecipients.contains(attacker.getUUID());
if (decreeApplies) {
if (this.activeDecree instanceof CloseQuartersDecree && projectile) {
this.failActiveDecree(attacker);
} else if (this.activeDecree instanceof ComeNoCloserDecree && !projectile) {
@@ -1552,6 +1743,7 @@ public class KingEntity extends RoyalBossEntity {
this.failActiveDecree(attacker);
}
}
}
return super.hurt(source, amount);
}
@@ -17,9 +17,16 @@ import net.minecraft.core.BlockPos;
import net.minecraft.core.particles.DustParticleOptions;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundSetTitleTextPacket;
import net.minecraft.network.protocol.game.ClientboundSetTitlesAnimationPacket;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.ChatFormatting;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.BossEvent;
@@ -68,6 +75,13 @@ public class QueenEntity extends RoyalBossEntity {
private static final int ACCELERATION_DURATION = 140;
private static final int BLACK_HOLE_COOLDOWN = 340;
private static final double QUEEN_BITE_COOLDOWN_MULTIPLIER = 1.5D;
private static final int MOMENTUM_LOCK_TELEGRAPH_TICKS = 40;
private static final int LANDING_TELEGRAPH_TICKS = 30;
private static final int LANDING_IMPACT_TICKS = 18;
private static final int GROUND_COMBAT_TICKS = 140;
private static final int TAKEOFF_TICKS = 30;
private static final int LANDING_COOLDOWN_MIN = 360;
private static final int LANDING_COOLDOWN_VARIANCE = 240;
private int manticoreSummonCooldownTicks;
private int gravityStompCooldownTicks;
@@ -75,6 +89,14 @@ public class QueenEntity extends RoyalBossEntity {
private int crushingGravityCooldownTicks;
private int accelerationCooldownTicks;
private int blackHoleCooldownTicks;
private int momentumLockTelegraphTicks;
private int queenLandingCooldownTicks = 300;
private int queenLandingStageTicks;
private QueenLandingStage queenLandingStage = QueenLandingStage.AERIAL;
@Nullable
private Vec3 queenLandingWaypoint;
@Nullable
private Phase pendingPhaseSignature;
private final int[] dreamFireballCooldownTicks = new int[3];
private int idleWanderCooldownTicks;
private boolean manticoreAnimationPending;
@@ -84,6 +106,15 @@ public class QueenEntity extends RoyalBossEntity {
@Nullable
private BlockPos naturalTrailHome;
private enum QueenLandingStage {
AERIAL,
TELEGRAPH,
DESCENT,
IMPACT,
GROUND_COMBAT,
TAKEOFF
}
public QueenEntity(EntityType<? extends QueenEntity> entityType, Level level) {
super(entityType, level);
}
@@ -107,6 +138,64 @@ public class QueenEntity extends RoyalBossEntity {
return 0.72D;
}
@Override
protected boolean managesOwnCombatLocomotion() {
return true;
}
@Override
protected boolean blocksRoyalAttacksForMovement() {
return this.queenLandingStage == QueenLandingStage.TELEGRAPH
|| this.queenLandingStage == QueenLandingStage.DESCENT
|| this.queenLandingStage == QueenLandingStage.IMPACT
|| this.queenLandingStage == QueenLandingStage.TAKEOFF;
}
@Override
protected void onRoyalPhaseTransitionStarted(Phase previousPhase, Phase nextPhase) {
this.resetQueenLandingSequence();
this.momentumLockTelegraphTicks = 0;
this.pendingPhaseSignature = null;
this.getNavigation().stop();
this.setDeltaMovement(this.getDeltaMovement().scale(0.35D));
this.playRoyalSound(AntarchySoundEvents.QUEEN_ROAR.get(), nextPhase == Phase.THREE ? 0.66F : 0.82F);
if (this.level() instanceof ServerLevel level) {
Component title = Component.translatable(nextPhase == Phase.TWO
? "boss.antarchy.queen.phase_two" : "boss.antarchy.queen.phase_three")
.withStyle(style -> style.withColor(nextPhase == Phase.TWO
? ChatFormatting.DARK_PURPLE : ChatFormatting.RED).withBold(true));
for (ServerPlayer player : this.royalEncounterPlayers(level)) {
player.connection.send(new ClientboundSetTitlesAnimationPacket(5, 45, 10));
player.connection.send(new ClientboundSetTitleTextPacket(title));
}
}
}
@Override
protected void tickRoyalPhaseTransition(Phase nextPhase, int elapsedTicks, int remainingTicks) {
this.getNavigation().stop();
this.setDeltaMovement(this.getDeltaMovement().scale(0.7D));
if (this.level() instanceof ServerLevel level && elapsedTicks % 3 == 0) {
level.sendParticles(nextPhase == Phase.THREE ? ACCEL_DUST : ParticleTypes.REVERSE_PORTAL,
this.getX(), this.getY() + this.getBbHeight() * 0.55D, this.getZ(),
20, this.getBbWidth() * 0.25D, this.getBbHeight() * 0.25D,
this.getBbWidth() * 0.25D, 0.08D);
}
}
@Override
protected void onRoyalPhaseTransitionCompleted(Phase nextPhase) {
if (nextPhase == Phase.TWO) {
this.momentumLockCooldownTicks = 0;
this.pendingPhaseSignature = Phase.TWO;
this.queenLandingCooldownTicks = Math.min(this.queenLandingCooldownTicks, 100);
} else if (nextPhase == Phase.THREE) {
this.accelerationCooldownTicks = 0;
this.startRoyalAcceleration();
this.queenLandingCooldownTicks = 0;
}
}
@Override
protected double biteApproachSpeed() {
return 1.55D;
@@ -239,9 +328,21 @@ public class QueenEntity extends RoyalBossEntity {
if (this.level().isClientSide || this.isDeadOrDying()) {
return;
}
if (this.isRoyalPhaseTransitionActive()) {
this.royalEffects.tick();
return;
}
LivingEntity target = this.getTarget();
if (target == null || !target.isAlive() || !this.canAttack(target)) {
this.resetQueenLandingSequence();
this.tickIdleWander();
} else {
this.tickQueenLandingSequence(target);
}
this.tickMomentumLockTelegraph();
if (this.blocksRoyalAttacksForMovement()) {
this.royalEffects.tick();
return;
}
this.tickManticoreSummon();
this.tickPendingManticoreAnimation();
@@ -251,6 +352,150 @@ public class QueenEntity extends RoyalBossEntity {
this.royalEffects.tick();
}
private void tickQueenLandingSequence(LivingEntity target) {
if (this.queenLandingStage == QueenLandingStage.AERIAL && this.isRoyalFlying()
&& this.queenLandingCooldownTicks-- <= 0 && target.onGround()) {
this.beginQueenLanding(target);
}
switch (this.queenLandingStage) {
case TELEGRAPH -> {
this.getNavigation().stop();
this.setDeltaMovement(this.getDeltaMovement().scale(0.72D));
this.tickQueenLandingIndicator();
if (--this.queenLandingStageTicks <= 0) {
this.queenLandingStage = QueenLandingStage.DESCENT;
this.playRoyalSound(AntarchySoundEvents.QUEEN_FLY_LOOP.get(), 0.72F);
}
}
case DESCENT -> {
Vec3 landing = this.queenLandingWaypoint;
if (landing == null) {
this.resetQueenLandingSequence();
return;
}
this.setRoyalFlying(true);
this.getMoveControl().setWantedPosition(landing.x, landing.y, landing.z, 1.15D);
this.tickQueenLandingIndicator();
boolean reachedLanding = this.onGround() || this.position().distanceToSqr(landing) <= 2.25D;
if (reachedLanding) {
this.setRoyalFlying(false);
this.queenLandingStage = QueenLandingStage.IMPACT;
this.queenLandingStageTicks = LANDING_IMPACT_TICKS;
this.performGravityStomp((ServerLevel) this.level(), target);
}
}
case IMPACT -> {
this.getNavigation().stop();
this.setDeltaMovement(Vec3.ZERO);
if (--this.queenLandingStageTicks <= 0) {
this.queenLandingStage = QueenLandingStage.GROUND_COMBAT;
this.queenLandingStageTicks = GROUND_COMBAT_TICKS;
this.queenLandingWaypoint = null;
}
}
case GROUND_COMBAT -> {
if (--this.queenLandingStageTicks <= 0) {
this.beginQueenTakeoff();
}
}
case TAKEOFF -> {
if (--this.queenLandingStageTicks <= 0) {
this.queenLandingStage = QueenLandingStage.AERIAL;
this.queenLandingCooldownTicks = LANDING_COOLDOWN_MIN
+ this.random.nextInt(LANDING_COOLDOWN_VARIANCE + 1);
}
}
case AERIAL -> {
}
}
}
private void beginQueenLanding(LivingEntity target) {
this.queenLandingWaypoint = this.findQueenLandingPoint(target);
if (this.queenLandingWaypoint == null) {
this.queenLandingCooldownTicks = 100;
return;
}
this.queenLandingStage = QueenLandingStage.TELEGRAPH;
this.queenLandingStageTicks = LANDING_TELEGRAPH_TICKS;
this.playRoyalSound(AntarchySoundEvents.QUEEN_ROAR.get(), 1.08F);
}
@Nullable
private Vec3 findQueenLandingPoint(LivingEntity target) {
Vec3 away = this.position().subtract(target.position()).multiply(1.0D, 0.0D, 1.0D);
double baseAngle = away.lengthSqr() > 1.0E-4D
? Math.atan2(away.z, away.x) : this.random.nextDouble() * Mth.TWO_PI;
boolean inverted = AntarchyGravityApi.isGravityInverted(this);
double[] radii = {10.0D, 14.0D, 18.0D, 7.0D};
for (int angleIndex = 0; angleIndex < 12; angleIndex++) {
double angle = baseAngle + angleIndex * Mth.TWO_PI / 12.0D;
for (double radius : radii) {
double x = target.getX() + Math.cos(angle) * radius;
double z = target.getZ() + Math.sin(angle) * radius;
double y = inverted
? target.getY() + target.getBbHeight() - this.getBbHeight()
: this.groundYBelow(x, z) + 0.1D;
Vec3 candidate = new Vec3(x, y, z);
AABB box = this.getBoundingBox().move(
candidate.x - this.getX(), candidate.y - this.getY(), candidate.z - this.getZ());
if (this.level().noCollision(this, box)) {
return candidate;
}
}
}
return null;
}
private void tickQueenLandingIndicator() {
if (!(this.level() instanceof ServerLevel level) || this.queenLandingWaypoint == null
|| this.tickCount % 2 != 0) {
return;
}
double progress = this.queenLandingStage == QueenLandingStage.TELEGRAPH
? 1.0D - this.queenLandingStageTicks / (double) LANDING_TELEGRAPH_TICKS : 1.0D;
double radius = Mth.lerp(progress, 4.0D, 18.0D);
double indicatorY = this.queenLandingWaypoint.y
+ (AntarchyGravityApi.isGravityInverted(this) ? this.getBbHeight() : 0.2D);
for (int index = 0; index < 40; index++) {
double angle = Mth.TWO_PI * index / 40.0D;
level.sendParticles(ParticleTypes.REVERSE_PORTAL,
this.queenLandingWaypoint.x + Math.cos(angle) * radius,
indicatorY,
this.queenLandingWaypoint.z + Math.sin(angle) * radius,
1, 0.0D, 0.0D, 0.0D, 0.0D);
}
}
private void beginQueenTakeoff() {
this.queenLandingStage = QueenLandingStage.TAKEOFF;
this.queenLandingStageTicks = TAKEOFF_TICKS;
this.triggerAnim("wing_action", "wing_gust");
this.playRoyalSound(AntarchySoundEvents.QUEEN_FLY_LOOP.get(), 0.9F);
if (this.level() instanceof ServerLevel level) {
level.sendParticles(ParticleTypes.REVERSE_PORTAL,
this.getX(), this.getY() + this.getBbHeight() * 0.4D, this.getZ(),
60, 7.0D, 1.0D, 7.0D, 0.12D);
}
this.setRoyalFlying(true);
double lift = AntarchyGravityApi.isGravityInverted(this) ? -0.42D : 0.42D;
this.setDeltaMovement(this.getDeltaMovement().add(0.0D, lift, 0.0D));
this.hasImpulse = true;
}
private void resetQueenLandingSequence() {
if (this.queenLandingStage != QueenLandingStage.AERIAL) {
this.setRoyalFlying(true);
}
this.queenLandingStage = QueenLandingStage.AERIAL;
this.queenLandingStageTicks = 0;
this.queenLandingWaypoint = null;
if (this.queenLandingCooldownTicks <= 0) {
this.queenLandingCooldownTicks = 100;
}
}
private void tickDreamFireballs() {
if (this.isRoyalRecoveryActive()) return;
if (!(this.level() instanceof ServerLevel level)) return;
@@ -478,7 +723,10 @@ public class QueenEntity extends RoyalBossEntity {
String[] candidates = phase == Phase.ONE && !this.isMomentumLockAvailableEarly()
? new String[] {"gravity_stomp", "black_hole"}
: new String[] {"gravity_stomp", "black_hole", "momentum_lock", "crushing_gravity", "acceleration"};
String selected = this.attackScheduler.chooseWeighted(candidates, id -> {
String selected = this.pendingPhaseSignature == Phase.TWO
&& this.queenAttackReady("momentum_lock", phase, majorBusy, allowOverlap)
? "momentum_lock"
: this.attackScheduler.chooseWeighted(candidates, id -> {
if (!this.queenAttackReady(id, phase, majorBusy, allowOverlap)) return 0;
return switch (id) {
case "black_hole", "crushing_gravity" -> phase == Phase.THREE ? 5 : 3;
@@ -495,7 +743,7 @@ public class QueenEntity extends RoyalBossEntity {
default -> ACCELERATION_COOLDOWN;
};
RoyalAttackLane lane = "black_hole".equals(selected) ? RoyalAttackLane.CENTER_HEAD : RoyalAttackLane.HAZARD;
int actionWindup = 10;
int actionWindup = "momentum_lock".equals(selected) ? MOMENTUM_LOCK_TELEGRAPH_TICKS : 10;
int actionRecovery = "momentum_lock".equals(selected)
? this.animationRecovery(55, actionWindup, 1, 14)
: this.animationRecovery(25, actionWindup, 1, 14);
@@ -506,12 +754,17 @@ public class QueenEntity extends RoyalBossEntity {
QueenEntity.this.triggerAnim("body_action", "stomp");
} else if ("momentum_lock".equals(selected)) {
QueenEntity.this.triggerAnim("wing_action", "wing_gust");
QueenEntity.this.momentumLockTelegraphTicks = MOMENTUM_LOCK_TELEGRAPH_TICKS;
QueenEntity.this.playRoyalSound(AntarchySoundEvents.QUEEN_ROAR.get(), 0.58F);
} 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) {
if ("momentum_lock".equals(selected)) {
QueenEntity.this.momentumLockTelegraphTicks = 0;
}
QueenEntity.this.performSelectedQueenAttack(selected, cooldown, level, target);
}
@Override public void onComplete() {
@@ -520,6 +773,9 @@ public class QueenEntity extends RoyalBossEntity {
}
}
})) return;
if ("momentum_lock".equals(selected)) {
this.pendingPhaseSignature = null;
}
}
private void performSelectedQueenAttack(String selected, int cooldown,
@@ -599,6 +855,51 @@ public class QueenEntity extends RoyalBossEntity {
30, 0.6D, 0.6D, 0.6D, 0.25D);
}
private void tickMomentumLockTelegraph() {
if (this.momentumLockTelegraphTicks <= 0 || !(this.level() instanceof ServerLevel level)) {
return;
}
this.getNavigation().stop();
this.setDeltaMovement(Vec3.ZERO);
int elapsed = MOMENTUM_LOCK_TELEGRAPH_TICKS - this.momentumLockTelegraphTicks;
if (this.tickCount % 2 == 0) {
double inwardRadius = Mth.lerp(elapsed / (double) MOMENTUM_LOCK_TELEGRAPH_TICKS,
MOMENTUM_LOCK_RADIUS, 2.0D);
for (int index = 0; index < 48; index++) {
double angle = Mth.TWO_PI * index / 48.0D;
double cos = Math.cos(angle);
double sin = Math.sin(angle);
level.sendParticles(ParticleTypes.END_ROD,
this.getX() + cos * MOMENTUM_LOCK_RADIUS,
this.getY() + this.getBbHeight() * 0.45D,
this.getZ() + sin * MOMENTUM_LOCK_RADIUS,
1, 0.0D, 0.0D, 0.0D, 0.0D);
if (index % 2 == 0) {
level.sendParticles(ParticleTypes.REVERSE_PORTAL,
this.getX() + cos * inwardRadius,
this.getY() + this.getBbHeight() * 0.45D,
this.getZ() + sin * inwardRadius,
1, 0.0D, 0.0D, 0.0D, 0.0D);
}
}
}
if (this.momentumLockTelegraphTicks <= 10) {
for (LivingEntity living : level.getEntitiesOfClass(LivingEntity.class,
this.getBoundingBox().inflate(MOMENTUM_LOCK_RADIUS), this::canDamageWithRoyalAttack)) {
level.sendParticles(ParticleTypes.END_ROD,
living.getX(), living.getY() + living.getBbHeight() * 0.5D, living.getZ(),
2, 0.25D, 0.35D, 0.25D, 0.0D);
}
}
if (this.momentumLockTelegraphTicks == 40 || this.momentumLockTelegraphTicks == 20
|| this.momentumLockTelegraphTicks == 10 || this.momentumLockTelegraphTicks == 4) {
float pitch = 0.65F + elapsed / (float) MOMENTUM_LOCK_TELEGRAPH_TICKS;
level.playSound(null, this.blockPosition(), SoundEvents.BEACON_POWER_SELECT,
SoundSource.HOSTILE, 2.0F, pitch);
}
this.momentumLockTelegraphTicks--;
}
private void startMomentumLock(ServerLevel level) {
this.momentumLockCooldownTicks = MOMENTUM_LOCK_COOLDOWN;
this.royalEffects.start("momentum_lock", MOMENTUM_LOCK_DURATION, () -> {
@@ -755,8 +1056,11 @@ public class QueenEntity extends RoyalBossEntity {
super.addAdditionalSaveData(tag);
tag.putInt(SUMMON_COOLDOWN_KEY, this.manticoreSummonCooldownTicks);
tag.putInt("GravityStompCooldownTicks", this.gravityStompCooldownTicks);
tag.putInt("MomentumLockCooldownTicks", this.momentumLockCooldownTicks);
tag.putInt("CrushingGravityCooldownTicks", this.crushingGravityCooldownTicks);
tag.putInt("AccelerationCooldownTicks", this.accelerationCooldownTicks);
tag.putInt("BlackHoleCooldownTicks", this.blackHoleCooldownTicks);
tag.putInt("QueenLandingCooldownTicks", this.queenLandingCooldownTicks);
if (this.naturalTrailHome != null) {
tag.putLong("NaturalTrailSiteId", this.naturalTrailSiteId);
tag.putLong("NaturalTrailHome", this.naturalTrailHome.asLong());
@@ -772,6 +1076,8 @@ public class QueenEntity extends RoyalBossEntity {
this.crushingGravityCooldownTicks = Math.max(0, tag.getInt("CrushingGravityCooldownTicks"));
this.accelerationCooldownTicks = Math.max(0, tag.getInt("AccelerationCooldownTicks"));
this.blackHoleCooldownTicks = Math.max(0, tag.getInt("BlackHoleCooldownTicks"));
this.queenLandingCooldownTicks = tag.contains("QueenLandingCooldownTicks")
? Math.max(0, tag.getInt("QueenLandingCooldownTicks")) : 300;
if (tag.contains("NaturalTrailHome")) {
this.naturalTrailSiteId = tag.getLong("NaturalTrailSiteId");
this.naturalTrailHome = BlockPos.of(tag.getLong("NaturalTrailHome"));
@@ -15,6 +15,7 @@ import com.craisinlord.antarchy.content.gravity.AntarchyGravityRotationUtil;
import com.craisinlord.antarchy.content.worldgen.thoraxis.ThoraxisUndersideManager;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
@@ -186,6 +187,10 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
private Vec3 aerialCombatAnchor;
private int aerialCombatAnchorTicks;
private int recoveryWindowTicks;
@Nullable
private Phase trackedPhase;
private int phaseTransitionTicks;
private int phaseTransitionDuration;
private boolean multiplayerScalingInitialized;
private final Set<UUID> encounterParticipants = new HashSet<>();
private double royalDamageMultiplier = 1.0D;
@@ -297,6 +302,24 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
return 1.3D;
}
/** Prevents normal head/body scheduling while a bespoke movement sequence owns the boss. */
protected boolean blocksRoyalAttacksForMovement() {
return false;
}
protected int royalPhaseTransitionDuration(Phase nextPhase) {
return 50;
}
protected void onRoyalPhaseTransitionStarted(Phase previousPhase, Phase nextPhase) {
}
protected void tickRoyalPhaseTransition(Phase nextPhase, int elapsedTicks, int remainingTicks) {
}
protected void onRoyalPhaseTransitionCompleted(Phase nextPhase) {
}
/** Allows a boss to tune its head-bite concurrency without changing the other royal boss. */
protected int maxConcurrentHeadAttacks(Phase phase) {
return phase.maxConcurrentHeadAttacks();
@@ -462,7 +485,8 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
if (this.level().isClientSide) {
return;
}
if (this.isRoyalFlying() && !this.isDeadOrDying() && !this.landingForCombat) {
if (this.isRoyalFlying() && !this.isDeadOrDying() && !this.landingForCombat
&& !this.blocksRoyalAttacksForMovement()) {
this.tickFlyingAltitude();
}
@@ -480,6 +504,23 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
return;
}
Phase currentPhase = this.phase();
if (this.trackedPhase == null) {
this.trackedPhase = currentPhase;
} else if (currentPhase != this.trackedPhase) {
Phase previousPhase = this.trackedPhase;
this.trackedPhase = currentPhase;
this.startRoyalPhaseTransition(previousPhase, currentPhase);
}
if (this.phaseTransitionTicks > 0) {
int elapsedTicks = this.phaseTransitionDuration - this.phaseTransitionTicks;
this.tickRoyalPhaseTransition(this.trackedPhase, elapsedTicks, this.phaseTransitionTicks);
if (--this.phaseTransitionTicks == 0) {
this.onRoyalPhaseTransitionCompleted(this.trackedPhase);
}
return;
}
LivingEntity primaryTarget = this.getTarget();
if (primaryTarget == null || !primaryTarget.isAlive() || !this.canAttack(primaryTarget)) {
for (RoyalHead head : this.heads) {
@@ -491,6 +532,9 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
this.initializeMultiplayerScaling(primaryTarget);
this.tickCombatLocomotionMode(primaryTarget);
if (this.blocksRoyalAttacksForMovement()) {
return;
}
boolean aeriallyStabilized = this.steerTowardTarget(primaryTarget);
if (!aeriallyStabilized) {
this.getLookControl().setLookAt(primaryTarget, 30.0F, 30.0F);
@@ -642,6 +686,37 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
this.recoveryWindowTicks = Math.max(this.recoveryWindowTicks, ticks);
}
protected final boolean isRoyalPhaseTransitionActive() {
return this.phaseTransitionTicks > 0;
}
private void startRoyalPhaseTransition(Phase previousPhase, Phase nextPhase) {
this.phaseTransitionDuration = Math.max(1, this.royalPhaseTransitionDuration(nextPhase));
this.phaseTransitionTicks = this.phaseTransitionDuration;
for (RoyalAttackLane lane : RoyalAttackLane.values()) {
this.attackScheduler.cancel(lane);
}
for (RoyalHead head : this.heads) {
if (head.beamActive() || head.shooting()) {
this.stopRoyalBeam(head);
}
}
this.getNavigation().stop();
this.onRoyalPhaseTransitionStarted(previousPhase, nextPhase);
}
/** Returns the live players currently participating in the encounter. */
protected final List<ServerPlayer> royalEncounterPlayers(ServerLevel level) {
double range = Math.max(32.0D, this.getAttributeValue(Attributes.FOLLOW_RANGE));
LinkedHashSet<ServerPlayer> players = new LinkedHashSet<>(this.bossEvent.getPlayers());
players.addAll(level.getPlayers(player -> player.isAlive()
&& !player.isSpectator()
&& player.distanceToSqr(this) <= range * range));
players.removeIf(player -> !player.isAlive() || player.isSpectator() || player.level() != level
|| player.distanceToSqr(this) > range * range);
return List.copyOf(players);
}
private void initializeMultiplayerScaling(LivingEntity target) {
if (this.multiplayerScalingInitialized || !(this.level() instanceof ServerLevel level)) {
return;
@@ -10,13 +10,25 @@ public final class KneelDecree implements RoyalDecree {
public String translationKey() { return "decree.antarchy.kneel"; }
public void apply(ServerLevel level, KingEntity king, LivingEntity target) {
UUID id = target.getUUID();
if (target.isCrouching()) { deadlines.remove(id); king.clearActiveDecree(target); return; }
int deadline = deadlines.computeIfAbsent(id, ignored -> target.tickCount + 100);
if (target.tickCount >= deadline) { king.failActiveDecree(target); }
deadlines.computeIfAbsent(id, ignored -> target.tickCount + 100);
}
@Override
public Evaluation evaluate(ServerLevel level, KingEntity king, LivingEntity target) {
if (target.isCrouching()) {
return Evaluation.COMPLETE;
}
int deadline = deadlines.computeIfAbsent(target.getUUID(), ignored -> target.tickCount + 100);
return target.tickCount >= deadline ? Evaluation.VIOLATED : Evaluation.COMPLIANT;
}
@Override
public int countdownTicks(LivingEntity target) {
return this.deadlines.getOrDefault(target.getUUID(), target.tickCount + 100) - target.tickCount;
}
@Override
public void onEnded() {
this.deadlines.clear();
}
}
@@ -1245,6 +1245,10 @@
"entity.antarchy.princess": "Princess",
"entity.antarchy.king": "The King",
"entity.antarchy.queen": "The Queen",
"boss.antarchy.king.phase_two": "THE LAW TIGHTENS",
"boss.antarchy.king.phase_three": "ROYAL JUDGMENT",
"boss.antarchy.queen.phase_two": "GRAVITY DOMINION",
"boss.antarchy.queen.phase_three": "TEMPORAL COLLAPSE",
"entity.antarchy.manticore": "Manticore",
"entity.antarchy.royal_black_hole": "Singularity",
"entity.antarchy.royal_bolt": "Royal Bolt",
@@ -1,7 +1,7 @@
{
"sender": "bloxiclean@antmail.com",
"subject": "IS YOUR HIVE QUEEN DUSTY?",
"body": "Bloxiclean now offers premium royal-area cleaning. We remove ash, ichor, bloodglass dust, and other difficult stains. Queen not included. Prices may vary by dimension.",
"subject": "IS YOUR CARPET DIRTY?",
"body": "Bloxiclean now offers premium royal-area cleaning. We remove ash, ichor, bloodglass dust, and other difficult stains. Prices may vary by dimension.",
"trigger": {
"type": "random",
"value": 0.01
@@ -1,7 +1,7 @@
{
"sender": "bloxiclean@antmail.com",
"subject": "BLOXICLEAN: WE MAKE BLOCKS SPARKLE",
"body": "Cobwebs? Sculk? Suspicious residue? Bloxiclean removes nearly anything from nearly anywhere. Book your complimentary pressure-wash before the stains become lore.",
"body": "Cobwebs? Sculk? Suspicious residue? Bloxiclean removes nearly anything from nearly anywhere. Book your complimentary pressure-wash before the stains become ancient history.",
"trigger": {
"type": "random",
"value": 0.01
@@ -1,5 +1,5 @@
{
"sender": "dr_traeoris@antmail.com",
"sender": "noreply@antmail.com",
"subject": "WELCOME TO ANTMAIL",
"body": "Your address has been registered. If this message arrived late, the network probably had ants in it.",
"trigger": {