new icons

royal guardian first pass
This commit is contained in:
craisinlord committed 2026-09-16 23:12:47 -05:00
1 parent 82cea4c293
commit c77d1da53b
158 files changed
+5313 -796

No files matched your search

Binary file not shown.
Whitespace-only changes.
Whitespace-only changes.
@@ -182,6 +182,10 @@ public final class AntarchyConfigCatalog {
|| name.startsWith("american")
|| name.startsWith("mogglesVision")
|| name.equals("ductTapeRepairPercentPerUse")
|| name.startsWith("fallenKingCrown");
|| name.startsWith("fallenKingCrown")
|| name.startsWith("royalArmor")
|| name.startsWith("royalGuardian")
|| name.startsWith("royalAssailant")
|| name.startsWith("royalWeapon");
}
}
@@ -212,6 +212,11 @@ public final class AntarchySettings {
private static int royalGuardianBootsArmorValue = 5;
private static double royalGuardianArmorToughness = 4.0D;
private static double royalGuardianArmorKnockbackResistance = 0.15D;
private static double royalGuardianHelmetJudgmentBonus = 0.06D;
private static double royalGuardianChestplateJudgmentBonus = 0.08D;
private static double royalGuardianLeggingsJudgmentBonus = 0.07D;
private static double royalGuardianBootsJudgmentBonus = 0.05D;
private static int royalGuardianJudgmentDurationTicks = 160;
private static int royalAssailantHelmetArmorValue = 5;
private static int royalAssailantChestplateArmorValue = 10;
private static int royalAssailantLeggingsArmorValue = 8;
@@ -224,6 +229,14 @@ public final class AntarchySettings {
private static double royalAssailantBootsDoubleDamageChance = 0.05D;
private static double royalGuardianSwordAttackDamage = 111.0D;
private static double royalGuardianSwordAttackSpeed = -2.5D;
private static int royalGuardianSwordElementalCooldownTicks = 120;
private static double royalGuardianSwordFirePrimaryDamage = 22.0D;
private static double royalGuardianSwordFireSplashDamage = 14.0D;
private static double royalGuardianSwordFrostSpikeDamage = 9.0D;
private static double royalGuardianSwordStormPrimaryDamage = 16.0D;
private static double royalGuardianSwordStormJumpDamage = 14.0D;
private static int royalGuardianSwordStormMaxJumps = 4;
private static double royalGuardianSwordStormJumpRange = 9.0D;
private static double royalAssailantBattleAxeAttackDamage = 151.0D;
private static double royalAssailantBattleAxeAttackSpeed = -2.9D;
private static double royalWeaponAttackReachBonus = 1.0D;
@@ -231,6 +244,21 @@ public final class AntarchySettings {
private static int royalWeaponDurability = 4608;
private static int royalWeaponEnchantability = 30;
private static int royalGuardianShieldDurability = 1008;
private static double royalGuardianMusterRange = 16.0D;
private static int royalGuardianMusterDurationTicks = 1200;
private static int royalGuardianMusterCooldownTicks = 500;
private static int royalGuardianMusterMobCap = 3;
private static double royalGuardianMusterDamageReduction = 0.40D;
private static double royalGuardianMusterAttackBonus = 0.50D;
private static double royalGuardianMusterSpeedBonus = 0.25D;
private static double royalGuardianMusterPassiveDamageFloor = 3.0D;
private static double royalGuardianBoundaryRadius = 9.0D;
private static int royalGuardianBoundaryDurationTicks = 120;
private static int royalGuardianBoundaryCooldownTicks = 700;
private static int royalGuardianBoundaryStrengthAmplifier = 1;
private static int royalGuardianBoundaryArrestTicks = 20;
private static int royalGuardianBoundaryDropTicks = 8;
private static int royalGuardianBoundaryProjectileCap = 64;
private static double royalBoltDamage = 6.0D;
private static int royalEggHatchChance = 3;
private static double princeHealth = 120.0D;
@@ -1957,6 +1985,17 @@ public final class AntarchySettings {
public static double royalGuardianArmorKnockbackResistance() { return royalGuardianArmorKnockbackResistance; }
public static void setRoyalGuardianArmorKnockbackResistance(double v) { royalGuardianArmorKnockbackResistance = v; }
public static double royalGuardianHelmetJudgmentBonus() { return royalGuardianHelmetJudgmentBonus; }
public static void setRoyalGuardianHelmetJudgmentBonus(double v) { royalGuardianHelmetJudgmentBonus = v; }
public static double royalGuardianChestplateJudgmentBonus() { return royalGuardianChestplateJudgmentBonus; }
public static void setRoyalGuardianChestplateJudgmentBonus(double v) { royalGuardianChestplateJudgmentBonus = v; }
public static double royalGuardianLeggingsJudgmentBonus() { return royalGuardianLeggingsJudgmentBonus; }
public static void setRoyalGuardianLeggingsJudgmentBonus(double v) { royalGuardianLeggingsJudgmentBonus = v; }
public static double royalGuardianBootsJudgmentBonus() { return royalGuardianBootsJudgmentBonus; }
public static void setRoyalGuardianBootsJudgmentBonus(double v) { royalGuardianBootsJudgmentBonus = v; }
public static int royalGuardianJudgmentDurationTicks() { return royalGuardianJudgmentDurationTicks; }
public static void setRoyalGuardianJudgmentDurationTicks(int v) { royalGuardianJudgmentDurationTicks = v; }
public static int royalAssailantHelmetArmorValue() { return royalAssailantHelmetArmorValue; }
public static void setRoyalAssailantHelmetArmorValue(int v) { royalAssailantHelmetArmorValue = v; }
@@ -1993,6 +2032,23 @@ public final class AntarchySettings {
public static double royalGuardianSwordAttackSpeed() { return royalGuardianSwordAttackSpeed; }
public static void setRoyalGuardianSwordAttackSpeed(double v) { royalGuardianSwordAttackSpeed = v; }
public static int royalGuardianSwordElementalCooldownTicks() { return royalGuardianSwordElementalCooldownTicks; }
public static void setRoyalGuardianSwordElementalCooldownTicks(int v) { royalGuardianSwordElementalCooldownTicks = v; }
public static double royalGuardianSwordFirePrimaryDamage() { return royalGuardianSwordFirePrimaryDamage; }
public static void setRoyalGuardianSwordFirePrimaryDamage(double v) { royalGuardianSwordFirePrimaryDamage = v; }
public static double royalGuardianSwordFireSplashDamage() { return royalGuardianSwordFireSplashDamage; }
public static void setRoyalGuardianSwordFireSplashDamage(double v) { royalGuardianSwordFireSplashDamage = v; }
public static double royalGuardianSwordFrostSpikeDamage() { return royalGuardianSwordFrostSpikeDamage; }
public static void setRoyalGuardianSwordFrostSpikeDamage(double v) { royalGuardianSwordFrostSpikeDamage = v; }
public static double royalGuardianSwordStormPrimaryDamage() { return royalGuardianSwordStormPrimaryDamage; }
public static void setRoyalGuardianSwordStormPrimaryDamage(double v) { royalGuardianSwordStormPrimaryDamage = v; }
public static double royalGuardianSwordStormJumpDamage() { return royalGuardianSwordStormJumpDamage; }
public static void setRoyalGuardianSwordStormJumpDamage(double v) { royalGuardianSwordStormJumpDamage = v; }
public static int royalGuardianSwordStormMaxJumps() { return royalGuardianSwordStormMaxJumps; }
public static void setRoyalGuardianSwordStormMaxJumps(int v) { royalGuardianSwordStormMaxJumps = v; }
public static double royalGuardianSwordStormJumpRange() { return royalGuardianSwordStormJumpRange; }
public static void setRoyalGuardianSwordStormJumpRange(double v) { royalGuardianSwordStormJumpRange = v; }
public static double royalAssailantBattleAxeAttackDamage() { return royalAssailantBattleAxeAttackDamage; }
public static void setRoyalAssailantBattleAxeAttackDamage(double v) { royalAssailantBattleAxeAttackDamage = v; }
@@ -2014,6 +2070,37 @@ public final class AntarchySettings {
public static int royalGuardianShieldDurability() { return royalGuardianShieldDurability; }
public static void setRoyalGuardianShieldDurability(int v) { royalGuardianShieldDurability = v; }
public static double royalGuardianMusterRange() { return royalGuardianMusterRange; }
public static void setRoyalGuardianMusterRange(double v) { royalGuardianMusterRange = v; }
public static int royalGuardianMusterDurationTicks() { return royalGuardianMusterDurationTicks; }
public static void setRoyalGuardianMusterDurationTicks(int v) { royalGuardianMusterDurationTicks = v; }
public static int royalGuardianMusterCooldownTicks() { return royalGuardianMusterCooldownTicks; }
public static void setRoyalGuardianMusterCooldownTicks(int v) { royalGuardianMusterCooldownTicks = v; }
public static int royalGuardianMusterMobCap() { return royalGuardianMusterMobCap; }
public static void setRoyalGuardianMusterMobCap(int v) { royalGuardianMusterMobCap = v; }
public static double royalGuardianMusterDamageReduction() { return royalGuardianMusterDamageReduction; }
public static void setRoyalGuardianMusterDamageReduction(double v) { royalGuardianMusterDamageReduction = v; }
public static double royalGuardianMusterAttackBonus() { return royalGuardianMusterAttackBonus; }
public static void setRoyalGuardianMusterAttackBonus(double v) { royalGuardianMusterAttackBonus = v; }
public static double royalGuardianMusterSpeedBonus() { return royalGuardianMusterSpeedBonus; }
public static void setRoyalGuardianMusterSpeedBonus(double v) { royalGuardianMusterSpeedBonus = v; }
public static double royalGuardianMusterPassiveDamageFloor() { return royalGuardianMusterPassiveDamageFloor; }
public static void setRoyalGuardianMusterPassiveDamageFloor(double v) { royalGuardianMusterPassiveDamageFloor = v; }
public static double royalGuardianBoundaryRadius() { return royalGuardianBoundaryRadius; }
public static void setRoyalGuardianBoundaryRadius(double v) { royalGuardianBoundaryRadius = v; }
public static int royalGuardianBoundaryDurationTicks() { return royalGuardianBoundaryDurationTicks; }
public static void setRoyalGuardianBoundaryDurationTicks(int v) { royalGuardianBoundaryDurationTicks = v; }
public static int royalGuardianBoundaryCooldownTicks() { return royalGuardianBoundaryCooldownTicks; }
public static void setRoyalGuardianBoundaryCooldownTicks(int v) { royalGuardianBoundaryCooldownTicks = v; }
public static int royalGuardianBoundaryStrengthAmplifier() { return royalGuardianBoundaryStrengthAmplifier; }
public static void setRoyalGuardianBoundaryStrengthAmplifier(int v) { royalGuardianBoundaryStrengthAmplifier = v; }
public static int royalGuardianBoundaryArrestTicks() { return royalGuardianBoundaryArrestTicks; }
public static void setRoyalGuardianBoundaryArrestTicks(int v) { royalGuardianBoundaryArrestTicks = v; }
public static int royalGuardianBoundaryDropTicks() { return royalGuardianBoundaryDropTicks; }
public static void setRoyalGuardianBoundaryDropTicks(int v) { royalGuardianBoundaryDropTicks = v; }
public static int royalGuardianBoundaryProjectileCap() { return royalGuardianBoundaryProjectileCap; }
public static void setRoyalGuardianBoundaryProjectileCap(int v) { royalGuardianBoundaryProjectileCap = v; }
public static double royalBoltDamage() { return royalBoltDamage; }
public static void setRoyalBoltDamage(double v) { royalBoltDamage = v; }
@@ -329,6 +329,7 @@ public final class AntarchyObjects {
public static Supplier<Holder<MobEffect>> STINKY_EFFECT = (Supplier<Holder<MobEffect>>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Holder<MobEffect>> GLIMMERING_EFFECT = (Supplier<Holder<MobEffect>>) UNBOUND;
public static Supplier<Holder<MobEffect>> ROYAL_VITALITY_PENALTY_EFFECT = (Supplier<Holder<MobEffect>>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Fluid> LUMEN = (Supplier<Fluid>) UNBOUND;
@SuppressWarnings("unchecked")
@@ -823,6 +824,10 @@ public final class AntarchyObjects {
GLIMMERING_EFFECT = supplier;
}
public static void setRoyalVitalityPenaltyEffect(Supplier<Holder<MobEffect>> supplier) {
ROYAL_VITALITY_PENALTY_EFFECT = supplier;
}
public static void setElka(Supplier<EntityType<com.craisinlord.antarchy.content.entity.ElkaEntity>> supplier) {
ELKA = supplier;
}
@@ -0,0 +1,19 @@
package com.craisinlord.antarchy.content.antmail;
import com.craisinlord.antarchy.Antarchy;
/** Opt-in diagnostics for AntMail networking and mailbox state transitions. */
public final class AntmailDebug {
private static final String PROPERTY = "antarchy.antmail.debug";
private AntmailDebug() {
}
public static void log(String message) {
if (Boolean.getBoolean(PROPERTY)) Antarchy.LOGGER.info("[AntMail debug] {}", message);
}
public static void error(String message, Throwable throwable) {
Antarchy.LOGGER.error("[AntMail] {}", message, throwable);
}
}
@@ -13,8 +13,10 @@ import net.minecraft.world.level.saveddata.SavedData;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
public final class AntmailServerData extends SavedData {
@@ -301,16 +303,21 @@ public final class AntmailServerData extends SavedData {
public synchronized void drain(MinecraftServer server, AntmailAddress address) {
AntmailMailbox mailbox = mailboxes.getOrCreate(address);
final boolean[] delivered = {false};
Set<AntmailAddress> deliveredSenders = new LinkedHashSet<>();
pending.removeIf(pendingMessage -> {
if (!pendingMessage.recipient().equals(address) || mailbox.inbox().size() >= AntmailValidation.MAX_MAILBOX_MESSAGES) return false;
if (!mailbox.addIncoming(pendingMessage.message())) return false;
pendingMessage.message().setDeliveryStatus("DELIVERED");
delivered[0] = true;
deliveredSenders.add(pendingMessage.message().sender());
return true;
});
if (delivered[0]) touchMailbox(address);
if (delivered[0]) {
touchMailbox(address);
for (AntmailAddress sender : deliveredSenders) touchMailbox(sender);
setDirty();
}
}
public synchronized AntmailDeliveryResult retry(MinecraftServer server, AntmailAddress sender, UUID messageId) {
AntmailMessage message = mailboxes.getOrCreate(sender).findSent(messageId);
@@ -2,13 +2,19 @@ package com.craisinlord.antarchy.content.antmail;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.TagParser;
import net.minecraft.nbt.NbtAccounter;
import net.minecraft.nbt.NbtIo;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
public final class AntmailWire {
private static final int MAX_COMPRESSED_BYTES = 1024 * 1024;
private static final long MAX_DECOMPRESSED_BYTES = 8L * 1024L * 1024L;
private AntmailWire() {
}
@@ -19,18 +25,30 @@ public final class AntmailWire {
ListTag list = new ListTag();
for (AntmailAttachment attachment : attachments) list.add(attachment.toTag());
root.put("Attachments", list);
String encoded = Base64.getEncoder().encodeToString(root.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8));
String encoded = encodeTag(root);
if (encoded.length() > AntmailValidation.MAX_ATTACHMENT_WIRE_CHARS) throw new IllegalArgumentException("Attachment payload is too large");
return encoded;
}
public static String encodeTag(CompoundTag tag) {
return Base64.getEncoder().encodeToString(tag.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8));
if (tag == null) throw new IllegalArgumentException("Antmail tag cannot be null");
try {
ByteArrayOutputStream output = new ByteArrayOutputStream();
NbtIo.writeCompressed(tag, output);
byte[] compressed = output.toByteArray();
if (compressed.length > MAX_COMPRESSED_BYTES) throw new IllegalArgumentException("Antmail data is too large");
return Base64.getEncoder().encodeToString(compressed);
} catch (IOException exception) {
throw new IllegalArgumentException("Could not encode Antmail data", exception);
}
}
public static CompoundTag decodeTag(String encoded) {
try {
return TagParser.parseTag(new String(Base64.getDecoder().decode(encoded), java.nio.charset.StandardCharsets.UTF_8));
if (encoded == null || encoded.isBlank()) throw new IllegalArgumentException("Antmail data is empty");
byte[] compressed = Base64.getDecoder().decode(encoded);
if (compressed.length > MAX_COMPRESSED_BYTES) throw new IllegalArgumentException("Antmail data is too large");
return NbtIo.readCompressed(new ByteArrayInputStream(compressed), NbtAccounter.create(MAX_DECOMPRESSED_BYTES));
} catch (Exception exception) {
throw new IllegalArgumentException("Invalid Antmail data", exception);
}
@@ -40,8 +58,7 @@ public final class AntmailWire {
if (encoded == null || encoded.isBlank()) return List.of();
if (encoded.length() > AntmailValidation.MAX_ATTACHMENT_WIRE_CHARS) throw new IllegalArgumentException("Attachment payload is too large");
try {
String snbt = new String(Base64.getDecoder().decode(encoded), java.nio.charset.StandardCharsets.UTF_8);
CompoundTag root = TagParser.parseTag(snbt);
CompoundTag root = decodeTag(encoded);
ListTag list = root.getList("Attachments", 10);
List<AntmailAttachment> result = new ArrayList<>();
for (int i = 0; i < list.size(); i++) result.add(AntmailAttachment.fromTag(list.getCompound(i)));
@@ -49,6 +49,10 @@ public final class QueenTrailSpawnMarkerBlockEntity extends BlockEntity {
return this.configured;
}
public boolean isConsumed() {
return this.consumed;
}
public static void serverTick(ServerLevel level, QueenTrailSpawnMarkerBlockEntity marker) {
if (!marker.configured || marker.consumed || level.getDifficulty() == Difficulty.PEACEFUL) {
return;
@@ -88,6 +92,22 @@ public final class QueenTrailSpawnMarkerBlockEntity extends BlockEntity {
}
}
QueenEntity queen = QueenEntity.spawnFromUndersideTrail(level, this.spawnPos, this.homePos, this.siteId, this.yaw);
if (queen == null) {
for (int radius = 2; radius <= 10 && queen == null; radius += 2) {
for (int dx = -radius; dx <= radius && queen == null; dx += radius) {
for (int dz = -radius; dz <= radius && queen == null; dz += radius) {
BlockPos candidate = this.spawnPos.offset(dx, 0, dz);
if (candidate.equals(this.spawnPos)) {
continue;
}
queen = QueenEntity.spawnFromUndersideTrail(level, candidate, this.homePos, this.siteId, this.yaw);
if (queen != null) {
this.spawnPos = candidate.immutable();
}
}
}
}
}
if (queen != null) {
this.consumed = true;
this.queenUuid = queen.getUUID();
@@ -1,6 +1,7 @@
package com.craisinlord.antarchy.content.client;
import com.craisinlord.antarchy.content.network.AntmailResultPayload;
import com.craisinlord.antarchy.content.antmail.AntmailDebug;
import com.craisinlord.antarchy.content.antmail.AntmailMessage;
import net.minecraft.core.BlockPos;
@@ -18,9 +19,19 @@ public final class AntmailClientState {
}
public static void update(AntmailResultPayload payload) {
AntmailDebug.log("S2C result pos=" + payload.pos() + " status=" + payload.status() + " detail=" + payload.detail()
+ " addressPresent=" + !payload.address().isBlank() + " dataChars=" + payload.data().length()
+ " version=" + payload.version());
RESULTS.put(payload.pos(), payload);
if ("unchanged".equals(payload.detail())) {
if (MAILBOXES.containsKey(payload.pos())) {
VERSIONS.put(payload.pos(), payload.version());
} else {
// An unchanged response cannot initialize a client that has no cached snapshot.
// Keep the next poll unconditional instead of getting stuck on "unchanged".
AntmailDebug.log("received unchanged without cached mailbox at " + payload.pos() + "; forcing full snapshot on next poll");
VERSIONS.remove(payload.pos());
}
return;
} else if ("message_detail".equals(payload.detail()) && !payload.data().isBlank()) {
try {
@@ -13,6 +13,13 @@ public final class ComputerAccessClientState {
}
public static void update(ComputerAccessResultPayload result) {
ComputerStructureLocatorClientState.update(result);
if (result.data().startsWith(com.craisinlord.antarchy.content.network.ComputerAccessPayload.ARCHIVE_STATE + "\0")) {
String[] envelope = result.data().split("\u0000", 3);
if (envelope.length == 3) {
com.craisinlord.antarchy.content.guide.ComputerGuideData.applyNetworkSnapshot(envelope[2]);
}
}
if (result.data().startsWith("18\u0000") || result.data().startsWith("19\u0000")) {
BlockleClientState.update(result);
}
@@ -25,6 +25,16 @@ public final class ComputerFileSystemClientState {
} catch (NumberFormatException ignored) {
return;
}
// Terminal, desktop, and game responses use the same payload envelope,
// but their status must not leak into the file explorer's shared state.
if (action != ComputerAccessPayload.FILE_LIST
&& action != ComputerAccessPayload.FILE_OPEN
&& action != ComputerAccessPayload.FILE_CREATE
&& action != ComputerAccessPayload.FILE_SAVE
&& action != ComputerAccessPayload.FILE_DELETE
&& action != ComputerAccessPayload.FILE_MOVE) {
return;
}
State state = STATES.computeIfAbsent(payload.pos(), ignored -> new State());
state.success = payload.result() == ComputerAccessResultPayload.SUCCESS;
state.error = envelope[1];
@@ -0,0 +1,43 @@
package com.craisinlord.antarchy.content.client;
import com.craisinlord.antarchy.content.network.ComputerAccessPayload;
import com.craisinlord.antarchy.content.network.ComputerAccessResultPayload;
import net.minecraft.core.BlockPos;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public final class ComputerStructureLocatorClientState {
private static final Map<BlockPos, String> RESULTS = new ConcurrentHashMap<>();
private ComputerStructureLocatorClientState() {
}
public static void searching(BlockPos pos, String dimensionId) {
String dimension = dimensionId == null || dimensionId.isBlank() ? "TARGET DIMENSION" : dimensionId.toUpperCase(java.util.Locale.ROOT);
RESULTS.put(pos, "SEARCHING IN " + dimension + "...");
}
public static void update(ComputerAccessResultPayload payload) {
String[] envelope = payload.data().split("\u0000", 3);
if (envelope.length != 3) return;
try {
if (Integer.parseInt(envelope[0]) != ComputerAccessPayload.LOCATE_STRUCTURE) return;
} catch (NumberFormatException ignored) {
return;
}
String message = switch (envelope[1]) {
case "not_found" -> "NO STRUCTURE FOUND WITHIN RANGE";
case "dimension_unavailable" -> "TARGET DIMENSION UNAVAILABLE";
default -> "LOCATOR UNAVAILABLE";
};
RESULTS.put(payload.pos(), payload.result() == ComputerAccessResultPayload.SUCCESS
? "STRUCTURE START // " + envelope[2]
: message);
}
public static String get(BlockPos pos) {
return RESULTS.getOrDefault(pos, "COORDINATES // SEARCH WHEN SELECTED");
}
}
@@ -0,0 +1,36 @@
package com.craisinlord.antarchy.content.client;
public final class KingJudgmentFlashClientState {
private static int flashTicks;
private static int flashDuration;
private KingJudgmentFlashClientState() {
}
public static void trigger(int ticks) {
int duration = Math.max(0, ticks);
if (duration >= flashTicks) {
flashTicks = duration;
flashDuration = duration;
}
}
public static void tick() {
if (flashTicks > 0) {
flashTicks--;
}
}
public static float alpha() {
if (flashTicks <= 0 || flashDuration <= 0) {
return 0.0F;
}
float remaining = flashTicks / (float) flashDuration;
return Math.min(0.92F, 0.92F * remaining * remaining);
}
public static void clear() {
flashTicks = 0;
flashDuration = 0;
}
}
@@ -0,0 +1,17 @@
package com.craisinlord.antarchy.content.client;
import net.minecraft.client.gui.GuiGraphics;
public final class KingJudgmentFlashHudRenderer {
private KingJudgmentFlashHudRenderer() {
}
public static void render(GuiGraphics guiGraphics) {
int alpha = Math.round(KingJudgmentFlashClientState.alpha() * 255.0F);
if (alpha <= 0) {
return;
}
guiGraphics.fill(0, 0, guiGraphics.guiWidth(), guiGraphics.guiHeight(),
alpha << 24 | 0x00FFFFFF);
}
}
@@ -0,0 +1,32 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.content.effect.JudgmentMarkAccess;
import com.craisinlord.antarchy.content.effect.RoyalEffectHooks;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import org.jetbrains.annotations.Nullable;
import software.bernie.geckolib.animatable.GeoAnimatable;
import software.bernie.geckolib.cache.object.BakedGeoModel;
import software.bernie.geckolib.renderer.GeoRenderer;
import software.bernie.geckolib.renderer.layer.GeoRenderLayer;
public final class RoyalIndicatorGeoLayer<T extends GeoAnimatable> extends GeoRenderLayer<T> {
public RoyalIndicatorGeoLayer(GeoRenderer<T> renderer) {
super(renderer);
}
@Override
public void render(com.mojang.blaze3d.vertex.PoseStack poseStack, T animatable, BakedGeoModel bakedModel,
@Nullable RenderType renderType, MultiBufferSource bufferSource,
@Nullable com.mojang.blaze3d.vertex.VertexConsumer buffer, float partialTick, int packedLight, int packedOverlay) {
if (!(animatable instanceof Entity entity) || !(entity instanceof LivingEntity living)) {
return;
}
RoyalIndicatorRenderer.render(poseStack, bufferSource, living,
RoyalEffectHooks.commandedHolder() != null && living.hasEffect(RoyalEffectHooks.commandedHolder()),
living instanceof JudgmentMarkAccess access && access.antarchy$isJudgmentMarked(),
living.tickCount + partialTick);
}
}
@@ -0,0 +1,24 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.content.effect.JudgmentMarkAccess;
import com.craisinlord.antarchy.content.effect.RoyalEffectHooks;
import net.minecraft.client.model.EntityModel;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.entity.RenderLayerParent;
import net.minecraft.client.renderer.entity.layers.RenderLayer;
import net.minecraft.world.entity.LivingEntity;
public final class RoyalIndicatorLivingLayer<T extends LivingEntity, M extends EntityModel<T>> extends RenderLayer<T, M> {
public RoyalIndicatorLivingLayer(RenderLayerParent<T, M> renderer) {
super(renderer);
}
@Override
public void render(com.mojang.blaze3d.vertex.PoseStack poseStack, MultiBufferSource buffers, int packedLight, T entity,
float limbSwing, float limbSwingAmount, float partialTick, float ageInTicks,
float netHeadYaw, float headPitch) {
RoyalIndicatorRenderer.render(poseStack, buffers, entity,
RoyalEffectHooks.commandedHolder() != null && entity.hasEffect(RoyalEffectHooks.commandedHolder()),
entity instanceof JudgmentMarkAccess access && access.antarchy$isJudgmentMarked(), ageInTicks);
}
}
@@ -0,0 +1,79 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.world.entity.LivingEntity;
public final class RoyalIndicatorRenderer {
private RoyalIndicatorRenderer() {
}
public static void render(PoseStack poseStack, MultiBufferSource buffers, LivingEntity entity,
boolean commanded, boolean judged, float ageInTicks) {
if ((!commanded && !judged) || entity.isInvisible()) {
return;
}
float radius = Math.max(0.28F, Math.min(1.15F, entity.getBbWidth() * 0.58F));
float baseY = entity.getBbHeight() + 0.18F;
poseStack.pushPose();
poseStack.translate(0.0D, baseY, 0.0D);
VertexConsumer lines = buffers.getBuffer(RenderType.lines());
PoseStack.Pose pose = poseStack.last();
float spin = ageInTicks * (judged ? 0.055F : 0.025F);
if (commanded) {
ring(lines, pose, radius, spin, 0.95F, 0.63F, 0.10F, 0.95F, 36);
crownPoints(lines, pose, radius, spin, 1.0F, 0.80F, 0.22F, 0.95F);
}
if (judged) {
float markY = commanded ? -0.13F : 0.0F;
poseStack.translate(0.0D, markY, 0.0D);
pose = poseStack.last();
brackets(lines, pose, radius * 1.18F, -spin * 1.7F);
ring(lines, pose, radius * 0.73F, -spin * 0.7F, 1.0F, 0.91F, 0.48F, 0.85F, 24);
}
poseStack.popPose();
}
private static void ring(VertexConsumer out, PoseStack.Pose pose, float radius, float rotation,
float r, float g, float b, float a, int segments) {
for (int i = 0; i < segments; i++) {
double angleA = (Math.PI * 2.0D * i) / segments + rotation;
double angleB = (Math.PI * 2.0D * (i + 1)) / segments + rotation;
line(out, pose, (float) Math.cos(angleA) * radius, 0.0F, (float) Math.sin(angleA) * radius,
(float) Math.cos(angleB) * radius, 0.0F, (float) Math.sin(angleB) * radius, r, g, b, a);
}
}
private static void crownPoints(VertexConsumer out, PoseStack.Pose pose, float radius, float rotation,
float r, float g, float b, float a) {
for (int i = 0; i < 3; i++) {
double angle = rotation + Math.PI * 2.0D * i / 3.0D;
float x = (float) Math.cos(angle) * radius;
float z = (float) Math.sin(angle) * radius;
line(out, pose, x, 0.0F, z, x * 0.68F, 0.19F, z * 0.68F, r, g, b, a);
}
}
private static void brackets(VertexConsumer out, PoseStack.Pose pose, float radius, float rotation) {
for (int i = 0; i < 3; i++) {
double angle = rotation + Math.PI * 2.0D * i / 3.0D;
float cx = (float) Math.cos(angle) * radius;
float cz = (float) Math.sin(angle) * radius;
float tx = (float) -Math.sin(angle);
float tz = (float) Math.cos(angle);
line(out, pose, cx - tx * 0.13F, 0.0F, cz - tz * 0.13F,
cx + tx * 0.13F, 0.0F, cz + tz * 0.13F, 1.0F, 0.81F, 0.20F, 1.0F);
line(out, pose, cx, 0.0F, cz, cx * 0.82F, 0.0F, cz * 0.82F, 1.0F, 0.91F, 0.52F, 0.9F);
}
}
private static void line(VertexConsumer out, PoseStack.Pose pose,
float x1, float y1, float z1, float x2, float y2, float z2,
float r, float g, float b, float a) {
out.addVertex(pose, x1, y1, z1).setColor(r, g, b, a).setNormal(pose, 0.0F, 1.0F, 0.0F);
out.addVertex(pose, x2, y2, z2).setColor(r, g, b, a).setNormal(pose, 0.0F, 1.0F, 0.0F);
}
}
@@ -69,6 +69,10 @@ public final class QueenLocateCommand {
continue;
}
QueenEntity queen = marker.spawnNow(thoraxis);
if (queen == null && !marker.isConsumed()) {
source.sendFailure(Component.literal("A Queen spawn site was found, but the Queen could not spawn at a clear location. The site can be retried later."));
return 0;
}
BlockPos spawn = marker.getSpawnPos();
if (queen != null) {
spawn = BlockPos.containing(queen.position());
@@ -77,7 +81,8 @@ public final class QueenLocateCommand {
String coordinates = "[" + spawn.getX() + ", " + spawn.getY() + ", " + spawn.getZ() + "]";
String teleportCommand = "/execute in " + THORAXIS.location() + " run tp @s "
+ spawn.getX() + " " + spawn.getY() + " " + spawn.getZ();
source.sendSuccess(() -> Component.literal("Nearest Queen spawn in " + THORAXIS.location() + ": ")
String state = queen == null ? " (already spawned; Queen is not currently loaded)" : "";
source.sendSuccess(() -> Component.literal("Nearest Queen spawn in " + THORAXIS.location() + state + ": ")
.append(Component.literal(coordinates)
.withStyle(Style.EMPTY
.withColor(net.minecraft.ChatFormatting.AQUA)
@@ -1,76 +1,98 @@
package com.craisinlord.antarchy.content.computer.blockle;
import com.craisinlord.antarchy.Antarchy;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.server.packs.resources.SimplePreparableReloadListener;
import net.minecraft.util.profiling.ProfilerFiller;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
public final class BlockleAnswers {
public final class BlockleAnswers extends SimplePreparableReloadListener<List<String>> {
public static final int VERSION = 1;
private static final int SALT = 0x4A17;
private static final List<Answer> ANSWERS = List.of(
new Answer("anvil", id("anvil")),
new Answer("apple", id("apple")),
new Answer("arrow", id("arrow")),
new Answer("bread", id("bread")),
new Answer("brick", id("brick")),
new Answer("chain", id("chain")),
new Answer("chest", id("chest")),
new Answer("clock", id("clock")),
new Answer("cocoa", id("cocoa")),
new Answer("flint", id("flint")),
new Answer("glass", id("glass")),
new Answer("melon", id("melon")),
new Answer("paper", id("paper")),
new Answer("poppy", id("poppy")),
new Answer("sculk", id("sculk")),
new Answer("stone", id("stone")),
new Answer("stick", id("stick")),
new Answer("sugar", id("sugar")),
new Answer("torch", id("torch")),
new Answer("vault", id("vault")),
new Answer("wheat", id("wheat")),
new Answer("allay", id("allay_spawn_egg")),
new Answer("blaze", id("blaze_spawn_egg")),
new Answer("camel", id("camel_spawn_egg")),
new Answer("drown", id("drowned_spawn_egg")),
new Answer("ghast", id("ghast_spawn_egg")),
new Answer("horse", id("horse_spawn_egg")),
new Answer("llama", id("llama_spawn_egg")),
new Answer("panda", id("panda_spawn_egg")),
new Answer("sheep", id("sheep_spawn_egg")),
new Answer("slime", id("slime_spawn_egg")),
new Answer("squid", id("squid_spawn_egg")),
new Answer("stray", id("stray_spawn_egg")),
new Answer("witch", id("witch_spawn_egg")),
new Answer("lever", id("lever")),
new Answer("sword", id("iron_sword")),
new Answer("cheep", antarchy("cheep_spawn_egg")),
new Answer("jerry", antarchy("jerry_spawn_egg")),
new Answer("lucid", antarchy("lucid_spawn_egg")),
new Answer("queen", antarchy("queen_spawn_egg")),
new Answer("ichor", antarchy("ichor")),
new Answer("lotus", antarchy("lotus")),
new Answer("mucus", antarchy("mucus")),
new Answer("peach", antarchy("peach"))
private static final ResourceLocation ANSWERS_FILE = ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "blockle/answers.json");
private static final BlockleAnswers INSTANCE = new BlockleAnswers();
private static final Map<String, ResourceLocation> ANSWER_ITEMS = Map.ofEntries(
Map.entry("anvil", vanilla("anvil")), Map.entry("apple", vanilla("apple")),
Map.entry("arrow", vanilla("arrow")), Map.entry("bread", vanilla("bread")),
Map.entry("brick", vanilla("brick")), Map.entry("chain", vanilla("chain")),
Map.entry("chest", vanilla("chest")), Map.entry("clock", vanilla("clock")),
Map.entry("cocoa", vanilla("cocoa")), Map.entry("flint", vanilla("flint")),
Map.entry("glass", vanilla("glass")), Map.entry("melon", vanilla("melon")),
Map.entry("paper", vanilla("paper")), Map.entry("poppy", vanilla("poppy")),
Map.entry("sculk", vanilla("sculk")), Map.entry("stone", vanilla("stone")),
Map.entry("stick", vanilla("stick")), Map.entry("sugar", vanilla("sugar")),
Map.entry("torch", vanilla("torch")), Map.entry("vault", vanilla("vault")),
Map.entry("wheat", vanilla("wheat")), Map.entry("allay", vanilla("allay_spawn_egg")),
Map.entry("blaze", vanilla("blaze_spawn_egg")), Map.entry("camel", vanilla("camel_spawn_egg")),
Map.entry("drown", vanilla("drowned_spawn_egg")), Map.entry("ghast", vanilla("ghast_spawn_egg")),
Map.entry("horse", vanilla("horse_spawn_egg")), Map.entry("llama", vanilla("llama_spawn_egg")),
Map.entry("panda", vanilla("panda_spawn_egg")), Map.entry("sheep", vanilla("sheep_spawn_egg")),
Map.entry("slime", vanilla("slime_spawn_egg")), Map.entry("squid", vanilla("squid_spawn_egg")),
Map.entry("stray", vanilla("stray_spawn_egg")), Map.entry("witch", vanilla("witch_spawn_egg")),
Map.entry("lever", vanilla("lever")), Map.entry("sword", vanilla("iron_sword")),
Map.entry("cheep", antarchy("cheep_spawn_egg")), Map.entry("jerry", antarchy("jerry_spawn_egg")),
Map.entry("lucid", antarchy("lucid_spawn_egg")), Map.entry("queen", antarchy("queen_spawn_egg")),
Map.entry("ichor", antarchy("ichor")), Map.entry("lotus", antarchy("lotus")),
Map.entry("mucus", antarchy("mucus")), Map.entry("peach", antarchy("peach"))
);
private static volatile List<String> answers = List.of();
private BlockleAnswers() {
}
public static BlockleAnswers instance() {
return INSTANCE;
}
public static Answer answerForDay(long day) {
int index = (int) Math.floorMod(day + SALT, ANSWERS.size());
return ANSWERS.get(index);
List<String> currentAnswers = answers;
if (currentAnswers.isEmpty()) return null;
int index = (int) Math.floorMod(day + SALT, currentAnswers.size());
String word = currentAnswers.get(index);
return new Answer(word, ANSWER_ITEMS.getOrDefault(word, vanilla("book")));
}
public static int size() {
return ANSWERS.size();
return answers.size();
}
@Override
protected List<String> prepare(ResourceManager resourceManager, ProfilerFiller profiler) {
var resource = resourceManager.getResource(ANSWERS_FILE);
if (resource.isEmpty()) return List.of();
try (var reader = resource.get().openAsReader()) {
JsonElement json = JsonParser.parseReader(reader);
if (!json.isJsonArray()) return List.of();
LinkedHashSet<String> validAnswers = new LinkedHashSet<>();
json.getAsJsonArray().forEach(element -> {
if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isString()) {
String word = element.getAsString();
if (word.matches("(?i)[a-z]{5}")) validAnswers.add(word.toLowerCase(java.util.Locale.ROOT));
}
});
return List.copyOf(validAnswers);
} catch (Exception ignored) {
return List.of();
}
}
@Override
protected void apply(List<String> loadedAnswers, ResourceManager resourceManager, ProfilerFiller profiler) {
answers = loadedAnswers;
Antarchy.LOGGER.info("Loaded {} valid Blockle answers", answers.size());
}
public record Answer(String word, ResourceLocation itemId) {
}
private static ResourceLocation id(String path) {
private static ResourceLocation vanilla(String path) {
return ResourceLocation.withDefaultNamespace(path);
}
@@ -1,13 +1,18 @@
package com.craisinlord.antarchy.content.effect;
import com.craisinlord.antarchy.config.AntarchySettings;
import java.util.UUID;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.Mob;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.ai.attributes.AttributeModifier;
public final class CommandedBehavior {
private static final ResourceLocation INVESTITURE_ATTACK = ResourceLocation.fromNamespaceAndPath("antarchy", "royal_investiture_attack");
private static final ResourceLocation INVESTITURE_SPEED = ResourceLocation.fromNamespaceAndPath("antarchy", "royal_investiture_speed");
private CommandedBehavior() {
}
@@ -26,6 +31,7 @@ public final class CommandedBehavior {
clear(mob);
return;
}
syncInvestiture(mob, access.antarchy$isRoyalInvested());
if (mob.tickCount % 10 == 0) {
LivingEntity commandedCandidate = findCommanderTarget(mob, commander);
if (commandedCandidate != null) {
@@ -100,6 +106,11 @@ public final class CommandedBehavior {
}
private static LivingEntity findCommanderTarget(Mob mob, Entity commander) {
if (mob instanceof CommandedEntityAccess access && access.antarchy$isRoyalInvested()
&& commander instanceof net.minecraft.world.entity.player.Player player) {
LivingEntity marked = JudgmentMarkManager.markedTarget(player);
if (isValidTarget(mob, commander, marked)) return marked;
}
LivingEntity target = commander instanceof Mob commanderMob ? commanderMob.getTarget() : null;
if (isValidTarget(mob, commander, target)) {
return target;
@@ -130,7 +141,9 @@ public final class CommandedBehavior {
if (mob.tickCount % 20 != 0 || mob.distanceToSqr(target) > 4.0D) {
return;
}
if (target.hurt(mob.damageSources().mobAttack(mob), 1.0F)) {
float damage = mob instanceof CommandedEntityAccess access && access.antarchy$isRoyalInvested()
? (float) AntarchySettings.royalGuardianMusterPassiveDamageFloor() : 1.0F;
if (target.hurt(mob.damageSources().mobAttack(mob), damage)) {
mob.swing(net.minecraft.world.InteractionHand.MAIN_HAND);
}
}
@@ -151,6 +164,28 @@ public final class CommandedBehavior {
mob.setTarget(null);
}
access.antarchy$setCommandedTargetOwned(false);
access.antarchy$setRoyalInvested(false);
syncInvestiture(mob, false);
access.antarchy$setCommanderUuid(null);
}
public static void syncInvestiture(Mob mob, boolean active) {
setModifier(mob, Attributes.ATTACK_DAMAGE, INVESTITURE_ATTACK,
active ? AntarchySettings.royalGuardianMusterAttackBonus() : 0.0D);
setModifier(mob, Attributes.MOVEMENT_SPEED, INVESTITURE_SPEED,
active ? AntarchySettings.royalGuardianMusterSpeedBonus() : 0.0D);
}
private static void setModifier(Mob mob, net.minecraft.core.Holder<net.minecraft.world.entity.ai.attributes.Attribute> attribute,
ResourceLocation id, double amount) {
var instance = mob.getAttribute(attribute);
if (instance == null) return;
if (amount > 0.0D) {
if (!instance.hasModifier(id)) {
instance.addTransientModifier(new AttributeModifier(id, amount, AttributeModifier.Operation.ADD_MULTIPLIED_BASE));
}
} else {
instance.removeModifier(id);
}
}
}
@@ -7,4 +7,6 @@ public interface CommandedEntityAccess {
void antarchy$setCommanderUuid(UUID uuid);
boolean antarchy$isCommandedTargetOwned();
void antarchy$setCommandedTargetOwned(boolean owned);
boolean antarchy$isRoyalInvested();
void antarchy$setRoyalInvested(boolean invested);
}
@@ -0,0 +1,8 @@
package com.craisinlord.antarchy.content.effect;
/** Synchronized, presentation-only state for the short-lived Royal Judgment mark. */
public interface JudgmentMarkAccess {
boolean antarchy$isJudgmentMarked();
void antarchy$setJudgmentMarked(boolean marked);
}
@@ -0,0 +1,140 @@
package com.craisinlord.antarchy.content.effect;
import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.item.RoyalGuardianArmorItem;
import java.util.HashMap;
import java.util.Map;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.WeakHashMap;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
/** Server-owned, player-specific Judgment marks. The visible bit is synchronized by vanilla entity data. */
public final class JudgmentMarkManager {
private static final Map<ServerLevel, MarkBook> MARKS = new WeakHashMap<>();
private JudgmentMarkManager() {
}
/** Returns true when this owner had already marked this target before the current hit. */
public static boolean recordSuccessfulHit(Player owner, LivingEntity target) {
if (!(owner instanceof ServerPlayer serverPlayer) || !(target.level() instanceof ServerLevel level)
|| owner.level() != target.level() || !hasGuardianArmor(owner) || !target.isAlive()) {
return false;
}
MarkBook book = MARKS.computeIfAbsent(level, ignored -> new MarkBook());
UUID ownerId = owner.getUUID();
Mark current = book.byOwner.get(ownerId);
boolean wasMarked = current != null && current.targetId.equals(target.getUUID())
&& current.expiresAt > level.getGameTime();
if (current != null && !current.targetId.equals(target.getUUID())) {
removeOwnerFromTarget(book, ownerId, current.targetId);
refreshVisibleState(level, current.targetId);
}
book.byOwner.put(ownerId, new Mark(target.getUUID(), level.getGameTime()
+ Math.max(1, AntarchySettings.royalGuardianJudgmentDurationTicks())));
book.ownersByTarget.computeIfAbsent(target.getUUID(), ignored -> new HashSet<>()).add(ownerId);
refreshVisibleState(target);
return wasMarked;
}
public static boolean isMarkedBy(Player owner, LivingEntity target) {
if (!(target.level() instanceof ServerLevel level)) {
return false;
}
MarkBook book = MARKS.get(level);
if (book == null) {
return false;
}
Mark mark = book.byOwner.get(owner.getUUID());
return mark != null && mark.targetId.equals(target.getUUID()) && mark.expiresAt > level.getGameTime()
&& hasGuardianArmor(owner);
}
public static LivingEntity markedTarget(Player owner) {
if (!(owner instanceof ServerPlayer serverPlayer) || !(owner.level() instanceof ServerLevel level)) return null;
MarkBook book = MARKS.get(level);
Mark mark = book == null ? null : book.byOwner.get(owner.getUUID());
if (mark == null || mark.expiresAt <= level.getGameTime() || !hasGuardianArmor(owner)) return null;
net.minecraft.world.entity.Entity entity = level.getEntity(mark.targetId);
return entity instanceof LivingEntity living && living.isAlive() ? living : null;
}
/** Prunes marks from this target and updates the vanilla-synchronized render bit. */
public static void tickTarget(LivingEntity target) {
if (!(target.level() instanceof ServerLevel level)) {
return;
}
MarkBook book = MARKS.get(level);
if (book == null || book.byOwner.isEmpty()) {
return;
}
long now = level.getGameTime();
UUID targetId = target.getUUID();
Set<UUID> ownerIds = book.ownersByTarget.get(targetId);
if (ownerIds == null || ownerIds.isEmpty()) {
if (target instanceof JudgmentMarkAccess access) access.antarchy$setJudgmentMarked(false);
return;
}
for (UUID ownerId : Set.copyOf(ownerIds)) {
Mark mark = book.byOwner.get(ownerId);
ServerPlayer owner = level.getServer().getPlayerList().getPlayer(ownerId);
if (mark == null || mark.expiresAt <= now || owner == null || owner.level() != level || !hasGuardianArmor(owner)) {
book.byOwner.remove(ownerId);
ownerIds.remove(ownerId);
}
}
if (ownerIds.isEmpty()) book.ownersByTarget.remove(targetId);
boolean active = !ownerIds.isEmpty();
if (target instanceof JudgmentMarkAccess access) {
access.antarchy$setJudgmentMarked(active);
}
if (book.byOwner.isEmpty()) {
MARKS.remove(level);
}
}
private static void refreshVisibleState(LivingEntity target) {
if (target instanceof JudgmentMarkAccess access) {
access.antarchy$setJudgmentMarked(true);
}
}
private static void refreshVisibleState(ServerLevel level, UUID targetId) {
net.minecraft.world.entity.Entity entity = level.getEntity(targetId);
if (entity instanceof LivingEntity target && target instanceof JudgmentMarkAccess access) {
MarkBook book = MARKS.get(level);
boolean stillMarked = book != null && !book.ownersByTarget.getOrDefault(targetId, Set.of()).isEmpty();
access.antarchy$setJudgmentMarked(stillMarked);
}
}
private static void removeOwnerFromTarget(MarkBook book, UUID ownerId, UUID targetId) {
Set<UUID> owners = book.ownersByTarget.get(targetId);
if (owners != null) {
owners.remove(ownerId);
if (owners.isEmpty()) book.ownersByTarget.remove(targetId);
}
}
private static boolean hasGuardianArmor(Player player) {
return player.getItemBySlot(EquipmentSlot.HEAD).getItem() instanceof RoyalGuardianArmorItem
|| player.getItemBySlot(EquipmentSlot.CHEST).getItem() instanceof RoyalGuardianArmorItem
|| player.getItemBySlot(EquipmentSlot.LEGS).getItem() instanceof RoyalGuardianArmorItem
|| player.getItemBySlot(EquipmentSlot.FEET).getItem() instanceof RoyalGuardianArmorItem;
}
private record Mark(UUID targetId, long expiresAt) {
}
private static final class MarkBook {
private final Map<UUID, Mark> byOwner = new HashMap<>();
private final Map<UUID, Set<UUID>> ownersByTarget = new HashMap<>();
}
}
@@ -0,0 +1,269 @@
package com.craisinlord.antarchy.content.effect;
import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.item.RoyalGuardianArmorItem;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.WeakHashMap;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.core.particles.DustParticleOptions;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.OwnableEntity;
import net.minecraft.world.entity.projectile.Projectile;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import org.joml.Vector3f;
/** Server-authoritative Royal Guardian chestplate field and temporary projectile arrest state. */
public final class RoyalBoundaryManager {
private static final int ALLY_REFRESH_INTERVAL = 10;
private static final DustParticleOptions ROYAL_GOLD = new DustParticleOptions(new Vector3f(1.0F, 0.72F, 0.12F), 1.15F);
private static final Map<ServerLevel, Map<UUID, Boundary>> BOUNDARIES = new WeakHashMap<>();
private static final Map<ServerLevel, Map<UUID, ArrestedProjectile>> ARRESTED = new WeakHashMap<>();
private RoyalBoundaryManager() {
}
public static boolean activate(ServerPlayer owner) {
if (!(owner.level() instanceof ServerLevel level) || !wearsGuardianChestplate(owner)) return false;
long now = level.getGameTime();
Map<UUID, Boundary> boundaries = BOUNDARIES.computeIfAbsent(level, ignored -> new HashMap<>());
net.minecraft.world.item.Item chestplate = owner.getItemBySlot(EquipmentSlot.CHEST).getItem();
if (owner.getCooldowns().isOnCooldown(chestplate) || boundaries.containsKey(owner.getUUID())) {
owner.displayClientMessage(net.minecraft.network.chat.Component.translatable("message.antarchy.royal_boundary.recharging"), true);
return false;
}
double radius = Math.max(0.5D, AntarchySettings.royalGuardianBoundaryRadius());
boundaries.put(owner.getUUID(), new Boundary(now + Math.max(1, AntarchySettings.royalGuardianBoundaryDurationTicks()), radius));
owner.getCooldowns().addCooldown(chestplate, Math.max(0, AntarchySettings.royalGuardianBoundaryCooldownTicks()));
owner.displayClientMessage(net.minecraft.network.chat.Component.translatable("message.antarchy.royal_boundary.activated"), true);
level.playSound(null, owner.blockPosition(), SoundEvents.BEACON_ACTIVATE, SoundSource.PLAYERS, 1.0F, 1.25F);
burst(level, center(owner), radius, 36);
return true;
}
/** Called from ServerPlayer.tick; ends fields immediately when their owner can no longer sustain them. */
public static void tickOwner(ServerPlayer owner) {
if (!(owner.level() instanceof ServerLevel level)) return;
UUID ownerId = owner.getUUID();
for (ServerLevel otherLevel : BOUNDARIES.keySet().toArray(ServerLevel[]::new)) {
if (otherLevel != level) removeBoundary(otherLevel, ownerId);
}
Map<UUID, Boundary> boundaries = BOUNDARIES.get(level);
if (boundaries == null) return;
Boundary boundary = boundaries.get(ownerId);
if (boundary == null) return;
long now = level.getGameTime();
if (!owner.isAlive() || owner.isRemoved() || !wearsGuardianChestplate(owner) || now >= boundary.expiresAt) {
boundaries.remove(ownerId);
if (boundaries.isEmpty()) BOUNDARIES.remove(level);
return;
}
if (owner.tickCount % ALLY_REFRESH_INTERVAL == 0) {
AABB area = owner.getBoundingBox().inflate(boundary.radius);
for (LivingEntity entity : level.getEntitiesOfClass(LivingEntity.class, area,
living -> living.isAlive() && living.distanceToSqr(owner) <= boundary.radius * boundary.radius && isAlly(owner, living))) {
entity.addEffect(new MobEffectInstance(MobEffects.DAMAGE_BOOST, ALLY_REFRESH_INTERVAL + 5,
Math.max(0, AntarchySettings.royalGuardianBoundaryStrengthAmplifier()), true, false, true), owner);
}
}
if (owner.tickCount % 5 == 0) renderBoundary(level, center(owner), boundary.radius);
}
public static void clearOwner(ServerPlayer owner) {
UUID ownerId = owner.getUUID();
for (ServerLevel level : BOUNDARIES.keySet().toArray(ServerLevel[]::new)) removeBoundary(level, ownerId);
}
private static void removeBoundary(ServerLevel level, UUID ownerId) {
Map<UUID, Boundary> boundaries = BOUNDARIES.get(level);
if (boundaries != null) {
boundaries.remove(ownerId);
if (boundaries.isEmpty()) BOUNDARIES.remove(level);
}
}
/** Predicts this tick's movement so a fast projectile is arrested before its own collision pass. */
public static void prepareProjectile(Projectile projectile) {
if (!(projectile.level() instanceof ServerLevel level) || projectile.isRemoved()) return;
Map<UUID, ArrestedProjectile> arrested = ARRESTED.get(level);
if (arrested != null && arrested.containsKey(projectile.getUUID())) {
ArrestedProjectile state = arrested.get(projectile.getUUID());
if (!state.dropping) {
projectile.setDeltaMovement(Vec3.ZERO);
projectile.setNoGravity(true);
}
return;
}
Vec3 start = projectile.position();
Vec3 movement = projectile.getDeltaMovement();
if (movement.lengthSqr() > 1.0E-8D) {
tryCapture(level, projectile, start, start.add(movement));
}
}
/** Swept fallback for unusual projectiles and arrest-state progression. */
public static void tickProjectile(Projectile projectile) {
if (!(projectile.level() instanceof ServerLevel level)) return;
Map<UUID, ArrestedProjectile> arrested = ARRESTED.get(level);
ArrestedProjectile state = arrested == null ? null : arrested.get(projectile.getUUID());
if (state != null) {
tickArrested(level, projectile, state);
return;
}
tryCapture(level, projectile, new Vec3(projectile.xo, projectile.yo, projectile.zo), projectile.position());
}
private static void tryCapture(ServerLevel level, Projectile projectile, Vec3 start, Vec3 end) {
Map<UUID, Boundary> boundaries = BOUNDARIES.get(level);
if (boundaries == null || boundaries.isEmpty() || projectile.isRemoved()) return;
var boundaryIterator = boundaries.entrySet().iterator();
while (boundaryIterator.hasNext()) {
Map.Entry<UUID, Boundary> entry = boundaryIterator.next();
ServerPlayer owner = level.getServer().getPlayerList().getPlayer(entry.getKey());
if (owner == null || owner.level() != level || !owner.isAlive() || !wearsGuardianChestplate(owner)
|| level.getGameTime() >= entry.getValue().expiresAt) {
boundaryIterator.remove();
continue;
}
Map<UUID, ArrestedProjectile> states = ARRESTED.computeIfAbsent(level, ignored -> new HashMap<>());
long ownedArrests = states.values().stream().filter(arrest -> entry.getKey().equals(arrest.ownerId)).count();
if (ownedArrests >= Math.max(0, AntarchySettings.royalGuardianBoundaryProjectileCap())) continue;
if (isProjectileAlly(owner, projectile)) continue;
Vec3 crossing = inwardIntersection(start, end, center(owner), entry.getValue().radius);
if (crossing == null) continue;
projectile.setPos(crossing.x, crossing.y, crossing.z);
projectile.setDeltaMovement(Vec3.ZERO);
boolean noGravity = projectile.isNoGravity();
projectile.setNoGravity(true);
states.put(projectile.getUUID(), new ArrestedProjectile(entry.getKey(),
level.getGameTime() + Math.max(0, AntarchySettings.royalGuardianBoundaryArrestTicks()), false, 0L, noGravity));
level.playSound(null, crossing.x, crossing.y, crossing.z, SoundEvents.AMETHYST_BLOCK_CHIME, SoundSource.PLAYERS, 0.65F, 1.7F);
level.sendParticles(ROYAL_GOLD, crossing.x, crossing.y, crossing.z, 18, 0.3D, 0.3D, 0.3D, 0.3D);
return;
}
if (boundaries.isEmpty()) BOUNDARIES.remove(level);
}
private static void tickArrested(ServerLevel level, Projectile projectile, ArrestedProjectile state) {
if (projectile.isRemoved()) {
removeArrest(level, projectile.getUUID());
return;
}
long now = level.getGameTime();
if (!state.dropping && now < state.releaseAt) {
projectile.setDeltaMovement(Vec3.ZERO);
projectile.setNoGravity(true);
if (now % 2 == 0) {
level.sendParticles(ROYAL_GOLD, projectile.getX(), projectile.getY(), projectile.getZ(), 3, 0.12D, 0.12D, 0.12D, 0.02D);
}
if (state.releaseAt - now <= 6) projectile.setXRot((float) (90.0D * (1.0D - (state.releaseAt - now) / 6.0D)));
return;
}
if (!state.dropping) {
state.dropping = true;
state.dropUntil = now + Math.max(0, AntarchySettings.royalGuardianBoundaryDropTicks());
projectile.setNoGravity(false);
projectile.setXRot(90.0F);
}
if (now < state.dropUntil) {
projectile.setDeltaMovement(0.0D, -0.45D, 0.0D);
projectile.setNoGravity(false);
} else {
projectile.setNoGravity(state.wasNoGravity);
removeArrest(level, projectile.getUUID());
}
}
private static void removeArrest(ServerLevel level, UUID projectileId) {
Map<UUID, ArrestedProjectile> states = ARRESTED.get(level);
if (states != null) {
states.remove(projectileId);
if (states.isEmpty()) ARRESTED.remove(level);
}
}
private static Vec3 inwardIntersection(Vec3 start, Vec3 end, Vec3 center, double radius) {
Vec3 offset = start.subtract(center);
if (offset.lengthSqr() <= radius * radius) return null;
Vec3 segment = end.subtract(start);
double a = segment.lengthSqr();
if (a < 1.0E-8D) return null;
double b = 2.0D * offset.dot(segment);
double c = offset.lengthSqr() - radius * radius;
double discriminant = b * b - 4.0D * a * c;
if (discriminant < 0.0D) return null;
double t = (-b - Math.sqrt(discriminant)) / (2.0D * a);
return t >= 0.0D && t <= 1.0D ? start.add(segment.scale(t)) : null;
}
private static boolean isProjectileAlly(ServerPlayer owner, Projectile projectile) {
Entity shooter = projectile.getOwner();
return shooter == owner || (shooter != null && (shooter.isAlliedTo(owner) || owner.isAlliedTo(shooter)
|| (shooter instanceof CommandedEntityAccess access && owner.getUUID().equals(access.antarchy$getCommanderUuid()))));
}
private static boolean isAlly(ServerPlayer owner, LivingEntity entity) {
if (entity == owner || entity.isAlliedTo(owner) || owner.isAlliedTo(entity)) return true;
if (entity instanceof OwnableEntity ownable && owner.getUUID().equals(ownable.getOwnerUUID())) return true;
return entity instanceof CommandedEntityAccess access && owner.getUUID().equals(access.antarchy$getCommanderUuid());
}
private static boolean wearsGuardianChestplate(ServerPlayer player) {
return player.getItemBySlot(EquipmentSlot.CHEST).getItem() instanceof RoyalGuardianArmorItem armor
&& armor.getArmorType() == net.minecraft.world.item.ArmorItem.Type.CHESTPLATE;
}
private static Vec3 center(ServerPlayer owner) {
return owner.position().add(0.0D, owner.getBbHeight() * 0.5D, 0.0D);
}
private static void renderBoundary(ServerLevel level, Vec3 center, double radius) {
for (int i = 0; i < 24; i++) {
double angle = (Math.PI * 2.0D * i) / 24.0D;
double x = center.x + Math.cos(angle) * radius;
double z = center.z + Math.sin(angle) * radius;
level.sendParticles(ROYAL_GOLD, x, center.y - 1.0D, z, 1, 0.04D, 0.1D, 0.04D, 0.02D);
if (i % 4 == 0) level.sendParticles(ParticleTypes.END_ROD, x, center.y + 1.2D, z, 1, 0.02D, 0.3D, 0.02D, 0.0D);
}
}
private static void burst(ServerLevel level, Vec3 center, double radius, int count) {
level.sendParticles(ROYAL_GOLD, center.x, center.y, center.z, count, radius * 0.35D, 1.2D, radius * 0.35D, 0.18D);
level.sendParticles(ParticleTypes.END_ROD, center.x, center.y, center.z, count / 2, radius * 0.25D, 0.8D, radius * 0.25D, 0.08D);
}
private static final class Boundary {
private final long expiresAt;
private final double radius;
private Boundary(long expiresAt, double radius) {
this.expiresAt = expiresAt;
this.radius = radius;
}
}
private static final class ArrestedProjectile {
private final UUID ownerId;
private final long releaseAt;
private boolean dropping;
private long dropUntil;
private final boolean wasNoGravity;
private ArrestedProjectile(UUID ownerId, long releaseAt, boolean dropping, long dropUntil, boolean wasNoGravity) {
this.ownerId = ownerId;
this.releaseAt = releaseAt;
this.dropping = dropping;
this.dropUntil = dropUntil;
this.wasNoGravity = wasNoGravity;
}
}
}
@@ -0,0 +1,34 @@
package com.craisinlord.antarchy.content.effect;
import com.craisinlord.antarchy.Antarchy;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.effect.MobEffect;
import net.minecraft.world.effect.MobEffectCategory;
import net.minecraft.world.entity.ai.attributes.AttributeInstance;
import net.minecraft.world.entity.ai.attributes.AttributeMap;
import net.minecraft.world.entity.ai.attributes.AttributeModifier;
import net.minecraft.world.entity.ai.attributes.Attributes;
public final class RoyalVitalityPenaltyEffect extends MobEffect {
public static final ResourceLocation MODIFIER_ID =
ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "royal_vitality_penalty");
public RoyalVitalityPenaltyEffect() {
super(MobEffectCategory.HARMFUL, 0xD8A52B);
}
@Override
public void addAttributeModifiers(AttributeMap attributeMap, int amplifier) {
AttributeInstance maxHealth = attributeMap.getInstance(Attributes.MAX_HEALTH);
if (maxHealth == null) return;
maxHealth.addTransientModifier(new AttributeModifier(
MODIFIER_ID, -0.5D, AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL));
}
@Override
public void removeAttributeModifiers(AttributeMap attributeMap) {
AttributeInstance maxHealth = attributeMap.getInstance(Attributes.MAX_HEALTH);
if (maxHealth == null) return;
maxHealth.removeModifier(MODIFIER_ID);
}
}
@@ -113,6 +113,9 @@ public final class AntTeleportHelper {
}
Vec3 destinationPos = getDestinationPosition(player, destination);
if (destinationPos == null) {
return failArrival(player);
}
teleportPlayerWithCompanions(player, destination, destinationPos);
player.playSound(SoundEvents.ENDERMAN_TELEPORT, 1.0F, 1.0F);
return InteractionResult.CONSUME;
@@ -121,6 +124,9 @@ public final class AntTeleportHelper {
public static InteractionResult teleportPlayerToReturnDestination(ServerPlayer player) {
ServerLevel destination = resolveReturnDestinationLevel(player);
Vec3 destinationPos = getDestinationPosition(player, destination);
if (destinationPos == null) {
return failArrival(player);
}
teleportPlayerWithCompanions(player, destination, destinationPos);
player.playSound(SoundEvents.ENDERMAN_TELEPORT, 1.0F, 1.0F);
return InteractionResult.CONSUME;
@@ -160,6 +166,9 @@ public final class AntTeleportHelper {
}
Vec3 destinationPos = getDestinationPosition(player, destination);
if (destinationPos == null) {
return failArrival(player);
}
teleportPlayerWithCompanions(player, destination, destinationPos);
player.playSound(SoundEvents.ENDERMAN_TELEPORT, 1.0F, 1.0F);
return InteractionResult.CONSUME;
@@ -245,6 +254,7 @@ public final class AntTeleportHelper {
return player.server.overworld();
}
@Nullable
public static Vec3 getDestinationPosition(ServerPlayer player, ServerLevel destination) {
BlockPos respawnPos = player.getRespawnPosition();
BlockPos preferredPos;
@@ -255,13 +265,9 @@ public final class AntTeleportHelper {
}
Vec3 safeArrivalPos = findSafeArrivalPosition(player, destination, preferredPos);
if (safeArrivalPos != null) {
return safeArrivalPos;
}
return createEmergencyArrivalPlatform(destination, findEmergencyPlatformPosition(destination, preferredPos));
}
@Nullable
private static Vec3 findSafeArrivalPosition(ServerPlayer player, ServerLevel destination, BlockPos preferredPos) {
loadArrivalSearchArea(destination, preferredPos);
@@ -310,7 +316,7 @@ public final class AntTeleportHelper {
/**
* Arrival validation needs neighboring chunks for collision checks. Load a
* bounded area up front so unloaded terrain is not mistaken for an unsafe
* destination and sent straight to the emergency-platform path.
* destination and rejected prematurely.
*/
private static void loadArrivalSearchArea(ServerLevel destination, BlockPos preferredPos) {
ChunkPos center = new ChunkPos(preferredPos);
@@ -343,6 +349,7 @@ public final class AntTeleportHelper {
@Nullable
private static Vec3 findSafeArrivalPositionInYRange(ServerPlayer player, ServerLevel destination, BlockPos preferredPos, int minY, int maxY) {
Set<BlockPos> surfaceCandidates = new LinkedHashSet<>();
boolean elythia = destination.dimension() == PermanentPortalType.ELYTHIA.primaryDimension();
for (int radius = 0; radius <= ARRIVAL_SEARCH_RADIUS; radius++) {
for (int xOff = -radius; xOff <= radius; xOff++) {
for (int zOff = -radius; zOff <= radius; zOff++) {
@@ -357,16 +364,23 @@ public final class AntTeleportHelper {
}
BlockPos surface = destination.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, column);
if (elythia) {
surfaceCandidates.add(surface);
surfaceCandidates.add(surface.above());
} else {
addArrivalCandidate(surfaceCandidates, surface);
addArrivalCandidate(surfaceCandidates, destination.getHeightmapPos(Heightmap.Types.WORLD_SURFACE, column));
}
for (int yOffset = 1; yOffset <= ARRIVAL_VERTICAL_SEARCH; yOffset++) {
surfaceCandidates.add(surface.above(yOffset));
if (!elythia) {
surfaceCandidates.add(surface.below(yOffset));
}
}
}
}
}
// Prefer actual generated surfaces. This is especially important in
// Elythia, where the configured spawn height is not necessarily the
@@ -381,8 +395,15 @@ public final class AntTeleportHelper {
}
}
// Elythia arrivals must stay on the generated surface; a vertical scan
// can otherwise select an underground cave floor after a surface miss.
if (elythia) {
return null;
}
// Heightmaps can be stale or unsuitable around structures, so retain a
// bounded exhaustive search as a final generated-terrain attempt.
// bounded exhaustive search as a final generated-terrain attempt in
// dimensions whose configured arrival ranges intentionally allow it.
for (int radius = 0; radius <= ARRIVAL_SEARCH_RADIUS; radius++) {
for (int xOff = -radius; xOff <= radius; xOff++) {
for (int zOff = -radius; zOff <= radius; zOff++) {
@@ -442,6 +463,37 @@ public final class AntTeleportHelper {
}
private static boolean isValidArrivalPosition(ServerLevel destination, Vec3 safePos) {
if (destination.dimension() == PermanentPortalType.ELYTHIA.primaryDimension()) {
BlockPos feet = BlockPos.containing(safePos);
BlockPos supportPos = feet.below();
if (!destination.hasChunkAt(supportPos)) {
return false;
}
BlockState support = destination.getBlockState(supportPos);
if (!support.isFaceSturdy(destination, supportPos, Direction.UP)
|| !support.getFluidState().isEmpty()
|| support.is(net.minecraft.tags.BlockTags.LEAVES)) {
return false;
}
int nearbySupports = 0;
for (Direction direction : Direction.Plane.HORIZONTAL) {
for (int yOffset = -2; yOffset <= 2; yOffset++) {
BlockPos nearby = supportPos.relative(direction).offset(0, yOffset, 0);
BlockState nearbyState = destination.getBlockState(nearby);
if (nearbyState.isFaceSturdy(destination, nearby, Direction.UP)
&& nearbyState.getFluidState().isEmpty()
&& !nearbyState.is(net.minecraft.tags.BlockTags.LEAVES)) {
nearbySupports++;
break;
}
}
}
if (nearbySupports < 2) {
return false;
}
}
if (destination.dimension() != AntarchySettings.termiteDestinationDimension()) {
return true;
}
@@ -469,98 +521,8 @@ public final class AntTeleportHelper {
return true;
}
private static BlockPos getClampedFallbackPosition(ServerLevel destination, BlockPos preferredPos) {
int minY = destination.getMinBuildHeight() + 1;
int maxY = destination.getMaxBuildHeight() - 1;
return new BlockPos(preferredPos.getX(), Mth.clamp(preferredPos.getY(), minY, maxY), preferredPos.getZ());
}
/**
* Keep the emergency platform grounded in generated terrain when possible.
* A configured/shared spawn can be an arbitrary coordinate in a custom
* dimension, so using its Y directly can strand the player in the sky.
*/
private static BlockPos findEmergencyPlatformPosition(ServerLevel destination, BlockPos preferredPos) {
BlockPos best = null;
int bestDistance = Integer.MAX_VALUE;
int minY = destination.getMinBuildHeight() + 1;
int maxY = destination.getMaxBuildHeight() - 2;
for (int radius = 0; radius <= 8; radius++) {
for (int xOffset = -radius; xOffset <= radius; xOffset++) {
for (int zOffset = -radius; zOffset <= radius; zOffset++) {
if (radius > 0 && Math.abs(xOffset) != radius && Math.abs(zOffset) != radius) {
continue;
}
BlockPos searchPos = preferredPos.offset(xOffset, 0, zOffset);
if (!destination.hasChunkAt(searchPos)) {
continue;
}
BlockPos surface = destination.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, searchPos);
if (surface.getY() < minY || surface.getY() > maxY) {
continue;
}
int distance = Math.abs(xOffset) + Math.abs(zOffset);
if (distance < bestDistance) {
best = surface;
bestDistance = distance;
}
}
}
if (best != null) {
break;
}
}
return best != null ? best : getClampedFallbackPosition(destination, preferredPos);
}
/**
* Last-resort landing location for custom dimensions whose generated spawn
* area has no valid player-sized space. The normal search always runs first;
* this only modifies terrain when there is no safe generated location.
*/
private static Vec3 createEmergencyArrivalPlatform(ServerLevel destination, BlockPos preferredPos) {
BlockPos center = getClampedFallbackPosition(destination, preferredPos);
int minY = destination.getMinBuildHeight() + 1;
int maxY = destination.getMaxBuildHeight() - 2;
center = new BlockPos(center.getX(), Mth.clamp(center.getY(), minY, maxY), center.getZ());
destination.getChunkAt(center);
BlockState platformState = emergencyPlatformState(destination);
for (int xOffset = -2; xOffset <= 2; xOffset++) {
for (int zOffset = -2; zOffset <= 2; zOffset++) {
BlockPos column = center.offset(xOffset, 0, zOffset);
destination.setBlock(column.below(), platformState, Block.UPDATE_ALL);
for (int yOffset = 0; yOffset <= 3; yOffset++) {
destination.removeBlock(column.above(yOffset), false);
}
}
}
return Vec3.atBottomCenterOf(center);
}
private static BlockState emergencyPlatformState(ServerLevel destination) {
if (destination.dimension() == PermanentPortalType.ELYTHIA.primaryDimension()) {
return PermanentPortalType.ELYTHIA.platformBlock().defaultBlockState();
}
if (destination.dimension() == PermanentPortalType.THORAXIS.primaryDimension()) {
return PermanentPortalType.THORAXIS.platformBlock().defaultBlockState();
}
if (destination.dimension() == PermanentPortalType.CAVARYN.primaryDimension()) {
return PermanentPortalType.CAVARYN.platformBlock().defaultBlockState();
}
if (destination.dimension() == Level.NETHER) {
return Blocks.NETHERRACK.defaultBlockState();
}
if (destination.dimension() == Level.END) {
return Blocks.END_STONE.defaultBlockState();
}
return Blocks.DIRT.defaultBlockState();
private static InteractionResult failArrival(ServerPlayer player) {
player.displayClientMessage(Component.translatable("message.antarchy.teleport_arrival_failed"), true);
return InteractionResult.CONSUME;
}
}
@@ -1323,6 +1323,7 @@ public abstract class BaseAntEntity extends Animal implements GeoEntity {
return AntTeleportHelper.resolveReturnDestinationLevel(player);
}
@Nullable
protected Vec3 getDestinationPosition(ServerLevel destination, ServerPlayer player) {
return AntTeleportHelper.getDestinationPosition(player, destination);
}
@@ -123,6 +123,10 @@ public class RainbowAntEntity extends BaseAntEntity implements GeoEntity {
if (this.isInInfinityDimension(serverPlayer.serverLevel())) {
ServerLevel returnLevel = this.resolveReturnDestinationLevel(serverPlayer);
Vec3 returnPos = this.getDestinationPosition(returnLevel, serverPlayer);
if (returnPos == null) {
serverPlayer.displayClientMessage(Component.translatable("message.antarchy.teleport_arrival_failed"), true);
return InteractionResult.CONSUME;
}
serverPlayer.teleportTo(returnLevel, returnPos.x, returnPos.y, returnPos.z, serverPlayer.getYRot(), serverPlayer.getXRot());
serverPlayer.playSound(SoundEvents.ENDERMAN_TELEPORT, 1.0F, 1.0F);
return InteractionResult.CONSUME;
@@ -3,6 +3,9 @@ package com.craisinlord.antarchy.content.entity.nightmare;
import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.AntarchySoundEvents;
import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityDirection;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityRotationUtil;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
@@ -30,6 +33,7 @@ import java.util.UUID;
public class NightmareBiteEntity extends Entity implements GeoEntity {
private static final EntityDataAccessor<Boolean> PHASE_TWO = SynchedEntityData.defineId(NightmareBiteEntity.class, EntityDataSerializers.BOOLEAN);
private static final EntityDataAccessor<Boolean> TELEGRAPH_ONLY = SynchedEntityData.defineId(NightmareBiteEntity.class, EntityDataSerializers.BOOLEAN);
private static final RawAnimation BITE_ANIM = RawAnimation.begin().thenPlay("bite");
private static final int LIFETIME_TICKS = 12;
private static final int SOUND_TICK = 7;
@@ -64,9 +68,21 @@ public class NightmareBiteEntity extends Entity implements GeoEntity {
return bite;
}
public static NightmareBiteEntity spawnTelegraphAt(ServerLevel level, LivingEntity target, boolean phaseTwo) {
NightmareBiteEntity bite = new NightmareBiteEntity(AntarchyObjects.NIGHTMARE_BITE.get(), level);
bite.targetId = target.getUUID();
bite.setPhaseTwo(phaseTwo);
bite.setTelegraphOnly(true);
bite.approachSide = target.getRandom().nextBoolean() ? 1 : -1;
bite.updateTrackingPosition(target, 0.0D);
level.addFreshEntity(bite);
return bite;
}
@Override
protected void defineSynchedData(SynchedEntityData.Builder builder) {
builder.define(PHASE_TWO, false);
builder.define(TELEGRAPH_ONLY, false);
}
@Override
@@ -106,6 +122,14 @@ public class NightmareBiteEntity extends Entity implements GeoEntity {
this.getEntityData().set(PHASE_TWO, phaseTwo);
}
private boolean isTelegraphOnly() {
return this.entityData.get(TELEGRAPH_ONLY);
}
private void setTelegraphOnly(boolean telegraphOnly) {
this.getEntityData().set(TELEGRAPH_ONLY, telegraphOnly);
}
@Override
public void tick() {
super.tick();
@@ -113,7 +137,8 @@ public class NightmareBiteEntity extends Entity implements GeoEntity {
return;
}
LivingEntity target = this.resolveTarget();
if (target == null || !target.isAlive() || !target.hasEffect(AntarchyObjects.DREAD.get())) {
if (target == null || !target.isAlive()
|| !this.isTelegraphOnly() && !target.hasEffect(AntarchyObjects.DREAD.get())) {
this.discard();
return;
}
@@ -126,8 +151,10 @@ public class NightmareBiteEntity extends Entity implements GeoEntity {
}
if (!this.biteDamageApplied && nextAge >= DAMAGE_TICK) {
this.biteDamageApplied = true;
if (!this.isTelegraphOnly()) {
this.performBite(target);
}
}
if (nextAge >= LIFETIME_TICKS) {
this.discard();
}
@@ -143,6 +170,10 @@ public class NightmareBiteEntity extends Entity implements GeoEntity {
}
private void updateTrackingPosition(LivingEntity target, double progress) {
AntarchyGravityDirection targetGravity = AntarchyGravityApi.getGravityDirection(target);
if (AntarchyGravityApi.getGravityDirection(this) != targetGravity) {
AntarchyGravityApi.setGravityDirection(this, targetGravity);
}
Vec3 forward = target.getLookAngle().multiply(1.0D, 0.0D, 1.0D);
if (forward.lengthSqr() < 1.0E-4D) {
forward = this.getViewVector(1.0F).multiply(1.0D, 0.0D, 1.0D);
@@ -151,17 +182,21 @@ public class NightmareBiteEntity extends Entity implements GeoEntity {
forward = new Vec3(0.0D, 0.0D, 1.0D);
}
forward = forward.normalize();
Vec3 right = new Vec3(-forward.z, 0.0D, forward.x);
Vec3 localUp = AntarchyGravityRotationUtil.vecPlayerToWorld(
0.0D, 1.0D, 0.0D, targetGravity);
Vec3 right = forward.cross(localUp).normalize();
double sideOffset = START_SIDE_OFFSET + (END_SIDE_OFFSET - START_SIDE_OFFSET) * progress;
double backOffset = START_BACK_OFFSET + (END_BACK_OFFSET - START_BACK_OFFSET) * progress;
Vec3 desiredPos = target.position()
.add(0.0D, target.getBbHeight() * 0.55D, 0.0D)
Vec3 bodyCenter = target.position().add(AntarchyGravityRotationUtil.vecPlayerToWorld(
0.0D, target.getBbHeight() * 0.55D, 0.0D, targetGravity));
Vec3 desiredPos = bodyCenter
.add(right.scale(this.approachSide * sideOffset))
.subtract(forward.scale(backOffset));
Vec3 toTarget = target.getEyePosition().subtract(desiredPos);
Vec3 toTarget = bodyCenter.subtract(desiredPos);
this.setPos(desiredPos.x, desiredPos.y, desiredPos.z);
if (toTarget.lengthSqr() > 1.0E-4D) {
float yaw = (float) Math.toDegrees(Math.atan2(toTarget.z, toTarget.x)) - 90.0F;
Vec3 localToTarget = AntarchyGravityRotationUtil.vecWorldToPlayer(toTarget, targetGravity);
float yaw = (float) Math.toDegrees(Math.atan2(localToTarget.z, localToTarget.x)) - 90.0F;
this.setYRot(yaw);
}
}
@@ -177,6 +212,7 @@ public class NightmareBiteEntity extends Entity implements GeoEntity {
@Override
public void addAdditionalSaveData(CompoundTag tag) {
tag.putBoolean("PhaseTwo", this.isPhaseTwo());
tag.putBoolean("TelegraphOnly", this.isTelegraphOnly());
tag.putInt("AgeTicks", this.ageTicks);
tag.putInt("ApproachSide", this.approachSide);
tag.putBoolean("BiteSoundPlayed", this.biteSoundPlayed);
@@ -189,6 +225,7 @@ public class NightmareBiteEntity extends Entity implements GeoEntity {
@Override
public void readAdditionalSaveData(CompoundTag tag) {
this.setPhaseTwo(tag.getBoolean("PhaseTwo"));
this.setTelegraphOnly(tag.getBoolean("TelegraphOnly"));
this.ageTicks = tag.getInt("AgeTicks");
this.approachSide = tag.getInt("ApproachSide");
this.biteSoundPlayed = tag.getBoolean("BiteSoundPlayed");
@@ -642,6 +642,11 @@ public class NightmareEntity extends Monster implements GeoEntity {
return this.entityData.get(ANIMATION_STATE);
}
/** Selects the looping flight pose for the isolated computer archive preview instance. */
public void setArchivePreviewFlying() {
this.setAnimationState(ANIM_FLY);
}
public int getActiveSpecialAnimation() {
return this.entityData.get(ACTIVE_SPECIAL_ANIMATION);
}
@@ -5,12 +5,16 @@ import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.content.AntarchySoundEvents;
import com.craisinlord.antarchy.content.effect.RoyalEffectEligibility;
import com.craisinlord.antarchy.content.effect.RoyalEffectHooks;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityRotationUtil;
import com.craisinlord.antarchy.content.network.KingJudgmentFlashSync;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.network.chat.Component;
import net.minecraft.ChatFormatting;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.core.particles.DustParticleOptions;
import net.minecraft.network.protocol.game.ClientboundClearTitlesPacket;
import net.minecraft.network.protocol.game.ClientboundSetSubtitleTextPacket;
import net.minecraft.network.protocol.game.ClientboundSetTitleTextPacket;
@@ -46,6 +50,7 @@ import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
import org.joml.Vector3f;
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;
@@ -73,6 +78,10 @@ import java.util.Set;
import java.util.UUID;
public class KingEntity extends RoyalBossEntity {
private static final DustParticleOptions KING_GOLD_DUST =
new DustParticleOptions(new Vector3f(1.0F, 0.72F, 0.12F), 1.25F);
private static final DustParticleOptions KING_WHITE_DUST =
new DustParticleOptions(new Vector3f(1.0F, 1.0F, 1.0F), 1.35F);
public enum Behavior {
AIRBORNE, SPRINTING, RETREATING, HUGGING, RANGED, KEEPING_FAR
}
@@ -83,6 +92,12 @@ public class KingEntity extends RoyalBossEntity {
private static final int WING_GUST_COOLDOWN_TICKS = 320;
private static final int WING_GUST_WINDUP_TICKS = 14;
private static final double WING_GUST_RADIUS = 22.0D;
private static final int ROYAL_CHARGE_WINDUP_TICKS = 24;
private static final int ROYAL_CHARGE_ACTIVE_TICKS = 18;
private static final int ROYAL_CHARGE_RECOVERY_TICKS = 18;
private static final double ROYAL_CHARGE_SPEED = 2.35D;
private static final double ROYAL_CHARGE_MIN_RANGE = 10.0D;
private static final double ROYAL_CHARGE_MAX_RANGE = 50.0D;
private static final int CHAIN_LIGHTNING_INTERVAL = 12;
private static final int CHAIN_LIGHTNING_JUMPS = 4;
private static final double CHAIN_LIGHTNING_JUMP_RANGE = 9.0D;
@@ -92,13 +107,16 @@ public class KingEntity extends RoyalBossEntity {
private static final double TREE_ORBIT_RADIUS = 128.0D;
private static final double TREE_ORBIT_ANGLE_STEP = 0.014D;
private static final int TREE_ORBIT_PATH_SAMPLES = 16;
private static final double KING_FOLLOW_RANGE = 192.0D;
private static final double COMBAT_ORBIT_ANGLE_STEP = 0.62D;
private static final double KING_DAMAGE_MULTIPLIER = 1.5D;
private static final double KING_FOLLOW_RANGE = 216.0D;
private static final double COME_NO_CLOSER_RADIUS = 16.0D;
private static final int COME_NO_CLOSER_PARTICLE_INTERVAL = 4;
private static final int COME_NO_CLOSER_PARTICLE_COUNT = 40;
private static final double CLOSE_QUARTERS_RADIUS = 16.0D;
private static final int CLOSE_QUARTERS_APPROACH_TICKS = 120;
private static final int CLOSE_QUARTERS_EXIT_GRACE_TICKS = 40;
private static final int DECREE_RESPONSE_GRACE_TICKS = 30;
private static final int PROJECTILE_PRESSURE_MAX = 60;
private static final int PROJECTILE_PRESSURE_PER_HIT = 10;
private static final int GROUND_ASSAULT_TICKS = 200;
@@ -109,6 +127,8 @@ public class KingEntity extends RoyalBossEntity {
private static final int TAKEOFF_TICKS = 30;
private static final int COMBAT_WAYPOINT_MIN_TICKS = 45;
private static final int COMBAT_WAYPOINT_VARIANCE = 35;
private static final int GROUND_COMBAT_WAYPOINT_MIN_TICKS = 18;
private static final int GROUND_COMBAT_WAYPOINT_VARIANCE = 12;
@Nullable
private Vec3 patrolCenter;
@@ -127,11 +147,16 @@ public class KingEntity extends RoyalBossEntity {
private int treePatrolFailures;
private int decreeCooldownTicks;
private int activeDecreeTicks;
private int decreeResponseGraceTicks;
private int stompCooldownTicks;
private int wingGustCooldownTicks;
private int fireballCooldownTicks = 80;
private int iceballCooldownTicks = 120;
private int iceSpikeCooldownTicks = 160;
private int royalChargeOpeningDelayTicks = 35;
private boolean royalChargeActive;
private Vec3 royalChargeDirection = Vec3.ZERO;
private final Set<UUID> royalChargeHitTargets = new HashSet<>();
private boolean decreeRetreatPressure;
@Nullable
private String lastDecreeKey;
@@ -150,6 +175,9 @@ public class KingEntity extends RoyalBossEntity {
private Vec3 kingCombatProgressPosition;
private int kingCombatWaypointTicks;
private int kingCombatProgressTicks;
private int kingGroundCombatWaypointTicks;
@Nullable
private Vec3 kingGroundCombatWaypoint;
private int kingStrafeDirection = 1;
private int royalBoundaryGraceTicks;
@Nullable
@@ -194,10 +222,16 @@ public class KingEntity extends RoyalBossEntity {
}
public static AttributeSupplier.Builder createAttributes() {
return createBaseAttributes(AntarchySettings.kingHealth(), AntarchySettings.kingAttackDamage(), AntarchySettings.kingArmor())
return createBaseAttributes(AntarchySettings.kingHealth(),
AntarchySettings.kingAttackDamage() * KING_DAMAGE_MULTIPLIER, AntarchySettings.kingArmor())
.add(net.minecraft.world.entity.ai.attributes.Attributes.FOLLOW_RANGE, KING_FOLLOW_RANGE);
}
@Override
public float scaleRoyalDamage(double damage) {
return super.scaleRoyalDamage(damage) * (float) KING_DAMAGE_MULTIPLIER;
}
@Override
protected int maxConcurrentHeadAttacks(Phase phase) {
return phase.maxConcurrentHeadAttacks();
@@ -205,14 +239,34 @@ public class KingEntity extends RoyalBossEntity {
@Override
protected boolean blocksRoyalAttacksForMovement() {
return this.kingLandingStage == KingLandingStage.TELEGRAPH
return this.royalChargeActive
|| this.kingLandingStage == KingLandingStage.TELEGRAPH
|| this.kingLandingStage == KingLandingStage.DESCENT
|| this.kingLandingStage == KingLandingStage.IMPACT
|| this.kingLandingStage == KingLandingStage.TAKEOFF;
}
@Override
protected boolean blocksRoyalBeamScheduling() {
if (this.royalChargeActive) {
return true;
}
LivingEntity target = this.getTarget();
if (target == null || this.activeDecree != null || this.royalChargeOpeningDelayTicks > 0
|| this.kingLandingStage != KingLandingStage.AERIAL
|| !this.attackScheduler.ready("royal_charge", RoyalAttackLane.MOVEMENT)) {
return false;
}
double distance = this.distanceTo(target);
return distance >= ROYAL_CHARGE_MIN_RANGE && distance <= ROYAL_CHARGE_MAX_RANGE;
}
@Override
protected void onRoyalPhaseTransitionStarted(Phase previousPhase, Phase nextPhase) {
this.attackScheduler.cancel(RoyalAttackLane.MOVEMENT);
this.royalChargeActive = false;
this.royalChargeDirection = Vec3.ZERO;
this.royalChargeHitTargets.clear();
if (this.activeDecree != null) {
this.endDecree(null);
}
@@ -226,7 +280,7 @@ public class KingEntity extends RoyalBossEntity {
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));
? ChatFormatting.GOLD : ChatFormatting.YELLOW).withBold(true));
for (ServerPlayer player : this.royalEncounterPlayers(level)) {
player.connection.send(new ClientboundSetTitlesAnimationPacket(5, 45, 10));
player.connection.send(new ClientboundSetTitleTextPacket(title));
@@ -239,7 +293,7 @@ public class KingEntity extends RoyalBossEntity {
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,
level.sendParticles(nextPhase == Phase.THREE ? KING_GOLD_DUST : ParticleTypes.ELECTRIC_SPARK,
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);
@@ -337,6 +391,12 @@ public class KingEntity extends RoyalBossEntity {
: AntarchySoundEvents.KING_BEAM_SHOOT.get();
}
@Override
protected float royalBeamStartSoundVolume(RoyalHead head) {
return head.slot() == RoyalHead.Slot.LEFT
? 0.18F : (float) AntarchySettings.royalBossSoundVolume();
}
@Override
protected SoundEvent royalBeamLoopSound() { return AntarchySoundEvents.KING_BEAM_SHOOT.get(); }
@@ -421,7 +481,7 @@ public class KingEntity extends RoyalBossEntity {
private void chainLightning(ServerLevel level, Vec3 origin) {
java.util.Set<UUID> struck = new java.util.HashSet<>();
Vec3 from = origin;
float damage = (float) AntarchySettings.kingBeamDamage() * 0.6F;
float damage = this.scaleRoyalDamage(AntarchySettings.kingBeamDamage() * 0.6D);
for (int jump = 0; jump < CHAIN_LIGHTNING_JUMPS; jump++) {
LivingEntity next = null;
double bestScore = Double.MAX_VALUE;
@@ -501,6 +561,9 @@ public class KingEntity extends RoyalBossEntity {
if (this.tickCount % 20 == 0 && this.getArrowCount() > 6) {
this.setArrowCount(6);
}
if (this.royalChargeOpeningDelayTicks > 0) {
this.royalChargeOpeningDelayTicks--;
}
if (this.isRoyalPhaseTransitionActive()) {
return;
}
@@ -554,7 +617,7 @@ public class KingEntity extends RoyalBossEntity {
double angle = (Math.PI * 2.0D * index) / COME_NO_CLOSER_PARTICLE_COUNT;
double x = this.getX() + Math.cos(angle) * radius;
double z = this.getZ() + Math.sin(angle) * radius;
serverLevel.sendParticles(keepOut ? ParticleTypes.END_ROD : ParticleTypes.ENCHANT,
serverLevel.sendParticles(keepOut ? ParticleTypes.END_ROD : KING_GOLD_DUST,
x, this.getY() + 0.25D, z,
1, 0.0D, 0.0D, 0.0D, 0.0D);
}
@@ -700,6 +763,10 @@ public class KingEntity extends RoyalBossEntity {
}
private void tickKingCombatMovement(LivingEntity target) {
if (this.royalChargeActive) {
this.getNavigation().stop();
return;
}
boolean closeQuarters = this.activeDecree instanceof CloseQuartersDecree;
if (closeQuarters && this.isRoyalFlying() && this.kingLandingStage == KingLandingStage.AERIAL) {
this.beginKingLanding(target);
@@ -728,9 +795,13 @@ public class KingEntity extends RoyalBossEntity {
this.setRoyalFlying(true);
this.getMoveControl().setWantedPosition(landing.x, landing.y, landing.z, 1.35D);
this.tickKingLandingIndicator();
Vec3 localLandingDelta = AntarchyGravityRotationUtil.vecWorldToPlayer(
this.position().subtract(landing), AntarchyGravityApi.getGravityDirection(this));
double horizontalDistanceSquared = this.position().subtract(landing)
.multiply(1.0D, 0.0D, 1.0D).lengthSqr();
boolean reachedLanding = this.onGround()
|| this.position().distanceToSqr(landing) <= 2.25D
|| this.getY() <= landing.y + 1.25D;
|| Math.abs(localLandingDelta.y) <= 1.25D && horizontalDistanceSquared <= 4.0D;
if (reachedLanding) {
this.setRoyalFlying(false);
this.setPos(this.getX(), landing.y, this.getZ());
@@ -750,8 +821,8 @@ public class KingEntity extends RoyalBossEntity {
if (--this.kingLandingStageTicks <= 0) {
this.kingLandingStage = KingLandingStage.GROUND_COMBAT;
this.kingLandingStageTicks = closeQuarters
? Math.max(GROUND_ASSAULT_TICKS, this.activeDecreeTicks + 20)
: GROUND_ASSAULT_TICKS;
? Math.max(this.groundAssaultTicksForPhase(), this.activeDecreeTicks + 20)
: this.groundAssaultTicksForPhase();
this.kingLandingWaypoint = null;
}
return;
@@ -760,7 +831,14 @@ public class KingEntity extends RoyalBossEntity {
if (closeQuarters) {
this.kingLandingStageTicks = Math.max(this.kingLandingStageTicks, this.activeDecreeTicks + 20);
}
this.getMoveControl().setWantedPosition(target.getX(), target.getY(), target.getZ(), 1.25D);
if (this.kingGroundCombatWaypoint == null || --this.kingGroundCombatWaypointTicks <= 0
|| this.position().distanceToSqr(this.kingGroundCombatWaypoint) < 4.0D) {
this.selectKingGroundCombatWaypoint(target);
}
if (this.kingGroundCombatWaypoint != null) {
this.getMoveControl().setWantedPosition(this.kingGroundCombatWaypoint.x,
this.kingGroundCombatWaypoint.y, this.kingGroundCombatWaypoint.z, 1.25D);
}
if (--this.kingLandingStageTicks <= 0 && !closeQuarters) {
this.beginKingTakeoff(target);
}
@@ -808,6 +886,9 @@ public class KingEntity extends RoyalBossEntity {
this.kingCombatWaypoint = null;
this.playRoyalSound(AntarchySoundEvents.KING_ROAR.get(), 1.12F);
} else {
// Keep Close Quarters from silently dissolving when the first ring is obstructed.
// The next aerial tick will retry the expanded landing search.
this.kingLandingWaypoint = null;
this.groundAssaultCooldownTicks = 100;
}
}
@@ -821,7 +902,8 @@ public class KingEntity extends RoyalBossEntity {
this.performWingGust(level, target);
}
this.setRoyalFlying(true);
this.setDeltaMovement(this.getDeltaMovement().add(0.0D, 0.42D, 0.0D));
this.addGravityAwareImpulse(this, AntarchyGravityRotationUtil.vecPlayerToWorld(
0.0D, 0.42D, 0.0D, AntarchyGravityApi.getGravityDirection(this)));
this.hasImpulse = true;
}
@@ -835,9 +917,11 @@ public class KingEntity extends RoyalBossEntity {
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,
Vec3 indicator = this.kingLandingWaypoint.add(AntarchyGravityRotationUtil.vecPlayerToWorld(
0.0D, 0.2D, 0.0D, AntarchyGravityApi.getGravityDirection(this)));
level.sendParticles(ParticleTypes.ELECTRIC_SPARK,
this.kingLandingWaypoint.x + Math.cos(angle) * radius,
this.kingLandingWaypoint.y + 0.2D,
indicator.y,
this.kingLandingWaypoint.z + Math.sin(angle) * radius,
1, 0.0D, 0.02D, 0.0D, 0.0D);
}
@@ -847,13 +931,15 @@ public class KingEntity extends RoyalBossEntity {
private Vec3 findKingLandingPoint(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;
double[] radii = {9.0D, 13.0D, 17.0D, 6.0D};
for (int angleIndex = 0; angleIndex < 12; angleIndex++) {
double angle = baseAngle + angleIndex * Mth.TWO_PI / 12.0D;
double[] radii = {9.0D, 13.0D, 17.0D, 6.0D, 4.0D, 2.0D, 0.0D};
for (int angleIndex = 0; angleIndex < 16; angleIndex++) {
double angle = baseAngle + angleIndex * Mth.TWO_PI / 16.0D;
for (double radius : radii) {
double x = target.getX() + Math.cos(angle) * radius;
double z = target.getZ() + Math.sin(angle) * radius;
double y = this.groundYBelow(x, z) + 0.1D;
double y = AntarchyGravityApi.isGravityInverted(this)
? target.position().y - 0.1D
: 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());
@@ -862,7 +948,25 @@ public class KingEntity extends RoyalBossEntity {
}
}
}
return null;
double fallbackY = AntarchyGravityApi.isGravityInverted(this)
? target.position().y - 0.1D
: this.groundYBelow(target.getX(), target.getZ()) + 0.1D;
Vec3 fallback = new Vec3(target.getX(), fallbackY, target.getZ());
AABB fallbackBox = this.getBoundingBox().move(
fallback.x - this.getX(), fallback.y - this.getY(), fallback.z - this.getZ());
if (this.level().noCollision(this, fallbackBox)) {
return fallback;
}
// Last resort: land directly beneath the King if the target ring is obstructed.
// This still gives Close Quarters a committed ground sequence instead of silently
// remaining airborne over difficult tree geometry.
double directY = AntarchyGravityApi.isGravityInverted(this)
? this.position().y - 0.1D
: this.groundYBelow(this.getX(), this.getZ()) + 0.1D;
Vec3 direct = new Vec3(this.getX(), directY, this.getZ());
AABB directBox = this.getBoundingBox().move(
direct.x - this.getX(), direct.y - this.getY(), direct.z - this.getZ());
return this.level().noCollision(this, directBox) ? direct : null;
}
private void selectKingCombatWaypoint(LivingEntity target) {
@@ -871,22 +975,71 @@ public class KingEntity extends RoyalBossEntity {
radial = new Vec3(1.0D, 0.0D, 0.0D);
}
radial = radial.normalize();
Vec3 tangent = new Vec3(-radial.z * this.kingStrafeDirection, 0.0D,
radial.x * this.kingStrafeDirection);
double range = this.activeDecree instanceof ComeNoCloserDecree || this.projectilePressure >= 30
? 40.0D : 34.0D;
Vec3 horizontal = target.position().add(radial.scale(range)).add(tangent.scale(12.0D));
double groundY = this.groundYBelow(horizontal.x, horizontal.z);
double y = Mth.clamp(target.getY() + 8.0D, groundY + FLYING_MIN_HOVER,
groundY + FLYING_MAX_HOVER_ABOVE_GROUND);
this.kingCombatWaypoint = new Vec3(horizontal.x, y, horizontal.z);
? 40.0D : this.aerialCombatRangeForPhase();
double currentAngle = Math.atan2(radial.z, radial.x);
Vec3 orbitWaypoint = null;
boolean treeBoundCombat = this.treePatrolBound && this.treePatrolCenter != null;
double boundaryRadius = treeBoundCombat
? Math.max(32.0D, AntarchySettings.royalBoundaryRadius()) - this.getBbWidth() * 0.5D
: Double.POSITIVE_INFINITY;
double hoverOffset = this.phase() == Phase.ONE ? 8.0D : this.phase() == Phase.TWO ? 6.0D : 4.0D;
for (int angleAttempt = 0; angleAttempt < 4 && orbitWaypoint == null; angleAttempt++) {
double angle = currentAngle + this.kingStrafeDirection * COMBAT_ORBIT_ANGLE_STEP * (angleAttempt + 1);
double candidateRange = range + (angleAttempt % 2 == 0 ? 0.0D : -6.0D);
double x = target.getX() + Math.cos(angle) * candidateRange;
double z = target.getZ() + Math.sin(angle) * candidateRange;
double groundY = this.groundYBelow(x, z);
double y = Mth.clamp(target.getY() + hoverOffset,
groundY + FLYING_MIN_HOVER, groundY + FLYING_MAX_HOVER_ABOVE_GROUND);
Vec3 candidate = new Vec3(x, y, z);
if (treeBoundCombat) {
double dx = x - this.treePatrolCenter.x;
double dz = z - this.treePatrolCenter.z;
if (dx * dx + dz * dz > boundaryRadius * boundaryRadius) {
continue;
}
}
if (this.clearTreePatrolPath(candidate)) {
orbitWaypoint = candidate;
}
}
if (orbitWaypoint == null && treeBoundCombat) {
// If the tree blocks the next orbital lane, turn away and try the opposite side
// before falling back to a clear target-relative position.
this.kingStrafeDirection *= -1;
double angle = currentAngle + this.kingStrafeDirection * COMBAT_ORBIT_ANGLE_STEP;
double x = target.getX() + Math.cos(angle) * range;
double z = target.getZ() + Math.sin(angle) * range;
double groundY = this.groundYBelow(x, z);
double y = Mth.clamp(target.getY() + hoverOffset,
groundY + FLYING_MIN_HOVER, groundY + FLYING_MAX_HOVER_ABOVE_GROUND);
Vec3 candidate = new Vec3(x, y, z);
double dx = candidate.x - this.treePatrolCenter.x;
double dz = candidate.z - this.treePatrolCenter.z;
if (dx * dx + dz * dz <= boundaryRadius * boundaryRadius
&& this.clearTreePatrolPath(candidate)) {
orbitWaypoint = candidate;
}
}
if (orbitWaypoint == null) {
double angle = currentAngle + this.kingStrafeDirection * COMBAT_ORBIT_ANGLE_STEP;
double x = target.getX() + Math.cos(angle) * range;
double z = target.getZ() + Math.sin(angle) * range;
double groundY = this.groundYBelow(x, z);
double y = Mth.clamp(target.getY() + hoverOffset,
groundY + FLYING_MIN_HOVER, groundY + FLYING_MAX_HOVER_ABOVE_GROUND);
orbitWaypoint = new Vec3(x, y, z);
if (treeBoundCombat) {
orbitWaypoint = this.findTreePatrolWaypoint(this.treePatrolCenter, this.treePatrolAngle);
}
}
this.kingCombatWaypoint = orbitWaypoint;
this.kingCombatWaypointTicks = COMBAT_WAYPOINT_MIN_TICKS
+ this.random.nextInt(COMBAT_WAYPOINT_VARIANCE + 1);
this.kingCombatProgressPosition = this.position();
this.kingCombatProgressTicks = 0;
if (this.random.nextInt(4) == 0) {
this.kingStrafeDirection *= -1;
}
// Keep a consistent orbit direction for several passes so movement reads as circling.
}
private void stabilizeKingRotation() {
@@ -908,11 +1061,38 @@ public class KingEntity extends RoyalBossEntity {
this.kingCombatProgressPosition = null;
this.kingCombatWaypointTicks = 0;
this.kingCombatProgressTicks = 0;
this.kingGroundCombatWaypoint = null;
this.kingGroundCombatWaypointTicks = 0;
if (this.groundAssaultCooldownTicks <= 0) {
this.groundAssaultCooldownTicks = 400;
}
}
private void selectKingGroundCombatWaypoint(LivingEntity target) {
Vec3 radial = this.position().subtract(target.position()).multiply(1.0D, 0.0D, 1.0D);
if (radial.lengthSqr() < 1.0E-4D) {
radial = new Vec3(1.0D, 0.0D, 0.0D);
}
radial = radial.normalize();
Vec3 tangent = new Vec3(-radial.z * this.kingStrafeDirection, 0.0D,
radial.x * this.kingStrafeDirection);
double distance = 4.0D + this.random.nextDouble() * 3.0D;
Vec3 candidate = target.position().add(radial.scale(distance)).add(tangent.scale(2.5D));
Vec3 waypoint = new Vec3(candidate.x, target.getY(), candidate.z);
AABB box = this.getBoundingBox().move(waypoint.x - this.getX(), waypoint.y - this.getY(), waypoint.z - this.getZ());
if (!this.level().noCollision(this, box)) {
waypoint = target.position().add(radial.scale(distance));
waypoint = new Vec3(waypoint.x, target.getY(), waypoint.z);
box = this.getBoundingBox().move(waypoint.x - this.getX(), waypoint.y - this.getY(), waypoint.z - this.getZ());
}
this.kingGroundCombatWaypoint = this.level().noCollision(this, box) ? waypoint : target.position();
this.kingGroundCombatWaypointTicks = GROUND_COMBAT_WAYPOINT_MIN_TICKS
+ this.random.nextInt(GROUND_COMBAT_WAYPOINT_VARIANCE + 1);
if (this.random.nextInt(4) == 0) {
this.kingStrafeDirection *= -1;
}
}
public void requestCloseQuartersAssault(LivingEntity target) {
if (target != null && target.isAlive() && this.isRoyalFlying()
&& this.kingLandingStage == KingLandingStage.AERIAL) {
@@ -937,6 +1117,13 @@ public class KingEntity extends RoyalBossEntity {
? RoyalDecree.Evaluation.VIOLATED : RoyalDecree.Evaluation.COMPLIANT;
}
public int closeQuartersCountdownTicks(LivingEntity target) {
if (this.closeQuartersEntered.contains(target.getUUID())) {
return -1;
}
return Math.max(0, CLOSE_QUARTERS_APPROACH_TICKS - (300 - this.activeDecreeTicks));
}
private void tickRoyalPunishment() {
if (this.exileTarget == null && this.titheTarget == null && this.targetShotPlayer == null && this.sealedTarget == null) {
return;
@@ -973,6 +1160,9 @@ public class KingEntity extends RoyalBossEntity {
this.exileTarget.setDeltaMovement(away.x * 2.6D, 1.35D, away.z * 2.6D);
this.exileTarget.hasImpulse = true;
this.playRoyalSound(AntarchySoundEvents.KING_WING_FLAP.get(), 0.82F);
if (this.level() instanceof ServerLevel level) {
this.spawnKingWingPressure(level, away);
}
this.exileWingAnimationPending = true;
}
}
@@ -1147,11 +1337,12 @@ public class KingEntity extends RoyalBossEntity {
this.endDecree(this.getTarget());
this.activeDecree = decree;
this.activeDecreeTicks = 300;
this.decreeResponseGraceTicks = DECREE_RESPONSE_GRACE_TICKS;
this.decreeCooldownTicks = 0;
this.closeQuartersEntered.clear();
this.closeQuartersOutsideTicks.clear();
this.decreeRecipients.clear();
this.projectRoyalSound(AntarchySoundEvents.KING_DECREE.get(), 4.0F, 1.0F, target);
this.projectRoyalSound(AntarchySoundEvents.KING_DECREE.get(), 1.15F, 1.0F, target);
this.broadcastActiveDecree((ServerLevel) this.level());
return true;
}
@@ -1189,7 +1380,7 @@ public class KingEntity extends RoyalBossEntity {
case KINGS_SEAL -> {
this.sealUntil = level.getGameTime() + 100L;
target.displayClientMessage(Component.literal("The King seals your inventory."), true);
level.sendParticles(ParticleTypes.ENCHANT, target.getX(), target.getY() + 1.0D, target.getZ(),
level.sendParticles(KING_GOLD_DUST, target.getX(), target.getY() + 1.0D, target.getZ(),
30, 0.6D, 1.0D, 0.6D, 0.1D);
this.startSeal(target);
return true;
@@ -1249,12 +1440,16 @@ public class KingEntity extends RoyalBossEntity {
|| this.activeDecree instanceof ComeNoCloserDecree
;
if (!forceFar && this.activeDecree == null && this.tryStartRoyalCharge(level, target)) {
return;
}
{
double distance = target.distanceTo(this);
if (this.attackScheduler.ready("stomp", RoyalAttackLane.BODY)
&& (distance <= STOMP_RADIUS || skyIsMine)
&& this.beginRoyalAttack("stomp", RoyalAttackLane.BODY,
skyIsMine ? STOMP_COOLDOWN_TICKS / 2 : STOMP_COOLDOWN_TICKS,
this.stompCooldownForPhase(skyIsMine),
STOMP_WINDUP_TICKS, 1, 18, new com.craisinlord.antarchy.content.entity.royal.attack.RoyalAttackScheduler.Action() {
@Override public void onStart() {
KingEntity.this.triggerAnim("body_action", "stomp");
@@ -1268,8 +1463,8 @@ public class KingEntity extends RoyalBossEntity {
this.getMoveControl().setWantedPosition(this.getX(), target.getY(), this.getZ(), 1.2D);
}
} else if (this.attackScheduler.ready("wing_gust", RoyalAttackLane.BODY)
&& distance <= WING_GUST_RADIUS && (forceFar || this.random.nextInt(3) == 0)
&& this.beginRoyalAttack("wing_gust", RoyalAttackLane.BODY, WING_GUST_COOLDOWN_TICKS,
&& distance <= WING_GUST_RADIUS && (forceFar || this.random.nextInt(this.phase() == Phase.ONE ? 3 : 2) == 0)
&& this.beginRoyalAttack("wing_gust", RoyalAttackLane.BODY, this.wingGustCooldownForPhase(),
WING_GUST_WINDUP_TICKS, 1, this.animationRecovery(55, WING_GUST_WINDUP_TICKS, 1, 16), new com.craisinlord.antarchy.content.entity.royal.attack.RoyalAttackScheduler.Action() {
@Override public void onStart() {
KingEntity.this.triggerAnim("wing_action", "wing_gust");
@@ -1281,7 +1476,7 @@ public class KingEntity extends RoyalBossEntity {
})) {
} else if (this.attackScheduler.ready("royal_muster", RoyalAttackLane.BODY)
&& !this.findRoyalMusterTargets(level).isEmpty()
&& (forceFar || this.random.nextInt(4) == 0)
&& (forceFar || this.random.nextInt(this.phase() == Phase.THREE ? 3 : 4) == 0)
&& this.beginRoyalAttack("royal_muster", RoyalAttackLane.BODY,
AntarchySettings.kingRoyalMusterCooldownTicks(),
AntarchySettings.kingRoyalMusterWindupTicks(), 1,
@@ -1289,8 +1484,8 @@ public class KingEntity extends RoyalBossEntity {
new com.craisinlord.antarchy.content.entity.royal.attack.RoyalAttackScheduler.Action() {
@Override public void onStart() {
KingEntity.this.triggerAnim("body_action", "minion_spawn");
KingEntity.this.playRoyalSound(AntarchySoundEvents.KING_DECREE_CAST.get(), 0.72F);
level.sendParticles(ParticleTypes.ENCHANT,
KingEntity.this.playRoyalSound(AntarchySoundEvents.KING_DECREE_CAST.get(), 0.34F, 0.72F);
level.sendParticles(KING_GOLD_DUST,
KingEntity.this.getX(), KingEntity.this.getY() + 2.0D, KingEntity.this.getZ(),
36, 5.0D, 2.0D, 5.0D, 0.12D);
}
@@ -1315,6 +1510,151 @@ public class KingEntity extends RoyalBossEntity {
this.tickKingElementalProjectiles(level, target);
}
private boolean tryStartRoyalCharge(ServerLevel level, LivingEntity target) {
double distance = this.distanceTo(target);
if (this.royalChargeOpeningDelayTicks > 0
|| this.kingLandingStage != KingLandingStage.AERIAL
|| distance < ROYAL_CHARGE_MIN_RANGE || distance > ROYAL_CHARGE_MAX_RANGE
|| this.attackScheduler.laneBusy(RoyalAttackLane.LEFT_HEAD)
|| this.attackScheduler.laneBusy(RoyalAttackLane.CENTER_HEAD)
|| this.attackScheduler.laneBusy(RoyalAttackLane.RIGHT_HEAD)
|| !this.attackScheduler.ready("royal_charge", RoyalAttackLane.MOVEMENT)) {
return false;
}
return this.beginRoyalAttack("royal_charge", RoyalAttackLane.MOVEMENT,
this.royalChargeCooldownForPhase(), ROYAL_CHARGE_WINDUP_TICKS,
ROYAL_CHARGE_ACTIVE_TICKS, ROYAL_CHARGE_RECOVERY_TICKS,
new com.craisinlord.antarchy.content.entity.royal.attack.RoyalAttackScheduler.Action() {
@Override
public void onStart() {
KingEntity.this.royalChargeActive = true;
KingEntity.this.royalChargeHitTargets.clear();
KingEntity.this.lockRoyalChargeDirection(target);
KingEntity.this.getNavigation().stop();
KingEntity.this.setDeltaMovement(KingEntity.this.getDeltaMovement().scale(0.2D));
KingEntity.this.triggerAnim("wing_action", "wing_gust");
KingEntity.this.playRoyalSound(AntarchySoundEvents.KING_WING_FLAP.get(), 0.55F, 0.78F);
}
@Override
public void onWindup(int elapsedTicks, int remainingTicks) {
KingEntity.this.getNavigation().stop();
KingEntity.this.setDeltaMovement(KingEntity.this.getDeltaMovement().scale(0.72D));
if (target.isAlive() && target.level() == level && remainingTicks > 8) {
KingEntity.this.lockRoyalChargeDirection(target);
}
KingEntity.this.sendRoyalChargeTelegraph(level, elapsedTicks);
}
@Override
public void onActive(int elapsedTicks) {
KingEntity.this.setRoyalFlying(true);
KingEntity.this.setDeltaMovement(KingEntity.this.royalChargeDirection.scale(ROYAL_CHARGE_SPEED));
KingEntity.this.hasImpulse = true;
KingEntity.this.applyRoyalChargeHits(level);
level.sendParticles(elapsedTicks % 3 == 0 ? ParticleTypes.END_ROD : KING_GOLD_DUST,
KingEntity.this.getX(), KingEntity.this.getY() + KingEntity.this.getBbHeight() * 0.5D,
KingEntity.this.getZ(), 6, KingEntity.this.getBbWidth() * 0.3D,
KingEntity.this.getBbHeight() * 0.22D, KingEntity.this.getBbWidth() * 0.3D, 0.03D);
if (elapsedTicks == ROYAL_CHARGE_ACTIVE_TICKS - 1) {
KingEntity.this.setDeltaMovement(KingEntity.this.getDeltaMovement().scale(0.12D));
}
}
@Override
public void onComplete() {
KingEntity.this.royalChargeActive = false;
KingEntity.this.royalChargeDirection = Vec3.ZERO;
KingEntity.this.royalChargeHitTargets.clear();
KingEntity.this.setDeltaMovement(KingEntity.this.getDeltaMovement().scale(0.2D));
KingEntity.this.startRoyalRecovery(16);
}
});
}
private void lockRoyalChargeDirection(LivingEntity target) {
Vec3 aimPoint = target.getEyePosition().add(target.getDeltaMovement().scale(4.0D));
Vec3 direction = aimPoint.subtract(this.position().add(0.0D, this.getBbHeight() * 0.5D, 0.0D));
if (direction.lengthSqr() > 1.0E-4D) {
this.royalChargeDirection = direction.normalize();
this.getLookControl().setLookAt(aimPoint.x, aimPoint.y, aimPoint.z, 30.0F, 30.0F);
}
}
private void sendRoyalChargeTelegraph(ServerLevel level, int elapsedTicks) {
if (this.royalChargeDirection.lengthSqr() < 1.0E-4D || elapsedTicks % 2 != 0) {
return;
}
Vec3 origin = this.position().add(0.0D, this.getBbHeight() * 0.5D, 0.0D);
double length = 12.0D + elapsedTicks * 1.25D;
int samples = Math.max(6, (int) (length / 2.5D));
for (int sample = 1; sample <= samples; sample++) {
Vec3 point = origin.add(this.royalChargeDirection.scale(length * sample / samples));
level.sendParticles(sample % 4 == 0 ? ParticleTypes.END_ROD : KING_GOLD_DUST,
point.x, point.y, point.z, 1, 0.025D, 0.025D, 0.025D, 0.0D);
}
}
private void applyRoyalChargeHits(ServerLevel level) {
AABB hitBox = this.getBoundingBox().inflate(2.25D);
for (LivingEntity living : level.getEntitiesOfClass(LivingEntity.class, hitBox,
entity -> entity.isAlive() && entity instanceof Player && this.canDamageWithRoyalAttack(entity))) {
if (!this.royalChargeHitTargets.add(living.getUUID())) {
continue;
}
living.hurt(this.damageSources().mobAttack(this), this.scaleRoyalDamage(12.0D));
Vec3 lift = AntarchyGravityRotationUtil.vecPlayerToWorld(
0.0D, 0.45D, 0.0D, AntarchyGravityApi.getGravityDirection(living));
this.addGravityAwareImpulse(living, this.royalChargeDirection.scale(2.1D).add(lift));
living.hasImpulse = true;
level.sendParticles(ParticleTypes.END_ROD, living.getX(), living.getY() + living.getBbHeight() * 0.5D,
living.getZ(), 18, 0.55D, 0.7D, 0.55D, 0.08D);
}
}
private int royalChargeCooldownForPhase() {
return switch (this.phase()) {
case ONE -> 300;
case TWO -> 250;
case THREE -> 210;
};
}
private int groundAssaultTicksForPhase() {
return switch (this.phase()) {
case ONE -> GROUND_ASSAULT_TICKS;
case TWO -> GROUND_ASSAULT_TICKS + 40;
case THREE -> GROUND_ASSAULT_TICKS + 80;
};
}
private double aerialCombatRangeForPhase() {
return switch (this.phase()) {
case ONE -> 34.0D;
case TWO -> 29.0D;
case THREE -> 24.0D;
};
}
private int stompCooldownForPhase(boolean skyIsMine) {
double scale = switch (this.phase()) {
case ONE -> 1.0D;
case TWO -> 0.88D;
case THREE -> 0.72D;
};
int cooldown = Mth.floor(STOMP_COOLDOWN_TICKS * scale);
return skyIsMine ? cooldown / 2 : cooldown;
}
private int wingGustCooldownForPhase() {
double scale = switch (this.phase()) {
case ONE -> 1.0D;
case TWO -> 0.88D;
case THREE -> 0.72D;
};
return Mth.floor(WING_GUST_COOLDOWN_TICKS * scale);
}
private List<Mob> findRoyalMusterTargets(ServerLevel level) {
double radius = AntarchySettings.kingRoyalMusterRadius();
return level.getEntitiesOfClass(Mob.class, this.getBoundingBox().inflate(radius), mob ->
@@ -1342,7 +1682,7 @@ public class KingEntity extends RoyalBossEntity {
}
}
if (applied > 0) {
this.playRoyalSound(AntarchySoundEvents.KING_DECREE_CAST.get(), 0.95F);
this.playRoyalSound(AntarchySoundEvents.KING_DECREE_CAST.get(), 0.34F, 0.95F);
}
}
@@ -1384,7 +1724,9 @@ public class KingEntity extends RoyalBossEntity {
RoyalElementalProjectileEntity fireball = RoyalElementalProjectileEntity.create(level, KingEntity.this,
RoyalBeamElement.FIRE, origin, direction);
level.addFreshEntity(fireball);
KingEntity.this.playRoyalSound(AntarchySoundEvents.KING_FIREBALL_SHOOT.get(), 0.9F);
if (KingEntity.this.random.nextInt(5) == 0) {
KingEntity.this.playRoyalSound(AntarchySoundEvents.KING_FIREBALL_SHOOT.get(), 0.18F, 0.9F);
}
}
})) return;
this.fireballCooldownTicks = AntarchySettings.kingFireballCooldownTicks();
@@ -1414,7 +1756,7 @@ public class KingEntity extends RoyalBossEntity {
}
@Override public void onActive(int elapsedTicks) {
KingEntity.this.raiseIceSpikes(level, target);
KingEntity.this.playRoyalSound(AntarchySoundEvents.KING_ICE_SPIKES.get(), 0.8F);
KingEntity.this.playRoyalSound(AntarchySoundEvents.KING_ICE_SPIKES.get(), 0.28F, 0.8F);
}
})) return;
this.iceSpikeCooldownTicks = AntarchySettings.kingIceSpikeCooldownTicks();
@@ -1449,7 +1791,9 @@ public class KingEntity extends RoyalBossEntity {
float falloff = (float) Math.max(0.2D, 1.0D - distance / (STOMP_RADIUS + 6.0D));
living.hurt(source, base * falloff);
Vec3 push = living.position().subtract(this.position()).normalize().scale(2.4D * falloff);
living.setDeltaMovement(living.getDeltaMovement().add(push.x, 0.85D, push.z));
Vec3 vertical = AntarchyGravityRotationUtil.vecPlayerToWorld(
0.0D, 0.85D, 0.0D, AntarchyGravityApi.getGravityDirection(living));
this.addGravityAwareImpulse(living, new Vec3(push.x, 0.0D, push.z).add(vertical));
living.hasImpulse = true;
}
RoyalBlockDestruction.destroySphere(level, this,
@@ -1475,9 +1819,25 @@ public class KingEntity extends RoyalBossEntity {
living.hasImpulse = true;
living.hurt(this.damageSources().mobAttack(this), this.scaleRoyalDamage(6.0D));
}
this.spawnKingWingPressure(level, forward);
level.sendParticles(ParticleTypes.SWEEP_ATTACK, this.getX() + forward.x * 6.0D, this.getY() + 4.0D, this.getZ() + forward.z * 6.0D, 12, 4.0D, 2.0D, 4.0D, 0.0D);
}
private void spawnKingWingPressure(ServerLevel level, Vec3 forward) {
Vec3 side = new Vec3(-forward.z, 0.0D, forward.x);
Vec3 origin = this.position().add(0.0D, this.getBbHeight() * 0.55D, 0.0D);
for (int band = 1; band <= 5; band++) {
double distance = band * 3.0D;
double width = 1.2D + band * 1.05D;
for (int sample = -3; sample <= 3; sample++) {
Vec3 point = origin.add(forward.scale(distance)).add(side.scale(width * sample / 3.0D));
Vec3 velocity = forward.scale(0.18D + band * 0.015D).add(0.0D, 0.025D, 0.0D);
level.sendParticles(sample % 2 == 0 ? KING_WHITE_DUST : ParticleTypes.CLOUD,
point.x, point.y, point.z, 0, velocity.x, velocity.y, velocity.z, 1.0D);
}
}
}
private void tickDecree(ServerLevel level, LivingEntity target) {
if (this.isRoyalRecoveryActive() && this.activeDecree == null) {
return;
@@ -1502,31 +1862,41 @@ public class KingEntity extends RoyalBossEntity {
return;
}
this.activeDecreeTicks = 300;
this.decreeResponseGraceTicks = DECREE_RESPONSE_GRACE_TICKS;
this.activeDecree = this.pickDecree(target);
this.decreeRetreatPressure = false;
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_DECREE_CAST.get(), 0.34F,
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.projectRoyalSound(AntarchySoundEvents.KING_DECREE.get(), 1.15F, 1.0F, target);
this.broadcastActiveDecree(level);
}
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, participant.blockPosition(), SoundEvents.BELL_BLOCK,
SoundSource.HOSTILE, 0.8F, 1.0F + countdown / 500.0F);
if (this.activeDecreeTicks % 20 == 0) {
int deadlineTicks = this.activeDecree.countdownTicks(this, participant);
int countdownTicks = deadlineTicks >= 0 ? deadlineTicks : this.activeDecreeTicks;
int seconds = Math.max(0, (countdownTicks + 19) / 20);
ChatFormatting countdownColor = seconds <= 3 ? ChatFormatting.RED : ChatFormatting.GOLD;
participant.displayClientMessage(Component.empty()
.append(Component.translatable(this.activeDecree.translationKey())
.withStyle(ChatFormatting.GOLD, ChatFormatting.BOLD))
.append(Component.literal(" " + seconds).withStyle(countdownColor, ChatFormatting.BOLD)), true);
float volume = seconds <= 3 ? 0.8F : 0.45F;
float pitch = 1.0F + Math.max(0, 15 - seconds) * 0.035F;
participant.playNotifySound(SoundEvents.BELL_BLOCK, SoundSource.HOSTILE, volume, pitch);
}
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) {
if (evaluation == RoyalDecree.Evaluation.VIOLATED && this.decreeResponseGraceTicks <= 0) {
this.failActiveDecree(participant);
return;
}
@@ -1541,6 +1911,9 @@ public class KingEntity extends RoyalBossEntity {
this.endDecree(target);
return;
}
if (this.decreeResponseGraceTicks > 0) {
this.decreeResponseGraceTicks--;
}
if (this.activeDecree != null && --this.activeDecreeTicks <= 0) {
this.projectRoyalSound(AntarchySoundEvents.KING_SUCCESS.get(), 4.0F, 1.0F, target);
this.endDecree(target);
@@ -1588,6 +1961,9 @@ public class KingEntity extends RoyalBossEntity {
}
for (ServerPlayer participant : this.royalEncounterPlayers(level)) {
if (this.decreeRecipients.add(participant.getUUID())) {
if (this.activeDecree instanceof ShowNoMercyDecree) {
this.lastPlayerDamageTime.put(participant.getUUID(), level.getGameTime());
}
this.sendDecreeTitle(participant);
}
}
@@ -1608,6 +1984,7 @@ public class KingEntity extends RoyalBossEntity {
Player recipientPlayer = level.getPlayerByUUID(recipientId);
if (recipientPlayer instanceof ServerPlayer recipient) {
recipient.connection.send(new ClientboundClearTitlesPacket(false));
recipient.displayClientMessage(Component.empty(), true);
}
}
}
@@ -1617,6 +1994,7 @@ public class KingEntity extends RoyalBossEntity {
}
this.activeDecree = null;
this.activeDecreeTicks = 0;
this.decreeResponseGraceTicks = 0;
this.decreeRetreatPressure = false;
this.closeQuartersEntered.clear();
this.closeQuartersOutsideTicks.clear();
@@ -1639,8 +2017,13 @@ public class KingEntity extends RoyalBossEntity {
}
this.attackScheduler.cancel(RoyalAttackLane.DECREE);
this.attackScheduler.cancel(RoyalAttackLane.HAZARD);
if (target instanceof ServerPlayer player) {
player.addEffect(new MobEffectInstance(
AntarchyObjects.ROYAL_VITALITY_PENALTY_EFFECT.get(), 20 * 30, 0, false, false, false));
player.setHealth(Math.min(player.getHealth(), player.getMaxHealth()));
}
this.endDecree(target);
this.projectRoyalSound(AntarchySoundEvents.KING_JUDGEMENT.get(), 4.0F, 1.0F, target);
this.projectRoyalSound(AntarchySoundEvents.KING_JUDGEMENT.get(), 1.1F, 1.0F, target);
if (target instanceof ServerPlayer player) {
this.sendDecreeViolationTitle(player);
}
@@ -1650,9 +2033,9 @@ public class KingEntity extends RoyalBossEntity {
private void sendDecreeViolationTitle(ServerPlayer player) {
player.connection.send(new ClientboundSetTitlesAnimationPacket(5, 45, 10));
player.connection.send(new ClientboundSetTitleTextPacket(Component.translatable("decree.antarchy.violated")
.withStyle(style -> style.withColor(ChatFormatting.DARK_PURPLE).withBold(true))));
.withStyle(style -> style.withColor(ChatFormatting.GOLD).withBold(true))));
player.connection.send(new ClientboundSetSubtitleTextPacket(Component.translatable("decree.antarchy.violated.subtitle")
.withStyle(style -> style.withColor(ChatFormatting.LIGHT_PURPLE))));
.withStyle(style -> style.withColor(ChatFormatting.YELLOW))));
}
private void startRoyalJudgmentBarrage(LivingEntity target) {
@@ -1666,13 +2049,37 @@ public class KingEntity extends RoyalBossEntity {
for (RoyalHead.Slot slot : RoyalHead.Slot.values()) {
KingEntity.this.triggerAnim(slot.controllerName(), "shoot");
}
level.sendParticles(ParticleTypes.WITCH,
level.sendParticles(ParticleTypes.ELECTRIC_SPARK,
KingEntity.this.getX(), KingEntity.this.getY() + KingEntity.this.getBbHeight() * 0.7D,
KingEntity.this.getZ(), 70, 4.0D, 3.0D, 4.0D, 0.12D);
KingEntity.this.sendRoyalJudgmentTelegraph(level, target, 0);
Vec3 targetFlash = target.getEyePosition();
level.sendParticles(ParticleTypes.END_ROD, targetFlash.x, targetFlash.y, targetFlash.z,
24, 0.35D, 0.45D, 0.35D, 0.04D);
}
@Override
public void onWindup(int elapsedTicks, int remainingTicks) {
if (target.isAlive() && target.level() == level) {
KingEntity.this.sendRoyalJudgmentTelegraph(level, target, elapsedTicks);
}
}
@Override
public void onActive(int elapsedTicks) {
if (target.isAlive() && target.level() == level) {
if (elapsedTicks == 0) {
Vec3 targetFlash = target.getEyePosition();
level.sendParticles(ParticleTypes.END_ROD, targetFlash.x, targetFlash.y, targetFlash.z,
32, 0.45D, 0.55D, 0.45D, 0.05D);
if (target instanceof ServerPlayer player) {
KingJudgmentFlashSync.send(player, 12);
player.playNotifySound(SoundEvents.BELL_BLOCK, SoundSource.HOSTILE, 0.8F, 1.65F);
}
target.hurt(KingEntity.this.damageSources().magic(),
KingEntity.this.scaleRoyalDamage(6.0D));
}
}
if (elapsedTicks % 4 == 0 && target.isAlive() && target.level() == level) {
KingEntity.this.fireRoyalJudgmentWave(level, target);
}
@@ -1680,6 +2087,35 @@ public class KingEntity extends RoyalBossEntity {
});
}
private void sendRoyalJudgmentTelegraph(ServerLevel level, LivingEntity target, int elapsedTicks) {
Vec3 targetPoint = target.position().add(AntarchyGravityRotationUtil.vecPlayerToWorld(
0.0D, 0.35D, 0.0D, AntarchyGravityApi.getGravityDirection(target)));
double pulse = 0.8D + Math.min(1.0D, elapsedTicks / 20.0D) * 0.5D;
for (RoyalHead.Slot slot : RoyalHead.Slot.values()) {
RoyalHead head = this.royalHead(slot);
Vec3 origin = this.headAnchor(head);
Vec3 direction = targetPoint.subtract(origin);
double length = direction.length();
if (length < 0.01D) {
continue;
}
direction = direction.scale(1.0D / length);
int samples = Math.max(4, (int) (length / 2.0D));
for (int sample = 1; sample <= samples; sample++) {
Vec3 point = origin.add(direction.scale(length * sample / samples));
level.sendParticles(ParticleTypes.ELECTRIC_SPARK, point.x, point.y, point.z,
1, 0.02D, 0.02D, 0.02D, 0.0D);
}
}
double radius = 1.4D + pulse * 0.8D;
for (int index = 0; index < 20; index++) {
double angle = Mth.TWO_PI * index / 20.0D;
Vec3 point = targetPoint.add(Math.cos(angle) * radius, 0.0D, Math.sin(angle) * radius);
level.sendParticles(ParticleTypes.ELECTRIC_SPARK, point.x, point.y, point.z,
1, 0.0D, 0.03D, 0.0D, 0.0D);
}
}
private void fireRoyalJudgmentWave(ServerLevel level, LivingEntity target) {
Vec3 predicted = target.getEyePosition().add(target.getDeltaMovement().scale(4.0D));
for (RoyalHead.Slot slot : RoyalHead.Slot.values()) {
@@ -1688,7 +2124,7 @@ public class KingEntity extends RoyalBossEntity {
Vec3 direction = predicted.subtract(origin).normalize();
RoyalBoltEntity bolt = new RoyalBoltEntity(AntarchyObjects.ROYAL_BOLT.get(), level);
bolt.setOwner(this);
bolt.setElement(RoyalBeamElement.QUEEN_PURPLE);
bolt.setElement(RoyalBeamElement.KING_GOLD);
bolt.setPos(origin.x, origin.y, origin.z);
bolt.shoot(direction.x, direction.y, direction.z, 1.55F, 1.0F);
level.addFreshEntity(bolt);
@@ -1710,7 +2146,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);
this.projectRoyalSound(AntarchySoundEvents.KING_JUDGEMENT.get(), 1.1F, 1.0F, target);
target.hurt(this.damageSources().magic(), this.scaleRoyalDamage(6.0D));
}
}
@@ -1734,7 +2170,7 @@ public class KingEntity extends RoyalBossEntity {
}
boolean decreeApplies = attacker instanceof ServerPlayer
&& this.decreeRecipients.contains(attacker.getUUID());
if (decreeApplies) {
if (decreeApplies && this.decreeResponseGraceTicks <= 0) {
if (this.activeDecree instanceof CloseQuartersDecree && projectile) {
this.failActiveDecree(attacker);
} else if (this.activeDecree instanceof ComeNoCloserDecree && !projectile) {
@@ -5,8 +5,10 @@ import com.craisinlord.antarchy.content.AntarchySoundEvents;
import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.content.AntarchyTags;
import com.craisinlord.antarchy.content.entity.ManticoreEntity;
import com.craisinlord.antarchy.content.entity.nightmare.NightmareBiteEntity;
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.content.gravity.AntarchyGravityTransition;
import com.craisinlord.antarchy.content.worldgen.thoraxis.ThoraxisUndersideManager;
import java.util.HashMap;
@@ -66,7 +68,6 @@ public class QueenEntity extends RoyalBossEntity {
private static final int MOMENTUM_LOCK_COOLDOWN = 420;
private static final int MOMENTUM_LOCK_DURATION = 90;
private static final double MOMENTUM_LOCK_RADIUS = 26.0D;
private static final float MOMENTUM_LOCK_EARLY_HEALTH_THRESHOLD = 0.80F;
private static final int CRUSHING_GRAVITY_COOLDOWN = 360;
private static final int CRUSHING_GRAVITY_DURATION = 120;
private static final double CRUSHING_GRAVITY_RADIUS = 18.0D;
@@ -75,13 +76,23 @@ 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 GRAVITY_STOMP_TELEGRAPH_TICKS = 22;
private static final int BLACK_HOLE_TELEGRAPH_TICKS = 24;
private static final int MOMENTUM_LOCK_TELEGRAPH_TICKS = 40;
private static final int CRUSHING_GRAVITY_TELEGRAPH_TICKS = 28;
private static final int ACCELERATION_TELEGRAPH_TICKS = 32;
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 static final int BLACK_HOLE_FOLLOW_UP_DELAY = 22;
private static final int CRUSHING_LANDING_DELAY = 40;
private static final int MOMENTUM_VOLLEY_DELAY = MOMENTUM_LOCK_DURATION + 5;
private static final int ACCELERATION_VOLLEY_DELAY = 18;
private static final int ACCELERATION_LANDING_DELAY = 55;
private static final int DIRECTED_LANDING_RETRY_TICKS = 60;
private int manticoreSummonCooldownTicks;
private int gravityStompCooldownTicks;
@@ -89,7 +100,13 @@ public class QueenEntity extends RoyalBossEntity {
private int crushingGravityCooldownTicks;
private int accelerationCooldownTicks;
private int blackHoleCooldownTicks;
private int gravityStompTelegraphTicks;
private int blackHoleTelegraphTicks;
private int momentumLockTelegraphTicks;
private int crushingGravityTelegraphTicks;
private int accelerationTelegraphTicks;
@Nullable
private Vec3 pendingBlackHoleAnchor;
private int queenLandingCooldownTicks = 300;
private int queenLandingStageTicks;
private QueenLandingStage queenLandingStage = QueenLandingStage.AERIAL;
@@ -97,6 +114,12 @@ public class QueenEntity extends RoyalBossEntity {
private Vec3 queenLandingWaypoint;
@Nullable
private Phase pendingPhaseSignature;
private QueenFollowUp queuedQueenFollowUp = QueenFollowUp.NONE;
private int queuedQueenFollowUpTicks;
private int queuedQueenLandingRetryTicks;
private int directedFireballVolleyTicks;
@Nullable
private String lastQueenMajorAttack;
private final int[] dreamFireballCooldownTicks = new int[3];
private int idleWanderCooldownTicks;
private boolean manticoreAnimationPending;
@@ -115,6 +138,13 @@ public class QueenEntity extends RoyalBossEntity {
TAKEOFF
}
private enum QueenFollowUp {
NONE,
FIREBALL_VOLLEY,
LANDING,
ACCELERATION_VOLLEY
}
public QueenEntity(EntityType<? extends QueenEntity> entityType, Level level) {
super(entityType, level);
}
@@ -148,13 +178,20 @@ public class QueenEntity extends RoyalBossEntity {
return this.queenLandingStage == QueenLandingStage.TELEGRAPH
|| this.queenLandingStage == QueenLandingStage.DESCENT
|| this.queenLandingStage == QueenLandingStage.IMPACT
|| this.queenLandingStage == QueenLandingStage.TAKEOFF;
|| this.queenLandingStage == QueenLandingStage.TAKEOFF
|| this.isQueenAbilityTelegraphActive();
}
@Override
protected boolean blocksRoyalBeamScheduling() {
return this.directedFireballVolleyTicks > 0;
}
@Override
protected void onRoyalPhaseTransitionStarted(Phase previousPhase, Phase nextPhase) {
this.resetQueenLandingSequence();
this.momentumLockTelegraphTicks = 0;
this.clearQueenAbilityTelegraphs();
this.clearQueenAttackDirector();
this.pendingPhaseSignature = null;
this.getNavigation().stop();
this.setDeltaMovement(this.getDeltaMovement().scale(0.35D));
@@ -191,7 +228,7 @@ public class QueenEntity extends RoyalBossEntity {
this.queenLandingCooldownTicks = Math.min(this.queenLandingCooldownTicks, 100);
} else if (nextPhase == Phase.THREE) {
this.accelerationCooldownTicks = 0;
this.startRoyalAcceleration();
this.pendingPhaseSignature = Phase.THREE;
this.queenLandingCooldownTicks = 0;
}
}
@@ -334,12 +371,14 @@ public class QueenEntity extends RoyalBossEntity {
}
LivingEntity target = this.getTarget();
if (target == null || !target.isAlive() || !this.canAttack(target)) {
this.clearQueenAttackDirector();
this.resetQueenLandingSequence();
this.tickIdleWander();
} else {
this.tickQueenAttackDirector(target);
this.tickQueenLandingSequence(target);
}
this.tickMomentumLockTelegraph();
this.tickQueenAbilityTelegraphs();
if (this.blocksRoyalAttacksForMovement()) {
this.royalEffects.tick();
return;
@@ -347,13 +386,19 @@ public class QueenEntity extends RoyalBossEntity {
this.tickManticoreSummon();
this.tickPendingManticoreAnimation();
this.tickQueenAbilities();
if (this.isQueenAbilityTelegraphActive()) {
this.royalEffects.tick();
return;
}
this.tickDreamFireballs();
this.tickDirectedFireballVolley();
this.tickFinalAcceleration();
this.royalEffects.tick();
}
private void tickQueenLandingSequence(LivingEntity target) {
if (this.queenLandingStage == QueenLandingStage.AERIAL && this.isRoyalFlying()
&& !this.isQueenAbilityTelegraphActive()
&& this.queenLandingCooldownTicks-- <= 0 && target.onGround()) {
this.beginQueenLanding(target);
}
@@ -435,7 +480,7 @@ public class QueenEntity extends RoyalBossEntity {
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()
? target.position().y - 0.1D
: this.groundYBelow(x, z) + 0.1D;
Vec3 candidate = new Vec3(x, y, z);
AABB box = this.getBoundingBox().move(
@@ -445,6 +490,15 @@ public class QueenEntity extends RoyalBossEntity {
}
}
}
double fallbackY = inverted
? target.position().y - 0.1D
: this.groundYBelow(target.getX(), target.getZ()) + 0.1D;
Vec3 fallback = new Vec3(target.getX(), fallbackY, target.getZ());
AABB fallbackBox = this.getBoundingBox().move(
fallback.x - this.getX(), fallback.y - this.getY(), fallback.z - this.getZ());
if (this.level().noCollision(this, fallbackBox)) {
return fallback;
}
return null;
}
@@ -456,13 +510,13 @@ public class QueenEntity extends RoyalBossEntity {
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);
Vec3 indicator = this.queenLandingWaypoint.add(AntarchyGravityRotationUtil.vecPlayerToWorld(
0.0D, 0.2D, 0.0D, AntarchyGravityApi.getGravityDirection(this)));
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,
indicator.y,
this.queenLandingWaypoint.z + Math.sin(angle) * radius,
1, 0.0D, 0.0D, 0.0D, 0.0D);
}
@@ -474,13 +528,14 @@ public class QueenEntity extends RoyalBossEntity {
this.triggerAnim("wing_action", "wing_gust");
this.playRoyalSound(AntarchySoundEvents.QUEEN_FLY_LOOP.get(), 0.9F);
if (this.level() instanceof ServerLevel level) {
this.spawnQueenWingPressure(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.addGravityAwareImpulse(this, AntarchyGravityRotationUtil.vecPlayerToWorld(
0.0D, 0.42D, 0.0D, AntarchyGravityApi.getGravityDirection(this)));
this.hasImpulse = true;
}
@@ -507,9 +562,20 @@ public class QueenEntity extends RoyalBossEntity {
this.dreamFireballCooldownTicks[index]--;
continue;
}
if (!head.readyToAttack() || this.distanceTo(target) < 12.0D || this.distanceTo(target) > 52.0D) continue;
double minimumRange = this.directedFireballVolleyTicks > 0 ? 6.0D : 12.0D;
double maximumRange = this.directedFireballVolleyTicks > 0 ? 64.0D : 52.0D;
if (!head.readyToAttack() || this.distanceTo(target) < minimumRange
|| this.distanceTo(target) > maximumRange) continue;
int cooldown = this.cooldown(180);
RoyalHead.Slot slot = head.slot();
LivingEntity fireballTarget = target;
if (this.directedFireballVolleyTicks > 0) {
LivingEntity assignedTarget = head.target(level);
if (assignedTarget != null && this.canAttack(assignedTarget)) {
fireballTarget = assignedTarget;
}
}
final LivingEntity committedTarget = fireballTarget;
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() {
@@ -518,7 +584,7 @@ public class QueenEntity extends RoyalBossEntity {
}
@Override public void onActive(int elapsedTicks) {
Vec3 origin = QueenEntity.this.headAnchor(head);
Vec3 direction = target.getEyePosition().subtract(origin).normalize();
Vec3 direction = committedTarget.getEyePosition().subtract(origin).normalize();
RoyalElementalProjectileEntity projectile = RoyalElementalProjectileEntity.create(
level, QueenEntity.this, RoyalBeamElement.DREAM_FIRE, origin, direction);
level.addFreshEntity(projectile);
@@ -533,6 +599,84 @@ public class QueenEntity extends RoyalBossEntity {
}
}
private void tickQueenAttackDirector(LivingEntity target) {
if (this.queuedQueenFollowUp == QueenFollowUp.NONE) {
return;
}
if (this.queuedQueenFollowUpTicks-- > 0) {
return;
}
QueenFollowUp followUp = this.queuedQueenFollowUp;
switch (followUp) {
case FIREBALL_VOLLEY -> {
this.clearQueuedQueenFollowUp();
this.primeDirectedFireballVolley();
}
case LANDING -> {
if (this.queenLandingStage == QueenLandingStage.AERIAL
&& this.isRoyalFlying() && target.onGround()) {
this.clearQueuedQueenFollowUp();
this.queenLandingCooldownTicks = 0;
} else if (--this.queuedQueenLandingRetryTicks <= 0) {
this.clearQueuedQueenFollowUp();
} else {
this.queuedQueenFollowUpTicks = 0;
}
}
case ACCELERATION_VOLLEY -> {
this.clearQueuedQueenFollowUp();
this.primeDirectedFireballVolley();
this.queueQueenFollowUp(QueenFollowUp.LANDING, ACCELERATION_LANDING_DELAY);
}
case NONE -> {
}
}
}
private void queueQueenFollowUp(QueenFollowUp followUp, int delayTicks) {
this.queuedQueenFollowUp = followUp;
this.queuedQueenFollowUpTicks = Math.max(0, delayTicks);
this.queuedQueenLandingRetryTicks = followUp == QueenFollowUp.LANDING
? DIRECTED_LANDING_RETRY_TICKS : 0;
}
private void clearQueuedQueenFollowUp() {
this.queuedQueenFollowUp = QueenFollowUp.NONE;
this.queuedQueenFollowUpTicks = 0;
this.queuedQueenLandingRetryTicks = 0;
}
private void clearQueenAttackDirector() {
this.clearQueuedQueenFollowUp();
this.directedFireballVolleyTicks = 0;
}
private void primeDirectedFireballVolley() {
this.directedFireballVolleyTicks = 60;
this.stopRoyalBeamsForDirectedAttack();
for (RoyalHead.Slot slot : RoyalHead.Slot.values()) {
int index = slot.ordinal();
this.dreamFireballCooldownTicks[index] = 0;
this.attackScheduler.resetCooldown("dream_fireball_" + slot.name());
}
}
private void tickDirectedFireballVolley() {
if (this.directedFireballVolleyTicks <= 0 || this.isRoyalRecoveryActive()) {
return;
}
boolean allCommitted = true;
for (int cooldown : this.dreamFireballCooldownTicks) {
if (cooldown <= 0) {
allCommitted = false;
break;
}
}
if (allCommitted || --this.directedFireballVolleyTicks <= 0) {
this.directedFireballVolleyTicks = 0;
}
}
private void tickIdleWander() {
if (this.idleWanderCooldownTicks-- > 0) {
return;
@@ -716,23 +860,27 @@ public class QueenEntity extends RoyalBossEntity {
if (target == null || !target.isAlive()) {
return;
}
if (this.queuedQueenFollowUp != QueenFollowUp.NONE || this.directedFireballVolleyTicks > 0) {
return;
}
Phase phase = this.phase();
boolean majorBusy = this.royalEffects.active("momentum_lock") || this.royalEffects.active("crushing_gravity");
boolean allowOverlap = phase != Phase.ONE;
String[] candidates = phase == Phase.ONE && !this.isMomentumLockAvailableEarly()
? new String[] {"gravity_stomp", "black_hole"}
: new String[] {"gravity_stomp", "black_hole", "momentum_lock", "crushing_gravity", "acceleration"};
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;
case "momentum_lock", "acceleration" -> phase == Phase.THREE ? 4 : 2;
default -> 4;
boolean majorBusy = this.royalEffects.active("momentum_lock")
|| this.royalEffects.active("crushing_gravity")
|| this.royalEffects.active("acceleration");
String[] candidates = switch (phase) {
case ONE -> new String[] {"gravity_stomp", "black_hole"};
case TWO -> new String[] {"gravity_stomp", "black_hole", "momentum_lock", "crushing_gravity"};
case THREE -> new String[] {"gravity_stomp", "black_hole", "momentum_lock", "crushing_gravity", "acceleration"};
};
String forcedAttack = this.pendingPhaseSignature == Phase.TWO ? "momentum_lock"
: this.pendingPhaseSignature == Phase.THREE ? "acceleration" : null;
String selected = forcedAttack != null
&& this.queenAttackReady(forcedAttack, phase, majorBusy)
? forcedAttack : this.attackScheduler.chooseWeighted(candidates, id -> {
if (!this.queenAttackReady(id, phase, majorBusy)
|| id.equals(this.lastQueenMajorAttack)) return 0;
return this.queenAttackWeight(id, phase);
}, this.random);
if (selected == null) return;
int cooldown = switch (selected) {
@@ -743,27 +891,30 @@ public class QueenEntity extends RoyalBossEntity {
default -> ACCELERATION_COOLDOWN;
};
RoyalAttackLane lane = "black_hole".equals(selected) ? RoyalAttackLane.CENTER_HEAD : RoyalAttackLane.HAZARD;
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);
int actionWindup = this.queenAbilityWindup(selected);
int actionAnimationTicks = "momentum_lock".equals(selected)
|| "crushing_gravity".equals(selected) || "acceleration".equals(selected) ? 55 : 25;
int actionRecovery = this.animationRecovery(actionAnimationTicks, actionWindup, 1, 14);
if (!this.beginRoyalAttack(selected, lane, this.cooldown(cooldown),
actionWindup, 1, actionRecovery, 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 if ("momentum_lock".equals(selected)) {
} else if ("momentum_lock".equals(selected)
|| "crushing_gravity".equals(selected)
|| "acceleration".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");
}
QueenEntity.this.startQueenAbilityTelegraph(selected, target);
}
@Override public void onActive(int elapsedTicks) {
if ("momentum_lock".equals(selected)) {
QueenEntity.this.momentumLockTelegraphTicks = 0;
QueenEntity.this.finishQueenAbilityTelegraph(selected);
if ("momentum_lock".equals(selected) || "crushing_gravity".equals(selected)
|| "acceleration".equals(selected)) {
QueenEntity.this.spawnQueenWingPressure(level);
}
QueenEntity.this.performSelectedQueenAttack(selected, cooldown, level, target);
}
@@ -773,7 +924,8 @@ public class QueenEntity extends RoyalBossEntity {
}
}
})) return;
if ("momentum_lock".equals(selected)) {
this.lastQueenMajorAttack = selected;
if (selected.equals(forcedAttack)) {
this.pendingPhaseSignature = null;
}
}
@@ -781,23 +933,65 @@ public class QueenEntity extends RoyalBossEntity {
private void performSelectedQueenAttack(String selected, int cooldown,
ServerLevel level, LivingEntity target) {
switch (selected) {
case "gravity_stomp" -> { this.gravityStompCooldownTicks = cooldown; this.performGravityStomp(level, target); }
case "black_hole" -> { this.blackHoleCooldownTicks = cooldown; this.castBlackHole(level, target); }
case "momentum_lock" -> { this.momentumLockCooldownTicks = cooldown; this.startMomentumLock(level); }
case "gravity_stomp" -> {
this.gravityStompCooldownTicks = cooldown;
this.performGravityStomp(level, target, false);
this.startRoyalRecovery(18);
}
case "black_hole" -> {
this.blackHoleCooldownTicks = cooldown;
this.castBlackHole(level, target);
this.startRoyalRecovery(16);
if (this.phase() == Phase.ONE) {
this.queueQueenFollowUp(QueenFollowUp.FIREBALL_VOLLEY, BLACK_HOLE_FOLLOW_UP_DELAY);
}
}
case "momentum_lock" -> {
this.momentumLockCooldownTicks = cooldown;
this.startMomentumLock(level);
this.queueQueenFollowUp(QueenFollowUp.FIREBALL_VOLLEY, MOMENTUM_VOLLEY_DELAY);
}
case "crushing_gravity" -> {
this.crushingGravityCooldownTicks = cooldown;
this.royalEffects.start("crushing_gravity", CRUSHING_GRAVITY_DURATION,
() -> {}, this::tickCrushingGravity, () -> this.startRoyalRecovery(35));
this.playRoyalSound(AntarchySoundEvents.QUEEN_ROAR.get(), 0.6F);
if (this.phase() == Phase.TWO) {
this.queueQueenFollowUp(QueenFollowUp.LANDING, CRUSHING_LANDING_DELAY);
}
}
case "acceleration" -> {
this.accelerationCooldownTicks = cooldown;
this.startRoyalAcceleration();
this.queueQueenFollowUp(QueenFollowUp.ACCELERATION_VOLLEY, ACCELERATION_VOLLEY_DELAY);
}
case "acceleration" -> { this.accelerationCooldownTicks = cooldown; this.startRoyalAcceleration(); }
}
}
private boolean queenAttackReady(String id, Phase phase, boolean majorBusy, boolean allowOverlap) {
if (!allowOverlap && majorBusy) return false;
private void spawnQueenWingPressure(ServerLevel level) {
AntarchyGravityDirection gravity = AntarchyGravityApi.getGravityDirection(this);
for (int ring = 1; ring <= 3; ring++) {
double radius = ring * 3.2D;
int samples = 14 + ring * 4;
for (int sample = 0; sample < samples; sample++) {
double angle = Mth.TWO_PI * sample / samples;
double localX = Math.cos(angle) * radius;
double localZ = Math.sin(angle) * radius;
Vec3 point = this.position().add(AntarchyGravityRotationUtil.vecPlayerToWorld(
localX, this.getBbHeight() * 0.45D, localZ, gravity));
Vec3 velocity = AntarchyGravityRotationUtil.vecPlayerToWorld(
Math.cos(angle) * 0.2D, 0.025D, Math.sin(angle) * 0.2D, gravity);
level.sendParticles(ACCEL_DUST, point.x, point.y, point.z,
0, velocity.x, velocity.y, velocity.z, 1.0D);
}
}
}
private boolean queenAttackReady(String id, Phase phase, boolean majorBusy) {
if (majorBusy) return false;
return switch (id) {
case "gravity_stomp" -> this.gravityStompCooldownTicks <= 0;
case "gravity_stomp" -> this.gravityStompCooldownTicks <= 0
&& !this.isRoyalFlying() && this.queenLandingStage == QueenLandingStage.GROUND_COMBAT;
case "black_hole" -> this.blackHoleCooldownTicks <= 0
&& this.royalHead(RoyalHead.Slot.CENTER).readyToAttack()
&& !this.attackScheduler.laneBusy(RoyalAttackLane.CENTER_HEAD);
@@ -808,9 +1002,23 @@ public class QueenEntity extends RoyalBossEntity {
};
}
private boolean isMomentumLockAvailableEarly() {
return this.getMaxHealth() > 0.0F
&& this.getHealth() / this.getMaxHealth() <= MOMENTUM_LOCK_EARLY_HEALTH_THRESHOLD;
private int queenAttackWeight(String id, Phase phase) {
return switch (phase) {
case ONE -> "black_hole".equals(id) ? 6 : 3;
case TWO -> switch (id) {
case "momentum_lock", "crushing_gravity" -> 6;
case "gravity_stomp" -> 3;
case "black_hole" -> 2;
default -> 0;
};
case THREE -> switch (id) {
case "acceleration" -> 7;
case "black_hole", "crushing_gravity" -> 5;
case "gravity_stomp" -> 4;
case "momentum_lock" -> 2;
default -> 0;
};
};
}
private void tickFinalAcceleration() {
@@ -818,12 +1026,18 @@ public class QueenEntity extends RoyalBossEntity {
return;
}
if (!this.royalEffects.active("acceleration") && this.accelerationCooldownTicks <= 0) {
this.startRoyalAcceleration();
this.pendingPhaseSignature = Phase.THREE;
}
}
private void performGravityStomp(ServerLevel level, LivingEntity target) {
this.performGravityStomp(level, target, true);
}
private void performGravityStomp(ServerLevel level, LivingEntity target, boolean triggerAnimation) {
if (triggerAnimation) {
this.triggerAnim("body_action", "stomp");
}
this.playRoyalSound(AntarchySoundEvents.QUEEN_ROAR.get(), 0.7F);
DamageSource source = this.damageSources().mobAttack(this);
for (LivingEntity living : level.getEntitiesOfClass(LivingEntity.class,
@@ -831,7 +1045,9 @@ public class QueenEntity extends RoyalBossEntity {
double distance = Math.max(1.0D, living.distanceTo(this));
living.hurt(source, this.scaleRoyalDamage(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));
Vec3 vertical = AntarchyGravityRotationUtil.vecPlayerToWorld(
0.0D, 0.65D, 0.0D, AntarchyGravityApi.getGravityDirection(living));
this.addGravityAwareImpulse(living, new Vec3(push.x, 0.0D, push.z).add(vertical));
living.hasImpulse = true;
}
level.sendParticles(ParticleTypes.EXPLOSION, this.getX(), this.getY() + 1.0D, this.getZ(),
@@ -839,14 +1055,9 @@ public class QueenEntity extends RoyalBossEntity {
}
private void castBlackHole(ServerLevel level, LivingEntity target) {
double vertical = 2.0D + this.random.nextDouble() * 2.0D;
if (AntarchyGravityApi.isGravityInverted(this)) {
vertical = -vertical;
}
Vec3 anchor = target.position().add(
(this.random.nextDouble() - 0.5D) * 4.0D,
vertical,
(this.random.nextDouble() - 0.5D) * 4.0D);
Vec3 anchor = this.pendingBlackHoleAnchor != null
? this.pendingBlackHoleAnchor : this.selectBlackHoleAnchor(target);
this.pendingBlackHoleAnchor = null;
Vec3 mouth = this.beamAnchor(this.royalHead(RoyalHead.Slot.CENTER));
RoyalBlackHoleEntity hole = RoyalBlackHoleEntity.createProjectile(level, mouth, anchor, this.getUUID());
level.addFreshEntity(hole);
@@ -855,40 +1066,136 @@ 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)) {
private Vec3 selectBlackHoleAnchor(LivingEntity target) {
double vertical = 2.0D + this.random.nextDouble() * 2.0D;
if (AntarchyGravityApi.isGravityInverted(this)) {
vertical = -vertical;
}
return target.position().add(
(this.random.nextDouble() - 0.5D) * 4.0D,
vertical,
(this.random.nextDouble() - 0.5D) * 4.0D);
}
private int queenAbilityWindup(String ability) {
return switch (ability) {
case "gravity_stomp" -> GRAVITY_STOMP_TELEGRAPH_TICKS;
case "black_hole" -> BLACK_HOLE_TELEGRAPH_TICKS;
case "momentum_lock" -> MOMENTUM_LOCK_TELEGRAPH_TICKS;
case "crushing_gravity" -> CRUSHING_GRAVITY_TELEGRAPH_TICKS;
case "acceleration" -> ACCELERATION_TELEGRAPH_TICKS;
default -> 10;
};
}
private void startQueenAbilityTelegraph(String ability, LivingEntity target) {
switch (ability) {
case "gravity_stomp" -> {
this.gravityStompTelegraphTicks = GRAVITY_STOMP_TELEGRAPH_TICKS;
this.playRoyalSound(AntarchySoundEvents.QUEEN_ROAR.get(), 0.82F);
}
case "black_hole" -> {
this.pendingBlackHoleAnchor = this.selectBlackHoleAnchor(target);
this.blackHoleTelegraphTicks = BLACK_HOLE_TELEGRAPH_TICKS;
this.playRoyalSound(AntarchySoundEvents.QUEEN_ROAR.get(), 0.48F);
}
case "momentum_lock" -> {
this.momentumLockTelegraphTicks = MOMENTUM_LOCK_TELEGRAPH_TICKS;
this.playRoyalSound(AntarchySoundEvents.QUEEN_ROAR.get(), 0.58F);
}
case "crushing_gravity" -> {
this.crushingGravityTelegraphTicks = CRUSHING_GRAVITY_TELEGRAPH_TICKS;
this.playRoyalSound(AntarchySoundEvents.QUEEN_ROAR.get(), 0.42F);
}
case "acceleration" -> {
this.accelerationTelegraphTicks = ACCELERATION_TELEGRAPH_TICKS;
this.playRoyalSound(AntarchySoundEvents.QUEEN_ROAR.get(), 1.22F);
}
}
}
private void finishQueenAbilityTelegraph(String ability) {
switch (ability) {
case "gravity_stomp" -> this.gravityStompTelegraphTicks = 0;
case "black_hole" -> this.blackHoleTelegraphTicks = 0;
case "momentum_lock" -> this.momentumLockTelegraphTicks = 0;
case "crushing_gravity" -> this.crushingGravityTelegraphTicks = 0;
case "acceleration" -> this.accelerationTelegraphTicks = 0;
}
}
private boolean isQueenAbilityTelegraphActive() {
return this.gravityStompTelegraphTicks > 0 || this.blackHoleTelegraphTicks > 0
|| this.momentumLockTelegraphTicks > 0 || this.crushingGravityTelegraphTicks > 0
|| this.accelerationTelegraphTicks > 0;
}
private void clearQueenAbilityTelegraphs() {
this.gravityStompTelegraphTicks = 0;
this.blackHoleTelegraphTicks = 0;
this.momentumLockTelegraphTicks = 0;
this.crushingGravityTelegraphTicks = 0;
this.accelerationTelegraphTicks = 0;
this.pendingBlackHoleAnchor = null;
}
private void tickQueenAbilityTelegraphs() {
if (!this.isQueenAbilityTelegraphActive() || !(this.level() instanceof ServerLevel level)) {
return;
}
this.getNavigation().stop();
this.setDeltaMovement(Vec3.ZERO);
this.tickGravityStompTelegraph(level);
this.tickBlackHoleTelegraph(level);
this.tickMomentumLockTelegraph(level);
this.tickCrushingGravityTelegraph(level);
this.tickAccelerationTelegraph(level);
}
private void tickGravityStompTelegraph(ServerLevel level) {
if (this.gravityStompTelegraphTicks <= 0) return;
int elapsed = GRAVITY_STOMP_TELEGRAPH_TICKS - this.gravityStompTelegraphTicks;
if (this.tickCount % 2 == 0) {
double radius = Mth.lerp(elapsed / (double) GRAVITY_STOMP_TELEGRAPH_TICKS, 3.0D, 18.0D);
Vec3 surfaceCenter = this.position().add(AntarchyGravityRotationUtil.vecPlayerToWorld(
0.0D, 0.2D, 0.0D, AntarchyGravityApi.getGravityDirection(this)));
this.spawnQueenTelegraphSpokes(level, surfaceCenter, radius);
}
this.playQueenTelegraphBeat(level, this.gravityStompTelegraphTicks,
GRAVITY_STOMP_TELEGRAPH_TICKS, 0.72F);
this.gravityStompTelegraphTicks--;
}
private void tickBlackHoleTelegraph(ServerLevel level) {
if (this.blackHoleTelegraphTicks <= 0 || this.pendingBlackHoleAnchor == null) return;
int elapsed = BLACK_HOLE_TELEGRAPH_TICKS - this.blackHoleTelegraphTicks;
double progress = elapsed / (double) BLACK_HOLE_TELEGRAPH_TICKS;
double fieldRadius = Math.max(4.0D, AntarchySettings.queenBlackHoleRadius());
if (this.tickCount % 2 == 0) {
this.spawnQueenTelegraphRing(level, this.pendingBlackHoleAnchor, fieldRadius, 40, ParticleTypes.WITCH);
this.spawnBlackHoleFunnel(level, this.pendingBlackHoleAnchor, fieldRadius, progress);
level.sendParticles(AntarchyObjects.LUCID_BOLT_IMPACT_LARGE.get(),
this.pendingBlackHoleAnchor.x, this.pendingBlackHoleAnchor.y, this.pendingBlackHoleAnchor.z,
2, 0.12D, 0.12D, 0.12D, 0.01D);
}
this.playQueenTelegraphBeat(level, this.blackHoleTelegraphTicks,
BLACK_HOLE_TELEGRAPH_TICKS, 0.48F);
this.blackHoleTelegraphTicks--;
}
private void tickMomentumLockTelegraph(ServerLevel level) {
if (this.momentumLockTelegraphTicks <= 0) return;
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);
this.spawnMomentumLockCage(level, elapsed / (double) MOMENTUM_LOCK_TELEGRAPH_TICKS);
}
}
}
if (this.momentumLockTelegraphTicks <= 10) {
if (this.momentumLockTelegraphTicks <= 14) {
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);
this.spawnMomentumTargetCage(level, living);
if (this.momentumLockTelegraphTicks == 12) {
NightmareBiteEntity.spawnTelegraphAt(level, living, true);
}
}
}
if (this.momentumLockTelegraphTicks == 40 || this.momentumLockTelegraphTicks == 20
@@ -900,6 +1207,164 @@ public class QueenEntity extends RoyalBossEntity {
this.momentumLockTelegraphTicks--;
}
private void tickCrushingGravityTelegraph(ServerLevel level) {
if (this.crushingGravityTelegraphTicks <= 0) return;
int elapsed = CRUSHING_GRAVITY_TELEGRAPH_TICKS - this.crushingGravityTelegraphTicks;
double progress = elapsed / (double) CRUSHING_GRAVITY_TELEGRAPH_TICKS;
if (this.tickCount % 2 == 0) {
for (LivingEntity living : level.getEntitiesOfClass(LivingEntity.class,
this.getBoundingBox().inflate(CRUSHING_GRAVITY_RADIUS), this::canDamageWithRoyalAttack)) {
double downSign = AntarchyGravityApi.isGravityInverted(living) ? 1.0D : -1.0D;
this.spawnCrushingGravityArrow(level, living, downSign, progress);
}
}
if (elapsed % 7 == 0) {
level.playSound(null, this.blockPosition(), SoundEvents.BEACON_AMBIENT,
SoundSource.HOSTILE, 1.8F, 0.55F + (float) progress * 0.35F);
}
this.crushingGravityTelegraphTicks--;
}
private void tickAccelerationTelegraph(ServerLevel level) {
if (this.accelerationTelegraphTicks <= 0) return;
int elapsed = ACCELERATION_TELEGRAPH_TICKS - this.accelerationTelegraphTicks;
double progress = elapsed / (double) ACCELERATION_TELEGRAPH_TICKS;
this.spawnAccelerationHelix(level, progress);
this.playQueenTelegraphBeat(level, this.accelerationTelegraphTicks,
ACCELERATION_TELEGRAPH_TICKS, 1.0F);
this.accelerationTelegraphTicks--;
}
private void spawnQueenTelegraphSpokes(ServerLevel level, Vec3 center, double radius) {
int spokes = 10;
int segments = 6;
for (int spoke = 0; spoke < spokes; spoke++) {
double angle = Mth.TWO_PI * spoke / spokes;
for (int segment = 1; segment <= segments; segment++) {
double distance = radius * segment / segments;
level.sendParticles(ParticleTypes.CRIT,
center.x + Math.cos(angle) * distance, center.y,
center.z + Math.sin(angle) * distance,
1, 0.08D, 0.02D, 0.08D, 0.0D);
}
}
level.sendParticles(ParticleTypes.LARGE_SMOKE, center.x, center.y, center.z,
3, 0.7D, 0.05D, 0.7D, 0.015D);
}
private void spawnBlackHoleFunnel(ServerLevel level, Vec3 center, double fieldRadius, double progress) {
double rotation = this.tickCount * 0.35D;
AntarchyGravityDirection gravity = AntarchyGravityApi.getGravityDirection(this);
for (int tier = 0; tier < 4; tier++) {
double tierProgress = tier / 3.0D;
double radius = Mth.lerp(progress, fieldRadius * (0.25D + tierProgress * 0.5D), 0.7D);
double localY = -1.5D + tierProgress * 4.5D;
for (int point = 0; point < 10; point++) {
double angle = rotation + tier * 0.65D + Mth.TWO_PI * point / 10.0D;
Vec3 particlePos = center.add(AntarchyGravityRotationUtil.vecPlayerToWorld(
Math.cos(angle) * radius, localY, Math.sin(angle) * radius, gravity));
level.sendParticles(ParticleTypes.REVERSE_PORTAL,
particlePos.x, particlePos.y, particlePos.z,
1, 0.0D, 0.0D, 0.0D, 0.0D);
}
}
}
private void spawnMomentumLockCage(ServerLevel level, double progress) {
double halfHeight = Mth.lerp(progress, 1.5D, 6.0D);
double centerY = this.getY(0.45D);
for (int pillar = 0; pillar < 8; pillar++) {
double angle = Mth.TWO_PI * pillar / 8.0D;
double x = this.getX() + Math.cos(angle) * MOMENTUM_LOCK_RADIUS;
double z = this.getZ() + Math.sin(angle) * MOMENTUM_LOCK_RADIUS;
for (int segment = 0; segment < 7; segment++) {
double y = centerY - halfHeight + segment * halfHeight / 3.0D;
level.sendParticles(ParticleTypes.END_ROD, x, y, z,
1, 0.0D, 0.0D, 0.0D, 0.0D);
}
}
}
private void spawnMomentumTargetCage(ServerLevel level, LivingEntity living) {
double centerY = living.getY(0.5D);
double halfHeight = living.getBbHeight() * 0.7D + 0.6D;
for (int corner = 0; corner < 4; corner++) {
double angle = Math.PI / 4.0D + Math.PI / 2.0D * corner;
double x = living.getX() + Math.cos(angle) * 0.9D;
double z = living.getZ() + Math.sin(angle) * 0.9D;
for (int segment = -1; segment <= 1; segment++) {
level.sendParticles(ParticleTypes.END_ROD, x,
centerY + segment * halfHeight, z,
1, 0.0D, 0.0D, 0.0D, 0.0D);
}
}
}
private void spawnCrushingGravityArrow(ServerLevel level, LivingEntity living,
double downSign, double progress) {
double centerY = living.getY(0.5D);
double shaftStartY = centerY - downSign * Mth.lerp(progress, 7.0D, 4.5D);
for (int segment = 0; segment < 6; segment++) {
level.sendParticles(ParticleTypes.REVERSE_PORTAL, living.getX(),
shaftStartY + downSign * segment * 0.75D, living.getZ(),
1, 0.04D, 0.02D, 0.04D, 0.0D);
}
double tipY = shaftStartY + downSign * 4.5D;
double headY = tipY - downSign * 0.85D;
level.sendParticles(ParticleTypes.WITCH, living.getX() + 0.7D, headY, living.getZ(),
1, 0.0D, 0.0D, 0.0D, 0.0D);
level.sendParticles(ParticleTypes.WITCH, living.getX() - 0.7D, headY, living.getZ(),
1, 0.0D, 0.0D, 0.0D, 0.0D);
level.sendParticles(ParticleTypes.WITCH, living.getX(), headY, living.getZ() + 0.7D,
1, 0.0D, 0.0D, 0.0D, 0.0D);
level.sendParticles(ParticleTypes.WITCH, living.getX(), headY, living.getZ() - 0.7D,
1, 0.0D, 0.0D, 0.0D, 0.0D);
level.sendParticles(AntarchyObjects.LUCID_BOLT_IMPACT_SMALL.get(),
living.getX(), tipY, living.getZ(), 1, 0.05D, 0.05D, 0.05D, 0.0D);
}
private void spawnAccelerationHelix(ServerLevel level, double progress) {
double rotation = this.tickCount * Mth.lerp(progress, 0.24D, 0.62D);
double radius = Mth.lerp(progress, 2.8D, 0.75D);
AntarchyGravityDirection gravity = AntarchyGravityApi.getGravityDirection(this);
for (int strand = 0; strand < 2; strand++) {
for (int segment = 0; segment < 9; segment++) {
double heightProgress = segment / 8.0D;
double angle = rotation + strand * Math.PI + heightProgress * Mth.TWO_PI * 1.5D;
double localY = 0.4D + heightProgress * (this.getBbHeight() + 2.0D);
Vec3 particlePos = this.position().add(AntarchyGravityRotationUtil.vecPlayerToWorld(
Math.cos(angle) * radius, localY, Math.sin(angle) * radius, gravity));
level.sendParticles(AntarchyObjects.LUCID_BOLT_IMPACT_SMALL.get(),
particlePos.x, particlePos.y, particlePos.z,
1, 0.02D, 0.02D, 0.02D, 0.0D);
if (segment % 2 == 0) {
level.sendParticles(ACCEL_DUST, particlePos.x, particlePos.y, particlePos.z,
1, 0.0D, 0.0D, 0.0D, 0.0D);
}
}
}
}
private <T extends net.minecraft.core.particles.ParticleOptions> void spawnQueenTelegraphRing(
ServerLevel level, Vec3 center, double radius, int count, T particle) {
for (int index = 0; index < count; index++) {
double angle = Mth.TWO_PI * index / count;
level.sendParticles(particle,
center.x + Math.cos(angle) * radius, center.y,
center.z + Math.sin(angle) * radius,
1, 0.0D, 0.0D, 0.0D, 0.0D);
}
}
private void playQueenTelegraphBeat(ServerLevel level, int remainingTicks, int duration, float basePitch) {
if (remainingTicks == duration || remainingTicks == duration / 2
|| remainingTicks == 8 || remainingTicks == 3) {
float progress = 1.0F - remainingTicks / (float) duration;
level.playSound(null, this.blockPosition(), SoundEvents.BEACON_POWER_SELECT,
SoundSource.HOSTILE, 2.0F, basePitch + progress * 0.7F);
}
}
private void startMomentumLock(ServerLevel level) {
this.momentumLockCooldownTicks = MOMENTUM_LOCK_COOLDOWN;
this.royalEffects.start("momentum_lock", MOMENTUM_LOCK_DURATION, () -> {
@@ -1061,6 +1526,13 @@ public class QueenEntity extends RoyalBossEntity {
tag.putInt("AccelerationCooldownTicks", this.accelerationCooldownTicks);
tag.putInt("BlackHoleCooldownTicks", this.blackHoleCooldownTicks);
tag.putInt("QueenLandingCooldownTicks", this.queenLandingCooldownTicks);
tag.putString("QueuedQueenFollowUp", this.queuedQueenFollowUp.name());
tag.putInt("QueuedQueenFollowUpTicks", this.queuedQueenFollowUpTicks);
tag.putInt("QueuedQueenLandingRetryTicks", this.queuedQueenLandingRetryTicks);
tag.putInt("DirectedFireballVolleyTicks", this.directedFireballVolleyTicks);
if (this.lastQueenMajorAttack != null) {
tag.putString("LastQueenMajorAttack", this.lastQueenMajorAttack);
}
if (this.naturalTrailHome != null) {
tag.putLong("NaturalTrailSiteId", this.naturalTrailSiteId);
tag.putLong("NaturalTrailHome", this.naturalTrailHome.asLong());
@@ -1078,6 +1550,21 @@ public class QueenEntity extends RoyalBossEntity {
this.blackHoleCooldownTicks = Math.max(0, tag.getInt("BlackHoleCooldownTicks"));
this.queenLandingCooldownTicks = tag.contains("QueenLandingCooldownTicks")
? Math.max(0, tag.getInt("QueenLandingCooldownTicks")) : 300;
if (tag.contains("QueuedQueenFollowUp")) {
try {
this.queuedQueenFollowUp = QueenFollowUp.valueOf(tag.getString("QueuedQueenFollowUp"));
} catch (IllegalArgumentException ignored) {
this.queuedQueenFollowUp = QueenFollowUp.NONE;
}
}
this.queuedQueenFollowUpTicks = Math.max(0, tag.getInt("QueuedQueenFollowUpTicks"));
this.queuedQueenLandingRetryTicks = Math.max(0, tag.getInt("QueuedQueenLandingRetryTicks"));
if (this.queuedQueenFollowUp == QueenFollowUp.LANDING && this.queuedQueenLandingRetryTicks == 0) {
this.queuedQueenLandingRetryTicks = DIRECTED_LANDING_RETRY_TICKS;
}
this.directedFireballVolleyTicks = Math.max(0, tag.getInt("DirectedFireballVolleyTicks"));
this.lastQueenMajorAttack = tag.contains("LastQueenMajorAttack")
? tag.getString("LastQueenMajorAttack") : null;
if (tag.contains("NaturalTrailHome")) {
this.naturalTrailSiteId = tag.getLong("NaturalTrailSiteId");
this.naturalTrailHome = BlockPos.of(tag.getLong("NaturalTrailHome"));
@@ -4,6 +4,7 @@ import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.entity.royal.beam.RoyalBeamElement;
import net.minecraft.core.particles.ParticleOptions;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.core.particles.DustParticleOptions;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
@@ -19,8 +20,11 @@ import net.minecraft.world.entity.projectile.AbstractHurtingProjectile;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.EntityHitResult;
import net.minecraft.world.phys.HitResult;
import org.joml.Vector3f;
public class RoyalBoltEntity extends AbstractHurtingProjectile {
private static final DustParticleOptions KING_GOLD_TRAIL =
new DustParticleOptions(new Vector3f(1.0F, 0.72F, 0.12F), 1.35F);
private static final EntityDataAccessor<Integer> ELEMENT =
SynchedEntityData.defineId(RoyalBoltEntity.class, EntityDataSerializers.INT);
@@ -78,6 +82,7 @@ public class RoyalBoltEntity extends AbstractHurtingProjectile {
case FIRE -> ParticleTypes.FLAME;
case LIGHTNING -> ParticleTypes.ELECTRIC_SPARK;
case ICE -> ParticleTypes.SNOWFLAKE;
case KING_GOLD -> KING_GOLD_TRAIL;
default -> ParticleTypes.WITCH;
};
}
@@ -258,6 +258,10 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
return this.royalBeamStartSound();
}
protected float royalBeamStartSoundVolume(RoyalHead head) {
return (float) AntarchySettings.royalBossSoundVolume();
}
protected void tickRoyalBeamEffects(RoyalHead head, Vec3 start, Vec3 end) {
}
@@ -307,6 +311,11 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
return false;
}
/** Prevents new beam volleys while a bespoke attack has reserved the heads. */
protected boolean blocksRoyalBeamScheduling() {
return false;
}
protected int royalPhaseTransitionDuration(Phase nextPhase) {
return 50;
}
@@ -428,6 +437,10 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
this.playSound(sound, (float) AntarchySettings.royalBossSoundVolume(), pitch);
}
protected void playRoyalSound(SoundEvent sound, float volume, float pitch) {
this.playSound(sound, volume, pitch);
}
protected void projectRoyalSound(SoundEvent sound, float volume, float pitch, @Nullable LivingEntity focus) {
if (this.level().isClientSide) {
return;
@@ -540,7 +553,9 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
this.getLookControl().setLookAt(primaryTarget, 30.0F, 30.0F);
}
this.assignHeadTargets(primaryTarget);
if (!this.blocksRoyalBeamScheduling()) {
this.tickRoyalBeam(primaryTarget);
}
this.tickBodyCrush();
Phase phase = this.phase();
@@ -657,7 +672,8 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
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.royalBeamStartSound(head),
RoyalBossEntity.this.royalBeamStartSoundVolume(head), 1.0F);
RoyalBossEntity.this.playRoyalSound(RoyalBossEntity.this.royalBeamShootSound(), 1.0F);
}
@@ -686,6 +702,11 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
this.recoveryWindowTicks = Math.max(this.recoveryWindowTicks, ticks);
}
protected final void addGravityAwareImpulse(Entity entity, Vec3 worldImpulse) {
AntarchyGravityApi.setWorldVelocity(entity,
AntarchyGravityApi.getWorldVelocity(entity).add(worldImpulse));
}
protected final boolean isRoyalPhaseTransitionActive() {
return this.phaseTransitionTicks > 0;
}
@@ -726,9 +747,10 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
return;
}
int maxPlayers = Math.max(1, AntarchySettings.royalBossScalingMaxPlayers());
double encounterRange = Math.max(32.0D, this.getAttributeValue(Attributes.FOLLOW_RANGE));
List<ServerPlayer> players = level.getPlayers(player -> player.isAlive()
&& !player.isSpectator()
&& player.distanceToSqr(this) <= AntarchySettings.royalBossFollowRange() * AntarchySettings.royalBossFollowRange());
&& player.distanceToSqr(this) <= encounterRange * encounterRange);
if (target instanceof ServerPlayer player && player.isAlive() && !players.contains(player)) {
players.add(player);
}
@@ -761,6 +783,14 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
this.setRoyalBeamEndPosition(head.slot(), null);
}
protected final void stopRoyalBeamsForDirectedAttack() {
for (RoyalHead head : this.heads) {
if (head.beamActive() || head.shooting()) {
this.stopRoyalBeam(head);
}
}
}
public Phase phase() {
float fraction = this.getMaxHealth() <= 0.0F ? 1.0F : this.getHealth() / this.getMaxHealth();
if (fraction > PHASE_TWO_THRESHOLD) {
@@ -1174,6 +1204,7 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
-0.2D - this.random.nextDouble() * 0.15D,
(this.random.nextDouble() - 0.5D) * 0.12D);
itemEntity.setDefaultPickUpDelay();
RoyalLootDropGlow.start(itemEntity);
this.level().addFreshEntity(itemEntity);
return itemEntity;
}
@@ -11,6 +11,8 @@ 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.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
@@ -143,6 +145,9 @@ public class RoyalElementalProjectileEntity extends ThrowableProjectile implemen
}
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);
level.playSound(null, BlockPos.containing(center), SoundEvents.GENERIC_EXPLODE.value(),
SoundSource.HOSTILE, this.isDreamFireball() ? 1.15F : 1.0F,
this.isDreamFireball() ? 0.78F : 0.92F);
}
private void igniteImpact(ServerLevel level, Vec3 center, boolean dream) {
@@ -30,6 +30,12 @@ public class RoyalIceSpikeEntity extends Entity implements GeoEntity {
private boolean impacted;
@Nullable
private UUID ownerId;
private float customImpactDamage = -1.0F;
private double customImpactRadius = 2.2D;
private int customFreezeTicks = 100;
private int customSlowTicks;
private int customSlowAmplifier;
private double customKnockup = 0.35D;
public RoyalIceSpikeEntity(EntityType<? extends RoyalIceSpikeEntity> type, Level level) {
super(type, level);
@@ -49,6 +55,18 @@ public class RoyalIceSpikeEntity extends Entity implements GeoEntity {
return spike;
}
public static RoyalIceSpikeEntity create(ServerLevel level, Vec3 position, LivingEntity owner,
float damage, double radius, int freezeTicks, int slowTicks, int slowAmplifier) {
RoyalIceSpikeEntity spike = create(level, position, owner);
spike.customImpactDamage = damage;
spike.customImpactRadius = radius;
spike.customFreezeTicks = freezeTicks;
spike.customSlowTicks = slowTicks;
spike.customSlowAmplifier = slowAmplifier;
spike.customKnockup = 0.1D;
return spike;
}
@Override
protected void defineSynchedData(net.minecraft.network.syncher.SynchedEntityData.Builder builder) {
}
@@ -73,25 +91,55 @@ public class RoyalIceSpikeEntity extends Entity implements GeoEntity {
private void impact(ServerLevel level) {
DamageSource source = this.damageSources().magic();
for (LivingEntity living : level.getEntitiesOfClass(LivingEntity.class,
new AABB(this.blockPosition()).inflate(2.2D), entity -> entity.isAlive())) {
Entity owner = this.ownerId == null ? null : level.getEntity(this.ownerId);
living.hurt(source, owner instanceof RoyalBossEntity royalBoss
if (this.ownerId != null && owner == null) return;
for (LivingEntity living : level.getEntitiesOfClass(LivingEntity.class,
new AABB(this.blockPosition()).inflate(this.customImpactRadius), entity -> entity.isAlive()
&& entity.distanceToSqr(this.position()) <= this.customImpactRadius * this.customImpactRadius
&& canHitOwnerTarget(owner, entity))) {
float damage = this.customImpactDamage >= 0.0F ? this.customImpactDamage
: owner instanceof RoyalBossEntity royalBoss
? royalBoss.scaleRoyalDamage(AntarchySettings.kingIceSpikeDamage())
: (float) AntarchySettings.kingIceSpikeDamage());
living.setTicksFrozen(Math.min(living.getTicksRequiredToFreeze() + 60, living.getTicksFrozen() + 100));
living.setDeltaMovement(living.getDeltaMovement().add(0.0D, 0.35D, 0.0D));
: (float) AntarchySettings.kingIceSpikeDamage();
boolean hit = owner instanceof net.minecraft.world.entity.player.Player player && this.customImpactDamage >= 0.0F
? com.craisinlord.antarchy.content.item.RoyalGuardianSwordAbilities.applySecondaryDamage(player, living, damage)
: living.hurt(source, damage);
if (!hit) continue;
living.setTicksFrozen(Math.min(living.getTicksRequiredToFreeze() + 60, living.getTicksFrozen() + this.customFreezeTicks));
if (this.customSlowTicks > 0) {
living.addEffect(new net.minecraft.world.effect.MobEffectInstance(
net.minecraft.world.effect.MobEffects.MOVEMENT_SLOWDOWN, this.customSlowTicks, this.customSlowAmplifier));
}
living.setDeltaMovement(living.getDeltaMovement().add(0.0D, this.customKnockup, 0.0D));
living.hasImpulse = true;
}
level.sendParticles(ParticleTypes.SNOWFLAKE, this.getX(), this.getY() + 1.5D, this.getZ(), 25, 1.5D, 1.0D, 1.5D, 0.04D);
}
private boolean canHitOwnerTarget(@Nullable Entity owner, LivingEntity target) {
if (owner == null) return this.ownerId == null;
if (target == owner || target.isAlliedTo(owner) || owner.isAlliedTo(target)) return false;
if (owner instanceof RoyalBossEntity royalBoss) return royalBoss.canDamageWithRoyalAttack(target);
if (owner instanceof net.minecraft.world.entity.player.Player player) {
return com.craisinlord.antarchy.content.item.RoyalGuardianSwordAbilities.canDamageTarget(player, target, false);
}
return true;
}
@Override
protected void readAdditionalSaveData(CompoundTag tag) {
this.impacted = tag.getBoolean("Impacted");
if (tag.hasUUID("Owner")) {
this.ownerId = tag.getUUID("Owner");
}
if (tag.contains("CustomImpactDamage")) {
this.customImpactDamage = tag.getFloat("CustomImpactDamage");
this.customImpactRadius = tag.getDouble("CustomImpactRadius");
this.customFreezeTicks = tag.getInt("CustomFreezeTicks");
this.customSlowTicks = tag.getInt("CustomSlowTicks");
this.customSlowAmplifier = tag.getInt("CustomSlowAmplifier");
this.customKnockup = tag.getDouble("CustomKnockup");
}
}
@Override
@@ -100,6 +148,14 @@ public class RoyalIceSpikeEntity extends Entity implements GeoEntity {
if (this.ownerId != null) {
tag.putUUID("Owner", this.ownerId);
}
if (this.customImpactDamage >= 0.0F) {
tag.putFloat("CustomImpactDamage", this.customImpactDamage);
tag.putDouble("CustomImpactRadius", this.customImpactRadius);
tag.putInt("CustomFreezeTicks", this.customFreezeTicks);
tag.putInt("CustomSlowTicks", this.customSlowTicks);
tag.putInt("CustomSlowAmplifier", this.customSlowAmplifier);
tag.putDouble("CustomKnockup", this.customKnockup);
}
}
@Override
@@ -0,0 +1,37 @@
package com.craisinlord.antarchy.content.entity.royal;
import net.minecraft.world.entity.item.ItemEntity;
public final class RoyalLootDropGlow {
private static final String TAG_PREFIX = "antarchy_royal_loot_glow_until_";
public static final int DURATION_TICKS = 20 * 120;
private RoyalLootDropGlow() {
}
public static void start(ItemEntity itemEntity) {
long expiryTick = itemEntity.level().getGameTime() + DURATION_TICKS;
itemEntity.addTag(TAG_PREFIX + expiryTick);
itemEntity.setGlowingTag(true);
}
public static void tick(ItemEntity itemEntity) {
if (itemEntity.level().isClientSide) return;
for (String tag : itemEntity.getTags()) {
if (!tag.startsWith(TAG_PREFIX)) continue;
long expiryTick;
try {
expiryTick = Long.parseLong(tag.substring(TAG_PREFIX.length()));
} catch (NumberFormatException ignored) {
itemEntity.removeTag(tag);
continue;
}
if (itemEntity.level().getGameTime() < expiryTick) {
itemEntity.setGlowingTag(true);
return;
}
itemEntity.removeTag(tag);
itemEntity.setGlowingTag(false);
}
}
}
@@ -11,6 +11,7 @@ import net.minecraft.util.RandomSource;
public final class RoyalAttackScheduler {
public interface Action {
default void onStart() {}
default void onWindup(int elapsedTicks, int remainingTicks) {}
default void onActive(int elapsedTicks) {}
default void onComplete() {}
}
@@ -44,6 +45,9 @@ public final class RoyalAttackScheduler {
running.elapsedTicks++;
int activeStart = running.windupTicks;
int activeEnd = activeStart + running.activeTicks;
if (running.elapsedTicks < activeStart) {
running.action.onWindup(running.elapsedTicks, activeStart - running.elapsedTicks);
}
if (running.elapsedTicks >= activeStart && running.elapsedTicks < activeEnd) {
running.action.onActive(running.elapsedTicks - activeStart);
}
@@ -116,6 +120,10 @@ public final class RoyalAttackScheduler {
return cooldowns.getOrDefault(id, 0);
}
public void resetCooldown(String id) {
cooldowns.remove(id);
}
public void cancel(RoyalAttackLane lane) {
active.remove(lane);
}
@@ -8,5 +8,6 @@ public enum RoyalBeamElement {
QUEEN_PURPLE,
QUEEN_RED,
QUEEN_BLACK,
DREAM_FIRE
DREAM_FIRE,
KING_GOLD
}
@@ -12,6 +12,10 @@ public final class CloseQuartersDecree implements RoyalDecree {
return king.evaluateCloseQuarters(target);
}
@Override
public int countdownTicks(KingEntity king, LivingEntity target) {
return king.closeQuartersCountdownTicks(target);
}
@Override
public int contextWeight(KingEntity king, LivingEntity target) {
return 4 + king.behaviorScore(KingEntity.Behavior.RANGED);
}
@@ -1,12 +1,18 @@
package com.craisinlord.antarchy.content.entity.royal.decree;
import com.craisinlord.antarchy.content.entity.royal.KingEntity;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.phys.Vec3;
public final class FightMeCowardDecree implements RoyalDecree {
private static final int APPROACH_TICKS = 80;
private final Map<UUID, Integer> deadlines = new HashMap<>();
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) {
this.deadlines.computeIfAbsent(target.getUUID(), ignored -> target.tickCount + APPROACH_TICKS);
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));
@@ -15,6 +21,21 @@ public final class FightMeCowardDecree implements RoyalDecree {
}
@Override
public Evaluation evaluate(ServerLevel level, KingEntity king, LivingEntity target) {
return target.distanceToSqr(king) > 784.0D ? Evaluation.VIOLATED : Evaluation.COMPLIANT;
int deadline = this.deadlines.computeIfAbsent(target.getUUID(), ignored -> target.tickCount + APPROACH_TICKS);
return target.tickCount >= deadline && target.distanceToSqr(king) > 784.0D
? Evaluation.VIOLATED : Evaluation.COMPLIANT;
}
@Override
public int countdownTicks(KingEntity king, LivingEntity target) {
if (target.distanceToSqr(king) <= 784.0D) {
return -1;
}
return this.deadlines.getOrDefault(target.getUUID(), target.tickCount + APPROACH_TICKS) - target.tickCount;
}
@Override
public void onEnded() {
this.deadlines.clear();
}
}
@@ -23,7 +23,7 @@ public final class KneelDecree implements RoyalDecree {
}
@Override
public int countdownTicks(LivingEntity target) {
public int countdownTicks(KingEntity king, LivingEntity target) {
return this.deadlines.getOrDefault(target.getUUID(), target.tickCount + 100) - target.tickCount;
}
@@ -18,7 +18,7 @@ public interface RoyalDecree {
return Evaluation.COMPLIANT;
}
default int countdownTicks(LivingEntity target) {
default int countdownTicks(KingEntity king, LivingEntity target) {
return -1;
}
@@ -35,7 +35,7 @@ public final class ComputerScientistTradeManager extends SimpleJsonResourceReloa
}
public static int[] slotsPerLevel() {
return new int[] {4, 2, 2, 3, 4};
return new int[] {4, 2, 2, 3, 5};
}
public static VillagerTrades.ItemListing listing(int level, int index) {
@@ -0,0 +1,13 @@
package com.craisinlord.antarchy.content.gravity;
import net.minecraft.world.phys.Vec3;
/** Converts a projectile launch vector from its shooter's gravity frame to world space. */
public final class ProjectileLaunchGravity {
private ProjectileLaunchGravity() {
}
public static Vec3 toWorld(Vec3 launchDirection, AntarchyGravityDirection gravityDirection) {
return AntarchyGravityRotationUtil.vecPlayerToWorld(launchDirection, gravityDirection);
}
}
@@ -1,5 +1,6 @@
package com.craisinlord.antarchy.content.guide;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.config.AntarchySettings;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
@@ -41,14 +42,14 @@ public final class ComputerGuideData extends SimplePreparableReloadListener<Comp
public static List<Entry> entriesFor(List<ResourceLocation> diskIds) {
if (AntarchySettings.unlockAllArchives()) {
Map<ResourceLocation, Entry> result = new LinkedHashMap<>();
result.put(INTRODUCTION, new Entry(INTRODUCTION, "article", "general", "guide.antarchy.entry.introduction.title", "guide.antarchy.entry.introduction.subtitle", List.of("guide.antarchy.entry.introduction.description"), "", "", "", "", ""));
result.put(INTRODUCTION, introduction());
result.putAll(entries);
return result.values().stream().sorted(Comparator.comparing(entry -> entry.titleKey().toString())).toList();
}
Map<ResourceLocation, Entry> result = new LinkedHashMap<>();
for (ResourceLocation diskId : diskIds) {
if (INTRODUCTION.equals(diskId)) {
result.put(INTRODUCTION, new Entry(INTRODUCTION, "article", "general", "guide.antarchy.entry.introduction.title", "guide.antarchy.entry.introduction.subtitle", List.of("guide.antarchy.entry.introduction.description"), "", "", "", "", ""));
result.put(INTRODUCTION, introduction());
continue;
}
Disk disk = disks.get(diskId);
@@ -65,6 +66,87 @@ public final class ComputerGuideData extends SimplePreparableReloadListener<Comp
return result.values().stream().sorted(Comparator.comparing(entry -> entry.titleKey().toString())).toList();
}
private static Entry introduction() {
return new Entry(INTRODUCTION, "article", "general", "guide.antarchy.entry.introduction.title",
"guide.antarchy.entry.introduction.subtitle", List.of("guide.antarchy.entry.introduction.description"),
"", "", "", "", "", "", "", 0, "", "", "", 0.0F, 1.0F);
}
/** Encodes the server data-pack view so remote clients do not need a duplicate asset copy. */
public static String encodeNetworkSnapshot() {
JsonObject root = new JsonObject();
JsonArray entryArray = new JsonArray();
entries.values().stream().sorted(Comparator.comparing(entry -> entry.id().toString())).forEach(entry -> {
JsonObject object = new JsonObject();
object.addProperty("id", entry.id().toString());
object.addProperty("type", entry.type());
object.addProperty("category", entry.category());
object.addProperty("title", entry.titleKey());
object.addProperty("subtitle", entry.subtitleKey());
JsonArray descriptions = new JsonArray();
entry.descriptionKeys().forEach(descriptions::add);
object.add("description", descriptions);
object.addProperty("item", entry.itemId());
object.addProperty("entity", entry.entityId());
object.addProperty("enchantment", entry.enchantmentId());
object.addProperty("recipe", entry.recipeId());
object.addProperty("structure", entry.structureId());
object.addProperty("structure_tag", entry.structureTagId());
object.addProperty("dimension", entry.dimensionId());
object.addProperty("search_radius", entry.searchRadius());
object.addProperty("cover_item", entry.coverItemId());
object.addProperty("cover_entity", entry.coverEntityId());
object.addProperty("cover_potion", entry.coverPotionId());
object.addProperty("rotation", entry.rotation());
object.addProperty("render_scale", entry.renderScale());
entryArray.add(object);
});
root.add("entries", entryArray);
JsonArray diskArray = new JsonArray();
disks.values().stream().sorted(Comparator.comparing(disk -> disk.id().toString())).forEach(disk -> {
JsonObject object = new JsonObject();
object.addProperty("id", disk.id().toString());
object.addProperty("category", disk.category());
object.addProperty("title", disk.titleKey());
JsonArray diskEntries = new JsonArray();
disk.entries().forEach(id -> diskEntries.add(id.toString()));
object.add("entries", diskEntries);
object.addProperty("wallpaper", disk.wallpaper());
diskArray.add(object);
});
root.add("disks", diskArray);
return root.toString();
}
/** Applies the authoritative server snapshot on a multiplayer client. */
public static void applyNetworkSnapshot(String json) {
try {
JsonObject root = JsonParser.parseString(json).getAsJsonObject();
Map<ResourceLocation, Entry> loadedEntries = new HashMap<>();
Map<ResourceLocation, Disk> loadedDisks = new HashMap<>();
JsonArray entryArray = root.getAsJsonArray("entries");
if (entryArray != null) {
for (JsonElement element : entryArray) {
JsonObject object = element.getAsJsonObject();
parseEntry(ResourceLocation.parse(string(object, "id", "")), object, loadedEntries);
}
}
JsonArray diskArray = root.getAsJsonArray("disks");
if (diskArray != null) {
for (JsonElement element : diskArray) {
JsonObject object = element.getAsJsonObject();
parseDisk(ResourceLocation.parse(string(object, "id", "")), object, loadedDisks);
}
}
entries = Map.copyOf(loadedEntries);
disks = Map.copyOf(loadedDisks);
Antarchy.LOGGER.info("Loaded {} computer archive entries and {} disks from the server", entries.size(), disks.size());
} catch (RuntimeException exception) {
Antarchy.LOGGER.error("Failed to decode computer archive data from the server", exception);
}
}
@Override
protected LoadedData prepare(ResourceManager resourceManager, ProfilerFiller profiler) {
Map<ResourceLocation, Entry> loadedEntries = new HashMap<>();
@@ -74,7 +156,8 @@ public final class ComputerGuideData extends SimplePreparableReloadListener<Comp
try {
JsonElement json = JsonParser.parseReader(resource.getValue().openAsReader());
parseEntry(id, json, loadedEntries);
} catch (Exception ignored) {
} catch (Exception exception) {
Antarchy.LOGGER.error("Failed to load computer archive entry {}", resource.getKey(), exception);
}
}
for (Map.Entry<ResourceLocation, net.minecraft.server.packs.resources.Resource> resource : resourceManager.listResources("computer/disk", path -> path.getPath().endsWith(".json")).entrySet()) {
@@ -82,7 +165,8 @@ public final class ComputerGuideData extends SimplePreparableReloadListener<Comp
try {
JsonElement json = JsonParser.parseReader(resource.getValue().openAsReader());
parseDisk(id, json, loadedDisks);
} catch (Exception ignored) {
} catch (Exception exception) {
Antarchy.LOGGER.error("Failed to load computer archive disk {}", resource.getKey(), exception);
}
}
return new LoadedData(Map.copyOf(loadedEntries), Map.copyOf(loadedDisks));
@@ -115,10 +199,23 @@ public final class ComputerGuideData extends SimplePreparableReloadListener<Comp
List<String> description = strings(object.get("description"));
String item = string(object, "item", "");
String entity = string(object, "entity", "");
String enchantment = string(object, "enchantment", "");
String recipe = string(object, "recipe", "");
String structure = string(object, "structure", "");
String structureTag = string(object, "structure_tag", "");
String dimension = string(object, "dimension", "");
target.put(id, new Entry(id, type, category, title, subtitle, description, item, entity, recipe, structure, dimension));
int searchRadius = object.has("search_radius") && object.get("search_radius").isJsonPrimitive()
? Math.max(0, Math.min(1000, object.get("search_radius").getAsInt())) : 0;
String coverItem = string(object, "cover_item", "");
String coverEntity = string(object, "cover_entity", "");
String coverPotion = string(object, "cover_potion", "");
float rotation = object.has("rotation") && object.get("rotation").isJsonPrimitive()
? object.get("rotation").getAsFloat() : 0.0F;
float renderScale = object.has("render_scale") && object.get("render_scale").isJsonPrimitive()
? object.get("render_scale").getAsFloat() : 1.0F;
renderScale = Math.max(0.1F, Math.min(3.0F, renderScale));
target.put(id, new Entry(id, type, category, title, subtitle, description, item, entity, enchantment, recipe, structure, structureTag, dimension, searchRadius,
coverItem, coverEntity, coverPotion, rotation, renderScale));
}
private static void parseDisk(ResourceLocation id, JsonElement element, Map<ResourceLocation, Disk> target) {
@@ -168,9 +265,11 @@ public final class ComputerGuideData extends SimplePreparableReloadListener<Comp
JsonObject render = value.getAsJsonObject();
String item = string(render, "item", "");
String entity = string(render, "entity", "");
String enchantment = string(render, "enchantment", "");
String recipe = string(render, "recipe", "");
if (!item.isBlank()) values.add("@item:" + item);
else if (!entity.isBlank()) values.add("@entity:" + entity);
else if (!enchantment.isBlank()) values.add("@enchantment:" + enchantment);
else if (!recipe.isBlank()) values.add("@recipe:" + recipe);
}
}
@@ -178,7 +277,9 @@ public final class ComputerGuideData extends SimplePreparableReloadListener<Comp
}
public record Entry(ResourceLocation id, String type, String category, String titleKey, String subtitleKey,
List<String> descriptionKeys, String itemId, String entityId, String recipeId, String structureId, String dimensionId) {
List<String> descriptionKeys, String itemId, String entityId, String enchantmentId,
String recipeId, String structureId, String structureTagId, String dimensionId, int searchRadius, String coverItemId,
String coverEntityId, String coverPotionId, float rotation, float renderScale) {
}
public record Disk(ResourceLocation id, String category, String titleKey, List<ResourceLocation> entries, String wallpaper) {
@@ -8,13 +8,19 @@ import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.EquipmentSlotGroup;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.attributes.AttributeModifier;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.item.ElytraItem;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.component.ItemAttributeModifiers;
public final class BrutalflyElytraItem extends ElytraItem {
private static final ResourceLocation REPAIR_MATERIAL_ID = ResourceLocation.fromNamespaceAndPath("antarchy", "brutalfly_wing");
private static final ResourceLocation ARMOR_MODIFIER_ID = ResourceLocation.fromNamespaceAndPath("antarchy", "brutalfly_elytra_armor");
private static final ResourceLocation TOUGHNESS_MODIFIER_ID = ResourceLocation.fromNamespaceAndPath("antarchy", "brutalfly_elytra_toughness");
// Set by NeoForge client setup to return the actual bound key name
public static Supplier<Component> FLAP_KEY_NAME = () -> Component.literal("Shift");
@@ -27,6 +33,15 @@ public final class BrutalflyElytraItem extends ElytraItem {
return entity != null && entity.getItemBySlot(EquipmentSlot.CHEST).getItem() instanceof BrutalflyElytraItem;
}
@Override
public ItemAttributeModifiers getDefaultAttributeModifiers() {
return ItemAttributeModifiers.builder()
.add(Attributes.ARMOR,
new AttributeModifier(ARMOR_MODIFIER_ID, 3.0D, AttributeModifier.Operation.ADD_VALUE),
EquipmentSlotGroup.CHEST)
.build();
}
@Override
public boolean isValidRepairItem(ItemStack toRepair, ItemStack repair) {
Optional<Item> repairMaterial = BuiltInRegistries.ITEM.getOptional(REPAIR_MATERIAL_ID);
@@ -10,16 +10,22 @@ import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.EquipmentSlotGroup;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.attributes.AttributeModifier;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ElytraItem;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.item.component.ItemAttributeModifiers;
import net.minecraft.world.level.Level;
public final class ManticoreWingsItem extends ElytraItem {
private static final ResourceLocation REPAIR_MATERIAL_ID = ResourceLocation.fromNamespaceAndPath("antarchy", "manticore_wing");
private static final ResourceLocation ARMOR_MODIFIER_ID = ResourceLocation.fromNamespaceAndPath("antarchy", "manticore_wings_armor");
private static final ResourceLocation TOUGHNESS_MODIFIER_ID = ResourceLocation.fromNamespaceAndPath("antarchy", "manticore_wings_toughness");
private static final int DAMAGE_COOLDOWN_TICKS = 10;
private static final double MIN_COLLISION_SPEED = 0.2D;
private static final float COLLISION_DAMAGE_PER_BLOCK_PER_TICK = 8.0F;
@@ -62,6 +68,18 @@ public final class ManticoreWingsItem extends ElytraItem {
return entity != null && entity.getItemBySlot(EquipmentSlot.CHEST).getItem() instanceof ManticoreWingsItem;
}
@Override
public ItemAttributeModifiers getDefaultAttributeModifiers() {
return ItemAttributeModifiers.builder()
.add(Attributes.ARMOR,
new AttributeModifier(ARMOR_MODIFIER_ID, 5.0D, AttributeModifier.Operation.ADD_VALUE),
EquipmentSlotGroup.CHEST)
.add(Attributes.ARMOR_TOUGHNESS,
new AttributeModifier(TOUGHNESS_MODIFIER_ID, 1.0D, AttributeModifier.Operation.ADD_VALUE),
EquipmentSlotGroup.CHEST)
.build();
}
@Override
public void appendHoverText(ItemStack stack, Item.TooltipContext context, List<Component> tooltipComponents, TooltipFlag tooltipFlag) {
tooltipComponents.add(Component.translatable("tooltip.antarchy.manticore_wings").withStyle(ChatFormatting.GRAY));
@@ -34,9 +34,13 @@ public class RainbowSugarItem extends Item {
if (!level.isClientSide && livingEntity instanceof ServerPlayer serverPlayer && isInInfinityDimension(serverPlayer.serverLevel())) {
ServerLevel returnLevel = AntTeleportHelper.resolveReturnDestinationLevel(serverPlayer);
var returnPos = AntTeleportHelper.getDestinationPosition(serverPlayer, returnLevel);
if (returnPos == null) {
serverPlayer.displayClientMessage(Component.translatable("message.antarchy.teleport_arrival_failed"), true);
} else {
serverPlayer.teleportTo(returnLevel, returnPos.x, returnPos.y, returnPos.z, serverPlayer.getYRot(), serverPlayer.getXRot());
serverPlayer.playSound(SoundEvents.ENDERMAN_TELEPORT, 1.0F, 1.0F);
}
}
return result;
}
@@ -2,16 +2,21 @@ package com.craisinlord.antarchy.content.item;
import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.item.royal.RoyalGearHelper;
import java.util.List;
import net.minecraft.core.Holder;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.EquipmentSlotGroup;
import net.minecraft.world.entity.ai.attributes.AttributeModifier;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ArmorItem;
import net.minecraft.world.item.ArmorMaterial;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.item.component.ItemAttributeModifiers;
import net.minecraft.world.level.Level;
@@ -42,6 +47,40 @@ public class RoyalGuardianArmorItem extends ArmorItem {
return (float) AntarchySettings.royalGuardianArmorToughness();
}
public static double judgmentDamageMultiplier(Player player) {
double bonus = 0.0D;
if (player.getItemBySlot(EquipmentSlot.HEAD).getItem() instanceof RoyalGuardianArmorItem) bonus += AntarchySettings.royalGuardianHelmetJudgmentBonus();
if (player.getItemBySlot(EquipmentSlot.CHEST).getItem() instanceof RoyalGuardianArmorItem) bonus += AntarchySettings.royalGuardianChestplateJudgmentBonus();
if (player.getItemBySlot(EquipmentSlot.LEGS).getItem() instanceof RoyalGuardianArmorItem) bonus += AntarchySettings.royalGuardianLeggingsJudgmentBonus();
if (player.getItemBySlot(EquipmentSlot.FEET).getItem() instanceof RoyalGuardianArmorItem) bonus += AntarchySettings.royalGuardianBootsJudgmentBonus();
return 1.0D + bonus;
}
private double judgmentBonus() {
return switch (this.armorType) {
case HELMET -> AntarchySettings.royalGuardianHelmetJudgmentBonus();
case CHESTPLATE, BODY -> AntarchySettings.royalGuardianChestplateJudgmentBonus();
case LEGGINGS -> AntarchySettings.royalGuardianLeggingsJudgmentBonus();
case BOOTS -> AntarchySettings.royalGuardianBootsJudgmentBonus();
};
}
@Override
public void appendHoverText(ItemStack stack, Item.TooltipContext context, List<Component> tooltipComponents, TooltipFlag tooltipFlag) {
String slotName = switch (this.armorType) {
case HELMET -> "helmet";
case CHESTPLATE, BODY -> "chestplate";
case LEGGINGS -> "leggings";
case BOOTS -> "boots";
};
tooltipComponents.add(Component.translatable("tooltip.antarchy.royal_guardian_" + slotName + ".judgment",
Math.round(judgmentBonus() * 100.0D)));
if (this.armorType == Type.CHESTPLATE) {
tooltipComponents.add(Component.translatable("tooltip.antarchy.royal_guardian_chestplate.boundary"));
}
super.appendHoverText(stack, context, tooltipComponents, tooltipFlag);
}
@Override
public ItemAttributeModifiers getDefaultAttributeModifiers() {
String prefix = "royal_guardian_" + this.armorType.getName();
@@ -2,9 +2,15 @@ package com.craisinlord.antarchy.content.item;
import com.craisinlord.antarchy.content.effect.RoyalEffectEligibility;
import com.craisinlord.antarchy.content.effect.RoyalEffectHooks;
import com.craisinlord.antarchy.content.effect.CommandedEntityAccess;
import com.craisinlord.antarchy.content.effect.CommandedBehavior;
import com.craisinlord.antarchy.config.AntarchySettings;
import java.util.List;
import java.util.Set;
import java.util.HashSet;
import java.util.Comparator;
import java.util.UUID;
import java.util.stream.StreamSupport;
import java.util.function.Supplier;
import net.minecraft.core.Holder;
import net.minecraft.world.InteractionHand;
@@ -12,6 +18,10 @@ import net.minecraft.world.InteractionResultHolder;
import net.minecraft.world.effect.MobEffect;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.entity.Mob;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
@@ -24,9 +34,6 @@ import net.minecraft.world.level.Level;
import net.minecraft.world.phys.AABB;
public class RoyalGuardianShieldItem extends ShieldItem {
private static final int COMMAND_COOLDOWN_TICKS = 20 * 15;
private static final int COMMAND_DURATION_TICKS = 20 * 30;
private static final double COMMAND_RANGE = 16.0D;
private final Supplier<Ingredient> repairIngredient;
public RoyalGuardianShieldItem(Item.Properties properties, Supplier<Ingredient> repairIngredient) {
@@ -54,6 +61,9 @@ public class RoyalGuardianShieldItem extends ShieldItem {
@Override
public InteractionResultHolder<ItemStack> use(Level level, Player player, InteractionHand hand) {
ItemStack stack = player.getItemInHand(hand);
if (!player.isShiftKeyDown()) {
return super.use(level, player, hand);
}
if (player.getCooldowns().isOnCooldown(this)) {
return InteractionResultHolder.fail(stack);
}
@@ -64,16 +74,54 @@ public class RoyalGuardianShieldItem extends ShieldItem {
if (commanded == null) {
return InteractionResultHolder.fail(stack);
}
Mob target = level.getEntitiesOfClass(Mob.class,
new AABB(player.blockPosition()).inflate(COMMAND_RANGE),
mob -> RoyalEffectEligibility.canApplyCommanded(mob))
.stream()
.min(Comparator.comparingDouble((Mob mob) -> player.distanceToSqr(mob)).thenComparingInt(Mob::getId))
.orElse(null);
if (target == null || !target.addEffect(new MobEffectInstance(commanded, COMMAND_DURATION_TICKS), player)) {
if (!(level instanceof ServerLevel serverLevel)) {
return InteractionResultHolder.fail(stack);
}
player.getCooldowns().addCooldown(this, COMMAND_COOLDOWN_TICKS);
int rosterSize = Math.max(0, AntarchySettings.royalGuardianMusterMobCap());
int duration = Math.max(1, AntarchySettings.royalGuardianMusterDurationTicks());
if (rosterSize == 0) return InteractionResultHolder.fail(stack);
Set<UUID> rosterIds = new HashSet<>();
List<Mob> existingRoster = StreamSupport.stream(serverLevel.getAllEntities().spliterator(), false)
.filter(Mob.class::isInstance).map(Mob.class::cast)
.filter(mob -> mob.hasEffect(commanded) && mob instanceof CommandedEntityAccess access
&& access.antarchy$isRoyalInvested() && player.getUUID().equals(access.antarchy$getCommanderUuid()))
.sorted(Comparator.comparingDouble((Mob mob) -> player.distanceToSqr(mob)).thenComparingInt(Mob::getId))
.limit(rosterSize).toList();
for (Mob mob : existingRoster) {
rosterIds.add(mob.getUUID());
mob.addEffect(new MobEffectInstance(commanded, duration), player);
}
int slotsRemaining = rosterSize - existingRoster.size();
int recruited = 0;
List<Mob> candidates = level.getEntitiesOfClass(Mob.class,
new AABB(player.blockPosition()).inflate(Math.max(0.0D, AntarchySettings.royalGuardianMusterRange())),
mob -> !rosterIds.contains(mob.getUUID()) && RoyalEffectEligibility.canApplyCommanded(mob))
.stream()
.sorted(Comparator.comparing((Mob mob) -> !isInCommandLookCone(player, mob))
.thenComparingDouble(player::distanceToSqr).thenComparingInt(Mob::getId))
.toList();
for (Mob target : candidates) {
if (recruited >= slotsRemaining) break;
if (!target.addEffect(new MobEffectInstance(commanded, duration), player)) continue;
CommandedEntityAccess access = (CommandedEntityAccess) target;
access.antarchy$setRoyalInvested(true);
CommandedBehavior.syncInvestiture(target, true);
rosterIds.add(target.getUUID());
recruited++;
}
if (existingRoster.isEmpty() && recruited == 0) {
return InteractionResultHolder.fail(stack);
}
player.getCooldowns().addCooldown(this, Math.max(0, AntarchySettings.royalGuardianMusterCooldownTicks()));
serverLevel.playSound(null, player.blockPosition(), SoundEvents.ILLUSIONER_CAST_SPELL, SoundSource.PLAYERS, 1.0F, 0.75F);
serverLevel.sendParticles(ParticleTypes.ENCHANT, player.getX(), player.getY() + 1.0D, player.getZ(), 32, 1.0D, 0.7D, 1.0D, 0.2D);
return InteractionResultHolder.success(stack);
}
private static boolean isInCommandLookCone(Player player, Mob mob) {
var direction = mob.getBoundingBox().getCenter().subtract(player.getEyePosition()).normalize();
return player.getViewVector(1.0F).dot(direction) >= 0.9063078D; // 25-degree half-angle
}
}
@@ -0,0 +1,211 @@
package com.craisinlord.antarchy.content.item;
import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.effect.CommandedEntityAccess;
import com.craisinlord.antarchy.content.entity.royal.RoyalIceSpikeEntity;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import net.minecraft.core.BlockPos;
import net.minecraft.core.particles.DustParticleOptions;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.MobCategory;
import net.minecraft.world.entity.OwnableEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.entity.LightningBolt;
import net.minecraft.world.level.GameRules;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import org.joml.Vector3f;
/** Player-safe kernels for the Royal Guardian Sword's King-derived elemental modes. */
public final class RoyalGuardianSwordAbilities {
private static final ThreadLocal<Integer> SECONDARY_DAMAGE_DEPTH = ThreadLocal.withInitial(() -> 0);
private static final ThreadLocal<MeleeAttackContext> MELEE_ATTACK = new ThreadLocal<>();
private static final DustParticleOptions FIRE_GOLD = new DustParticleOptions(new Vector3f(1.0F, 0.55F, 0.08F), 1.6F);
private static final DustParticleOptions STORM_GOLD = new DustParticleOptions(new Vector3f(1.0F, 0.86F, 0.25F), 1.25F);
private RoyalGuardianSwordAbilities() {
}
public static boolean isApplyingSecondaryDamage() {
return SECONDARY_DAMAGE_DEPTH.get() > 0;
}
public static void beginMeleeAttack(Player player, Entity target) {
boolean charged = player.getMainHandItem().getItem() instanceof RoyalGuardianSwordItem
&& player.getAttackStrengthScale(0.5F) >= 0.9F;
MELEE_ATTACK.set(new MeleeAttackContext(player.getUUID(), target.getUUID(), charged));
}
public static boolean isChargedMeleeAttack(Player player, LivingEntity target) {
MeleeAttackContext context = MELEE_ATTACK.get();
return context != null && context.charged && context.playerId.equals(player.getUUID())
&& context.targetId.equals(target.getUUID());
}
public static void endMeleeAttack(Player player) {
MeleeAttackContext context = MELEE_ATTACK.get();
if (context != null && context.playerId.equals(player.getUUID())) MELEE_ATTACK.remove();
}
public static boolean applySecondaryDamage(Player owner, LivingEntity target, float amount) {
if (amount <= 0.0F || !canDamageTarget(owner, target, false)) return false;
int depth = SECONDARY_DAMAGE_DEPTH.get();
SECONDARY_DAMAGE_DEPTH.set(depth + 1);
try {
return target.hurt(owner.damageSources().playerAttack(owner), amount);
} finally {
if (depth == 0) SECONDARY_DAMAGE_DEPTH.remove();
else SECONDARY_DAMAGE_DEPTH.set(depth);
}
}
public static void discharge(Player owner, LivingEntity primary, ItemStack swordStack) {
if (!(owner.level() instanceof ServerLevel level) || !canDamageTarget(owner, primary, true)
|| !(swordStack.getItem() instanceof RoyalGuardianSwordItem)
|| !RoyalGuardianSwordItem.isDischargeReady(swordStack, level.getGameTime())) return;
switch (RoyalGuardianSwordItem.getMode(swordStack)) {
case FIRE -> fireImpact(level, owner, primary);
case FROST -> frostFan(level, owner, primary);
case STORM -> chainLightning(level, owner, primary);
}
RoyalGuardianSwordItem.startDischargeCooldown(swordStack, level.getGameTime());
}
private static void fireImpact(ServerLevel level, Player owner, LivingEntity primary) {
Vec3 center = primary.position().add(0.0D, primary.getBbHeight() * 0.5D, 0.0D);
applySecondaryDamage(owner, primary, (float) AntarchySettings.royalGuardianSwordFirePrimaryDamage());
if (primary.isAlive()) primary.setRemainingFireTicks(Math.max(primary.getRemainingFireTicks(), 100));
for (LivingEntity target : level.getEntitiesOfClass(LivingEntity.class, new AABB(center, center).inflate(4.0D),
candidate -> candidate != primary && canDamageTarget(owner, candidate, false))) {
double distance = Math.sqrt(target.distanceToSqr(center));
float falloff = (float) Math.max(0.25D, 1.0D - distance / 5.0D);
applySecondaryDamage(owner, target, (float) AntarchySettings.royalGuardianSwordFireSplashDamage() * falloff);
target.setRemainingFireTicks(Math.max(target.getRemainingFireTicks(), 80));
}
if (level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) placeBoundedFire(level, center, 6);
level.sendParticles(ParticleTypes.FLAME, center.x, center.y, center.z, 55, 1.4D, 1.0D, 1.4D, 0.1D);
level.sendParticles(FIRE_GOLD, center.x, center.y, center.z, 28, 1.1D, 0.8D, 1.1D, 0.04D);
level.sendParticles(ParticleTypes.EXPLOSION, center.x, center.y, center.z, 3, 0.8D, 0.6D, 0.8D, 0.0D);
level.playSound(null, BlockPos.containing(center), SoundEvents.GENERIC_EXPLODE.value(), SoundSource.PLAYERS, 1.0F, 1.05F);
}
private static void placeBoundedFire(ServerLevel level, Vec3 center, int cap) {
int placed = 0;
BlockPos base = BlockPos.containing(center);
for (BlockPos pos : BlockPos.betweenClosed(base.offset(-2, -1, -2), base.offset(2, 2, 2))) {
if (placed >= cap) return;
if (pos.distToCenterSqr(center.x, center.y, center.z) > 7.0D || !level.isEmptyBlock(pos)) continue;
BlockPos below = pos.below();
if (!level.getBlockState(below).isFaceSturdy(level, below, net.minecraft.core.Direction.UP)) continue;
BlockState fire = Blocks.FIRE.defaultBlockState();
if (fire.canSurvive(level, pos) && level.setBlockAndUpdate(pos, fire)) placed++;
}
}
private static void frostFan(ServerLevel level, Player owner, LivingEntity primary) {
if (primary.isAlive()) {
primary.setTicksFrozen(Math.min(primary.getTicksRequiredToFreeze(), primary.getTicksFrozen() + 50));
primary.addEffect(new net.minecraft.world.effect.MobEffectInstance(net.minecraft.world.effect.MobEffects.MOVEMENT_SLOWDOWN, 60, 1), owner);
}
Vec3 forward = primary.position().subtract(owner.position()).multiply(1.0D, 0.0D, 1.0D);
if (forward.lengthSqr() < 1.0E-5D) forward = owner.getLookAngle().multiply(1.0D, 0.0D, 1.0D);
if (forward.lengthSqr() < 1.0E-5D) forward = new Vec3(0.0D, 0.0D, 1.0D);
forward = forward.normalize();
Vec3 side = new Vec3(-forward.z, 0.0D, forward.x);
for (int distanceStep = 1; distanceStep <= 4; distanceStep++) {
double distance = 2.5D + distanceStep * 2.7D;
for (int lane = -1; lane <= 1; lane++) {
Vec3 point = primary.position().add(forward.scale(distance)).add(side.scale(lane * 1.8D));
BlockPos surface = level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, BlockPos.containing(point));
Vec3 spawn = new Vec3(point.x, surface.getY() + 0.1D, point.z);
RoyalIceSpikeEntity spike = RoyalIceSpikeEntity.create(level, spawn, owner,
(float) AntarchySettings.royalGuardianSwordFrostSpikeDamage(), 1.55D, 100, 60, 1);
level.addFreshEntity(spike);
}
}
Vec3 center = primary.position().add(0.0D, 0.8D, 0.0D);
level.sendParticles(ParticleTypes.SNOWFLAKE, center.x, center.y, center.z, 70, 1.3D, 1.1D, 1.3D, 0.1D);
level.sendParticles(ParticleTypes.SNOWFLAKE, owner.getX(), owner.getY() + 1.0D, owner.getZ(), 18, 0.7D, 0.5D, 0.7D, 0.04D);
level.playSound(null, primary.blockPosition(), SoundEvents.GLASS_BREAK, SoundSource.PLAYERS, 1.0F, 0.6F);
}
private static void chainLightning(ServerLevel level, Player owner, LivingEntity primary) {
Set<UUID> struck = new HashSet<>();
struck.add(primary.getUUID());
strikeVisual(level, primary.position().add(0.0D, primary.getBbHeight() * 0.5D, 0.0D));
applySecondaryDamage(owner, primary, (float) AntarchySettings.royalGuardianSwordStormPrimaryDamage());
LivingEntity previous = primary;
float damage = (float) AntarchySettings.royalGuardianSwordStormJumpDamage();
double jumpRange = Math.max(0.0D, AntarchySettings.royalGuardianSwordStormJumpRange());
for (int jump = 0; jump < Math.max(0, AntarchySettings.royalGuardianSwordStormMaxJumps()); jump++) {
Vec3 origin = previous.position().add(0.0D, previous.getBbHeight() * 0.5D, 0.0D);
LivingEntity next = level.getEntitiesOfClass(LivingEntity.class, new AABB(origin, origin).inflate(jumpRange),
candidate -> !struck.contains(candidate.getUUID()) && canDamageTarget(owner, candidate, false)
&& candidate.distanceToSqr(origin) <= jumpRange * jumpRange)
.stream().min(java.util.Comparator.comparingDouble((LivingEntity candidate) -> candidate.distanceToSqr(origin))
.thenComparingInt(LivingEntity::getId)).orElse(null);
if (next == null) break;
struck.add(next.getUUID());
Vec3 end = next.position().add(0.0D, next.getBbHeight() * 0.5D, 0.0D);
drawLightning(level, origin, end);
applySecondaryDamage(owner, next, damage);
next.setRemainingFireTicks(Math.max(next.getRemainingFireTicks(), 20));
previous = next;
damage *= 0.76F;
}
level.playSound(null, primary.blockPosition(), SoundEvents.LIGHTNING_BOLT_THUNDER, SoundSource.PLAYERS, 0.8F, 1.45F);
}
private static void strikeVisual(ServerLevel level, Vec3 at) {
LightningBolt bolt = new LightningBolt(EntityType.LIGHTNING_BOLT, level);
bolt.moveTo(at.x, at.y, at.z);
bolt.setVisualOnly(true);
level.addFreshEntity(bolt);
level.sendParticles(STORM_GOLD, at.x, at.y, at.z, 20, 0.45D, 0.7D, 0.45D, 0.1D);
}
private static void drawLightning(ServerLevel level, Vec3 start, Vec3 end) {
LightningBolt bolt = new LightningBolt(EntityType.LIGHTNING_BOLT, level);
bolt.moveTo(end.x, end.y, end.z);
bolt.setVisualOnly(true);
level.addFreshEntity(bolt);
Vec3 direction = end.subtract(start);
double length = direction.length();
if (length > 0.001D) {
Vec3 step = direction.scale(1.0D / length);
for (double d = 0.0D; d < length; d += 1.2D) {
Vec3 point = start.add(step.scale(d));
level.sendParticles(STORM_GOLD, point.x, point.y, point.z, 2, 0.08D, 0.08D, 0.08D, 0.04D);
}
}
level.sendParticles(ParticleTypes.ELECTRIC_SPARK, end.x, end.y, end.z, 18, 0.25D, 0.4D, 0.25D, 0.08D);
}
public static boolean canDamageTarget(Player owner, LivingEntity target, boolean primary) {
if (target == null || target == owner || (!primary && !target.isAlive())) return false;
if (target.isAlliedTo(owner) || owner.isAlliedTo(target)) return false;
if (target instanceof OwnableEntity ownable && owner.getUUID().equals(ownable.getOwnerUUID())) return false;
if (target instanceof CommandedEntityAccess access && owner.getUUID().equals(access.antarchy$getCommanderUuid())) return false;
if (target instanceof Player other) return owner.canHarmPlayer(other);
if (primary) return true;
return target instanceof Mob mob && (mob.getType().getCategory() == MobCategory.MONSTER
|| mob.getTarget() == owner || mob.getLastHurtByMob() == owner);
}
private record MeleeAttackContext(UUID playerId, UUID targetId, boolean charged) {
}
}
@@ -4,14 +4,25 @@ import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.client.model.ResourceBackedGeoItemModel;
import com.craisinlord.antarchy.content.client.renderer.AnimatedHeldItemRenderer;
import java.util.function.Consumer;
import java.util.List;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResultHolder;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.EquipmentSlotGroup;
import net.minecraft.world.entity.ai.attributes.AttributeModifier;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.item.SwordItem;
import net.minecraft.world.item.Tier;
import net.minecraft.world.item.component.CustomData;
import net.minecraft.core.component.DataComponents;
import net.minecraft.world.item.component.ItemAttributeModifiers;
import software.bernie.geckolib.animatable.GeoItem;
import software.bernie.geckolib.animatable.client.GeoRenderProvider;
@@ -25,6 +36,8 @@ public class RoyalGuardianSwordItem extends SwordItem implements GeoItem {
private static final ResourceLocation ANIMATION_LOCATION = ResourceLocation.fromNamespaceAndPath("antarchy", "animations/static_item.animation.json");
private static final ResourceLocation REACH_MODIFIER_ID = ResourceLocation.fromNamespaceAndPath("antarchy", "royal_guardian_sword_reach");
private static final ResourceLocation KNOCKBACK_MODIFIER_ID = ResourceLocation.fromNamespaceAndPath("antarchy", "royal_guardian_sword_knockback");
private static final String MODE_TAG = "antarchy.royal_guardian_sword.mode";
private static final String DISCHARGE_READY_AT_TAG = "antarchy.royal_guardian_sword.discharge_ready_at";
private final Tier tier;
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
@@ -60,6 +73,79 @@ public class RoyalGuardianSwordItem extends SwordItem implements GeoItem {
return AntarchySettings.royalWeaponEnchantability();
}
@Override
public InteractionResultHolder<ItemStack> use(net.minecraft.world.level.Level level, Player player, InteractionHand hand) {
ItemStack stack = player.getItemInHand(hand);
if (!player.isShiftKeyDown()) return InteractionResultHolder.pass(stack);
cycleMode(level, player, stack);
return InteractionResultHolder.sidedSuccess(stack, level.isClientSide);
}
private void cycleMode(net.minecraft.world.level.Level level, Player player, ItemStack stack) {
Mode next = getMode(stack).next();
setMode(stack, next);
if (!level.isClientSide) {
player.displayClientMessage(Component.translatable("message.antarchy.royal_guardian_sword.mode",
Component.translatable(next.translationKey)), true);
level.playSound(null, player.blockPosition(), SoundEvents.AMETHYST_BLOCK_CHIME, SoundSource.PLAYERS, 0.8F, 0.8F + next.ordinal() * 0.2F);
}
}
private static void setMode(ItemStack stack, Mode mode) {
CustomData.update(DataComponents.CUSTOM_DATA, stack, tag -> {
if (mode == Mode.FIRE) tag.remove(MODE_TAG);
else tag.putInt(MODE_TAG, mode.ordinal());
});
}
public boolean shouldCauseReequipAnimation(ItemStack oldStack, ItemStack newStack, boolean slotChanged) {
return slotChanged || !ItemStack.isSameItem(oldStack, newStack);
}
@Override
public void appendHoverText(ItemStack stack, Item.TooltipContext context, List<Component> tooltipComponents, TooltipFlag tooltipFlag) {
Mode mode = getMode(stack);
tooltipComponents.add(Component.translatable("tooltip.antarchy.royal_guardian_sword.mode",
Component.translatable(mode.translationKey)).withStyle(ChatFormatting.GOLD));
tooltipComponents.add(Component.translatable(mode.tooltipKey).withStyle(ChatFormatting.GRAY));
tooltipComponents.add(Component.translatable("tooltip.antarchy.royal_guardian_sword.controls",
AntarchySettings.royalGuardianSwordElementalCooldownTicks() / 20.0D).withStyle(ChatFormatting.DARK_GRAY));
super.appendHoverText(stack, context, tooltipComponents, tooltipFlag);
}
public static Mode getMode(ItemStack stack) {
int value = stack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY).copyTag().getInt(MODE_TAG);
return Mode.values()[Math.floorMod(value, Mode.values().length)];
}
public static boolean isDischargeReady(ItemStack stack, long gameTime) {
return stack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY).copyTag()
.getLong(DISCHARGE_READY_AT_TAG) <= gameTime;
}
public static void startDischargeCooldown(ItemStack stack, long gameTime) {
CustomData.update(DataComponents.CUSTOM_DATA, stack, tag -> tag.putLong(DISCHARGE_READY_AT_TAG,
gameTime + Math.max(0, AntarchySettings.royalGuardianSwordElementalCooldownTicks())));
}
public enum Mode {
FIRE("tooltip.antarchy.royal_guardian_sword.mode.fire", "tooltip.antarchy.royal_guardian_sword.fire"),
FROST("tooltip.antarchy.royal_guardian_sword.mode.frost", "tooltip.antarchy.royal_guardian_sword.frost"),
STORM("tooltip.antarchy.royal_guardian_sword.mode.storm", "tooltip.antarchy.royal_guardian_sword.storm");
private final String translationKey;
private final String tooltipKey;
Mode(String translationKey, String tooltipKey) {
this.translationKey = translationKey;
this.tooltipKey = tooltipKey;
}
public Mode next() {
return values()[(this.ordinal() + 1) % values().length];
}
}
@Override
public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
}
@@ -0,0 +1,18 @@
package com.craisinlord.antarchy.content.network;
import com.craisinlord.antarchy.Antarchy;
import io.netty.buffer.ByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
public record ActivateRoyalBoundaryPayload() implements CustomPacketPayload {
public static final Type<ActivateRoyalBoundaryPayload> TYPE = new Type<>(
ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "activate_royal_boundary"));
public static final StreamCodec<ByteBuf, ActivateRoyalBoundaryPayload> STREAM_CODEC = StreamCodec.unit(new ActivateRoyalBoundaryPayload());
@Override
public Type<ActivateRoyalBoundaryPayload> type() {
return TYPE;
}
}
@@ -1,26 +1,32 @@
package com.craisinlord.antarchy.content.network;
import com.craisinlord.antarchy.content.antmail.AntmailAttachment;
import com.craisinlord.antarchy.content.antmail.AntmailDebug;
import com.craisinlord.antarchy.content.antmail.AntmailDraft;
import com.craisinlord.antarchy.content.antmail.AntmailWire;
import net.minecraft.core.BlockPos;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
public final class AntmailNetworking {
private static Consumer<Object> sender = payload -> { };
private static volatile boolean senderConfigured;
private static final AtomicBoolean senderErrorLogged = new AtomicBoolean();
private AntmailNetworking() {
}
public static void setSender(Consumer<Object> sender) {
AntmailNetworking.sender = sender;
senderConfigured = sender != null;
}
public static void setup(BlockPos pos, String username) {
sender.accept(new AntmailSetupPayload(pos, username));
AntmailDebug.log("C2S setup request pos=" + pos + " usernameLength=" + username.length());
sendPayload(new AntmailSetupPayload(pos, username));
}
public static void requestState(BlockPos pos) {
@@ -32,38 +38,46 @@ public final class AntmailNetworking {
}
public static void requestState(BlockPos pos, int folder, int page, long knownVersion) {
sender.accept(new AntmailStateRequestPayload(pos, folder, page, knownVersion));
AntmailDebug.log("C2S state request pos=" + pos + " folder=" + folder + " page=" + page + " knownVersion=" + knownVersion);
sendPayload(new AntmailStateRequestPayload(pos, folder, page, knownVersion));
}
public static void requestMessage(BlockPos pos, UUID messageId) {
sender.accept(new AntmailMessageRequestPayload(pos, messageId.toString()));
sendPayload(new AntmailMessageRequestPayload(pos, messageId.toString()));
}
public static void send(BlockPos pos, String recipient, String subject, String body, List<AntmailAttachment> attachments) {
sender.accept(new AntmailSendPayload(pos, recipient, subject, body, AntmailWire.encodeAttachments(attachments)));
sendPayload(new AntmailSendPayload(pos, recipient, subject, body, AntmailWire.encodeAttachments(attachments)));
}
public static void markRead(BlockPos pos, UUID messageId) {
sender.accept(new AntmailReadPayload(pos, messageId.toString(), true));
sendPayload(new AntmailReadPayload(pos, messageId.toString(), true));
}
public static void markUnread(BlockPos pos, UUID messageId) {
sender.accept(new AntmailReadPayload(pos, messageId.toString(), false));
sendPayload(new AntmailReadPayload(pos, messageId.toString(), false));
}
public static void delete(BlockPos pos, UUID messageId, boolean sent) {
sender.accept(new AntmailDeletePayload(pos, messageId.toString(), sent));
sendPayload(new AntmailDeletePayload(pos, messageId.toString(), sent));
}
public static void saveDraft(BlockPos pos, AntmailDraft draft) {
sender.accept(new AntmailDraftPayload(pos, AntmailDraftPayload.SAVE, AntmailWire.encodeTag(draft.toTag())));
sendPayload(new AntmailDraftPayload(pos, AntmailDraftPayload.SAVE, AntmailWire.encodeTag(draft.toTag())));
}
public static void deleteDraft(BlockPos pos, UUID draftId) {
sender.accept(new AntmailDraftPayload(pos, AntmailDraftPayload.DELETE, draftId.toString()));
sendPayload(new AntmailDraftPayload(pos, AntmailDraftPayload.DELETE, draftId.toString()));
}
public static void retry(BlockPos pos, UUID messageId) {
sender.accept(new AntmailRetryPayload(pos, messageId.toString()));
sendPayload(new AntmailRetryPayload(pos, messageId.toString()));
}
private static void sendPayload(Object payload) {
if (!senderConfigured) {
if (senderErrorLogged.compareAndSet(false, true)) AntmailDebug.error("Network sender is not configured; dropping " + payload.getClass().getSimpleName(), new IllegalStateException("AntmailNetworking.setSender was not called"));
}
else sender.accept(payload);
}
}
@@ -1,6 +1,7 @@
package com.craisinlord.antarchy.content.network;
import com.craisinlord.antarchy.content.antmail.AntmailAddress;
import com.craisinlord.antarchy.content.antmail.AntmailDebug;
import com.craisinlord.antarchy.content.antmail.AntmailAttachment;
import com.craisinlord.antarchy.content.antmail.AntmailDeliveryResult;
import com.craisinlord.antarchy.content.antmail.AntmailDraft;
@@ -20,17 +21,22 @@ import java.util.function.BiConsumer;
public final class AntmailServerHandler {
private static BiConsumer<ServerPlayer, AntmailResultPayload> resultSender = (player, payload) -> { };
private static volatile boolean resultSenderConfigured;
private AntmailServerHandler() {
}
public static void setResultSender(BiConsumer<ServerPlayer, AntmailResultPayload> sender) {
resultSender = sender;
resultSenderConfigured = sender != null;
}
public static void handle(ServerPlayer player, AntmailSetupPayload payload) {
AntmailDebug.log("C2S setup received player=" + player.getGameProfile().getName() + " pos=" + payload.pos()
+ " usernameLength=" + payload.username().length());
ComputerBlockEntity computer = computer(player, payload.pos());
if (computer == null || !computer.canUseFileSystem(player)) {
AntmailDebug.log("setup rejected: computer missing, out of reach, unauthenticated, or owned by another user");
result(player, payload.pos(), AntmailDeliveryResult.Status.FAILED.ordinal(), "", "", "registration_failed:unauthorized", "");
return;
}
@@ -43,8 +49,11 @@ public final class AntmailServerHandler {
}
public static void handle(ServerPlayer player, AntmailStateRequestPayload payload) {
AntmailDebug.log("state request received player=" + player.getGameProfile().getName() + " pos=" + payload.pos()
+ " folder=" + payload.folder() + " page=" + payload.page() + " knownVersion=" + payload.knownVersion());
ComputerBlockEntity computer = authorizedComputer(player, payload.pos());
if (computer == null) {
AntmailDebug.log("state request rejected: computer missing, out of reach, unauthenticated, or owned by another user");
result(player, payload.pos(), AntmailDeliveryResult.Status.FAILED.ordinal(), "", "", "unauthorized", "");
return;
}
@@ -63,7 +72,13 @@ public final class AntmailServerHandler {
int folder = payload.folder() < AntmailStateRequestPayload.INBOX || payload.folder() > AntmailStateRequestPayload.DRAFTS
? AntmailStateRequestPayload.INBOX : payload.folder();
int page = Math.max(0, Math.min(payload.page(), AntmailValidation.MAX_MAILBOX_MESSAGES / AntmailMailbox.PAGE_SIZE));
result(player, payload.pos(), AntmailDeliveryResult.Status.DELIVERED.ordinal(), address.fullAddress(), "", "", AntmailWire.encodeTag(mailbox.toPageTag(folder, page)));
try {
String encoded = AntmailWire.encodeTag(mailbox.toPageTag(folder, page));
result(player, payload.pos(), AntmailDeliveryResult.Status.DELIVERED.ordinal(), address.fullAddress(), "", "", encoded);
} catch (RuntimeException exception) {
AntmailDebug.error("Failed to encode state for " + address.fullAddress() + " at " + payload.pos(), exception);
result(player, payload.pos(), AntmailDeliveryResult.Status.FAILED.ordinal(), address.fullAddress(), "", "state_encode_failed", "");
}
}
public static void handle(ServerPlayer player, AntmailMessageRequestPayload payload) {
@@ -240,6 +255,12 @@ public final class AntmailServerHandler {
if (!address.isBlank()) version = AntmailServerData.access(player.server).mailboxVersion(AntmailAddress.parse(address));
} catch (IllegalArgumentException ignored) {
}
AntmailDebug.log("sending result player=" + player.getGameProfile().getName() + " pos=" + pos + " status=" + status
+ " detail=" + detail + " addressPresent=" + !address.isBlank() + " dataChars=" + data.length() + " version=" + version);
if (!resultSenderConfigured) {
AntmailDebug.error("Result sender is not configured; dropping response at " + pos, new IllegalStateException("AntmailServerHandler.setResultSender was not called"));
return;
}
resultSender.accept(player, new AntmailResultPayload(pos, status, address, messageId, detail, data, version));
}
}
@@ -5,6 +5,7 @@ import com.craisinlord.antarchy.content.computer.ComputerFileSystem;
import com.craisinlord.antarchy.content.computer.terminal.TerminalCommandService;
import com.craisinlord.antarchy.content.computer.terminal.TerminalFileSystem;
import com.craisinlord.antarchy.content.computer.terminal.TerminalResult;
import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.antmail.AntmailWire;
import com.craisinlord.antarchy.content.computer.blockle.BlockleAnswers;
import com.craisinlord.antarchy.content.computer.blockle.BlockleDictionary;
@@ -13,6 +14,9 @@ import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.Level;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.core.registries.Registries;
import net.minecraft.tags.TagKey;
import net.minecraft.world.level.levelgen.structure.Structure;
import net.minecraft.world.level.block.entity.BlockEntity;
import java.util.function.BiConsumer;
@@ -68,6 +72,7 @@ public final class ComputerAccessHandler {
case ComputerAccessPayload.ANTMAN_STATE -> antmanState(player, computer, payload);
case ComputerAccessPayload.BLOCKLE_STATE -> blockleState(player, computer, payload, false);
case ComputerAccessPayload.BLOCKLE_GUESS -> blockleState(player, computer, payload, true);
case ComputerAccessPayload.LOCATE_STRUCTURE -> locateStructure(player, computer, payload);
default -> send(player, payload, ComputerAccessResultPayload.INVALID);
}
}
@@ -75,6 +80,7 @@ public final class ComputerAccessHandler {
private static void open(ServerPlayer player, ComputerBlockEntity computer, ComputerAccessPayload payload) {
if (computer.hasActiveUser() && !computer.isAuthenticatedBy(player)) {
send(player, payload, ComputerAccessResultPayload.BUSY);
sendArchive(player, computer, payload);
return;
}
if (!computer.hasPassword()) {
@@ -86,6 +92,13 @@ public final class ComputerAccessHandler {
} else {
send(player, payload, ComputerAccessResultPayload.READY);
}
sendArchive(player, computer, payload);
}
private static void sendArchive(ServerPlayer player, ComputerBlockEntity computer, ComputerAccessPayload payload) {
resultSender.accept(player, new ComputerAccessResultPayload(payload.pos(), ComputerAccessResultPayload.SUCCESS,
computer.hasPassword(), computer.isAuthenticated(), ComputerAccessPayload.ARCHIVE_STATE + "\0\0"
+ com.craisinlord.antarchy.content.guide.ComputerGuideData.encodeNetworkSnapshot()));
}
private static void setup(ServerPlayer player, ComputerBlockEntity computer, ComputerAccessPayload payload) {
@@ -204,10 +217,6 @@ public final class ComputerAccessHandler {
}
private static void terminalCommand(ServerPlayer player, ComputerBlockEntity computer, ComputerAccessPayload payload) {
if (!computer.canUseFileSystem(player)) {
send(player, payload, ComputerAccessResultPayload.INVALID);
return;
}
String[] request = splitFileRequest(payload.value());
if (request == null) {
sendTerminal(player, payload, TerminalResult.error("/", "ERROR: INVALID REQUEST"));
@@ -320,7 +329,7 @@ public final class ComputerAccessHandler {
return;
}
ResourceLocation disk = ResourceLocation.fromNamespaceAndPath("antarchy", "blockle_game");
if (!computer.diskIds().contains(disk)) {
if (!AntarchySettings.unlockAllArchives() && !computer.diskIds().contains(disk)) {
sendFile(player, payload, false, "", "not_installed");
return;
}
@@ -332,6 +341,10 @@ public final class ComputerAccessHandler {
long day = overworld.getDayTime() / 24000L;
computer.resetBlockle(day);
BlockleAnswers.Answer answer = BlockleAnswers.answerForDay(day);
if (answer == null) {
sendFile(player, payload, false, "", "answers_unavailable");
return;
}
if (submit) {
String guess = payload.value() == null ? "" : payload.value().toLowerCase(java.util.Locale.ROOT);
if (guess.length() != 5 || !guess.chars().allMatch(value -> value >= 'a' && value <= 'z')) {
@@ -351,6 +364,49 @@ public final class ComputerAccessHandler {
sendFile(player, payload, true, encodeBlockle(day, answer, computer.blockleGuesses()), "");
}
private static void locateStructure(ServerPlayer player, ComputerBlockEntity computer, ComputerAccessPayload payload) {
ResourceLocation entryId;
try {
entryId = ResourceLocation.parse(payload.value());
} catch (RuntimeException exception) {
sendFile(player, payload, false, "", "unauthorized");
return;
}
var entry = com.craisinlord.antarchy.content.guide.ComputerGuideData.entry(entryId);
boolean unlocked = com.craisinlord.antarchy.content.guide.ComputerGuideData.entriesFor(computer.diskIds()).stream()
.anyMatch(candidate -> candidate.id().equals(entryId));
if (!computer.canUseFileSystem(player) || !unlocked || entry == null || entry.structureId().isBlank()
|| entry.structureTagId().isBlank() || entry.dimensionId().isBlank() || entry.searchRadius() <= 0) {
sendFile(player, payload, false, "", "unauthorized");
return;
}
ResourceLocation dimensionId;
ResourceLocation structureTagId;
try {
dimensionId = ResourceLocation.parse(entry.dimensionId());
structureTagId = ResourceLocation.parse(entry.structureTagId());
} catch (RuntimeException exception) {
sendFile(player, payload, false, "", "dimension_unavailable");
return;
}
ServerLevel targetLevel = player.server.getLevel(net.minecraft.resources.ResourceKey.create(Registries.DIMENSION, dimensionId));
if (targetLevel == null) {
sendFile(player, payload, false, "", "dimension_unavailable");
return;
}
TagKey<Structure> structures = TagKey.create(Registries.STRUCTURE, structureTagId);
net.minecraft.core.BlockPos origin = computer.getBlockPos();
var nearest = targetLevel.findNearestMapStructure(structures, origin, entry.searchRadius(), false);
if (nearest == null) {
sendFile(player, payload, false, "", "not_found");
return;
}
sendFile(player, payload, true, nearest.getX() + ", " + nearest.getY() + ", " + nearest.getZ(), "");
}
private static String encodeBlockle(long day, BlockleAnswers.Answer answer, java.util.List<String> guesses) {
StringBuilder result = new StringBuilder(Long.toString(day));
boolean solved = false;
@@ -29,6 +29,8 @@ public record ComputerAccessPayload(BlockPos pos, int action, String value) impl
public static final int ANTMAN_STATE = 17;
public static final int BLOCKLE_STATE = 18;
public static final int BLOCKLE_GUESS = 19;
public static final int ARCHIVE_STATE = 20;
public static final int LOCATE_STRUCTURE = 21;
public static final Type<ComputerAccessPayload> TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "computer_access"));
public static final StreamCodec<RegistryFriendlyByteBuf, ComputerAccessPayload> STREAM_CODEC = StreamCodec.composite(
BlockPos.STREAM_CODEC, ComputerAccessPayload::pos,
@@ -103,4 +103,8 @@ public final class ComputerNetworking {
public static void submitBlockleGuess(BlockPos pos, String guess) {
sender.accept(new ComputerAccessPayload(pos, ComputerAccessPayload.BLOCKLE_GUESS, guess));
}
public static void locateStructure(BlockPos pos, ResourceLocation entryId) {
sender.accept(new ComputerAccessPayload(pos, ComputerAccessPayload.LOCATE_STRUCTURE, entryId.toString()));
}
}
@@ -0,0 +1,20 @@
package com.craisinlord.antarchy.content.network;
import com.craisinlord.antarchy.Antarchy;
import io.netty.buffer.ByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
public record KingJudgmentFlashPayload(int durationTicks) implements CustomPacketPayload {
public static final Type<KingJudgmentFlashPayload> TYPE =
new Type<>(ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "king_judgment_flash"));
public static final StreamCodec<ByteBuf, KingJudgmentFlashPayload> STREAM_CODEC =
ByteBufCodecs.VAR_INT.map(KingJudgmentFlashPayload::new, KingJudgmentFlashPayload::durationTicks);
@Override
public Type<KingJudgmentFlashPayload> type() {
return TYPE;
}
}
@@ -0,0 +1,22 @@
package com.craisinlord.antarchy.content.network;
import java.util.function.BiConsumer;
import net.minecraft.server.level.ServerPlayer;
public final class KingJudgmentFlashSync {
private static final BiConsumer<ServerPlayer, Integer> NOOP = (player, ticks) -> {
};
private static volatile BiConsumer<ServerPlayer, Integer> sink = NOOP;
private KingJudgmentFlashSync() {
}
public static void setSink(BiConsumer<ServerPlayer, Integer> sink) {
KingJudgmentFlashSync.sink = sink == null ? NOOP : sink;
}
public static void send(ServerPlayer player, int ticks) {
sink.accept(player, ticks);
}
}
@@ -5,6 +5,7 @@ import java.util.LinkedHashSet;
import java.util.Set;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.tags.BlockTags;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
@@ -46,6 +47,12 @@ public final class PermanentPortalTeleporter {
}
Vec3 arrival = findArrivalPosition(entity, destination, portalPos, type);
if (arrival == null) {
if (entity instanceof ServerPlayer player) {
player.displayClientMessage(net.minecraft.network.chat.Component.translatable("message.antarchy.teleport_arrival_failed"), true);
}
return new DimensionTransition(sourceLevel, entity, DimensionTransition.DO_NOTHING);
}
return new DimensionTransition(destination, arrival, Vec3.ZERO, entity.getYRot(), entity.getXRot(), DimensionTransition.PLAY_PORTAL_SOUND);
}
@@ -57,17 +64,24 @@ public final class PermanentPortalTeleporter {
return sourceLevel.getServer().getLevel(type.primaryDimension());
}
@Nullable
private static Vec3 findArrivalPosition(Entity entity, ServerLevel destination, BlockPos preferredPos, PermanentPortalType type) {
PermanentPortalShape active = PermanentPortalShape.findActiveNear(destination, preferredPos, type);
if (active != null) {
return active.center();
Vec3 safePortalPosition = tryFindSafePosition(entity, destination, BlockPos.containing(active.center()));
if (safePortalPosition != null) {
return safePortalPosition;
}
}
Vec3 safe = findSafeArrivalPosition(entity, destination, preferredPos, type);
if (safe != null) {
PermanentPortalShape nearbyActive = findActiveNearby(destination, BlockPos.containing(safe), type);
if (nearbyActive != null) {
return nearbyActive.center();
Vec3 safePortalPosition = tryFindSafePosition(entity, destination, BlockPos.containing(nearbyActive.center()));
if (safePortalPosition != null) {
return safePortalPosition;
}
}
PermanentPortalShape created = createReturnPortal(destination, safe, type);
@@ -77,11 +91,7 @@ public final class PermanentPortalTeleporter {
return createFallbackPortal(destination, BlockPos.containing(safe), 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);
return null;
}
@Nullable
@@ -119,7 +129,8 @@ public final class PermanentPortalTeleporter {
}
Set<BlockPos> candidates = new LinkedHashSet<>();
if (type != PermanentPortalType.ELYTHIA) {
boolean elythia = destination.dimension() == PermanentPortalType.ELYTHIA.primaryDimension();
if (!elythia) {
addCandidate(candidates, preferredPos);
}
@@ -132,9 +143,16 @@ public final class PermanentPortalTeleporter {
BlockPos horizontalPos = preferredPos.offset(xOff, 0, zOff);
if (destination.hasChunkAt(horizontalPos)) {
addCandidate(candidates, destination.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, horizontalPos));
BlockPos surface = destination.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, horizontalPos);
if (elythia) {
candidates.add(surface);
candidates.add(surface.above());
} else {
addCandidate(candidates, surface);
addCandidate(candidates, destination.getHeightmapPos(Heightmap.Types.WORLD_SURFACE, horizontalPos));
}
}
if (!elythia) {
for (int yOff = 0; yOff <= VERTICAL_SEARCH; yOff++) {
addCandidate(candidates, horizontalPos.above(yOff));
if (yOff > 0) {
@@ -144,8 +162,9 @@ public final class PermanentPortalTeleporter {
}
}
}
}
if (type == PermanentPortalType.ELYTHIA) {
if (elythia) {
addCandidate(candidates, preferredPos);
}
@@ -300,6 +319,36 @@ public final class PermanentPortalTeleporter {
private static boolean isValidArrivalPosition(ServerLevel destination, Vec3 safePos) {
BlockPos standPos = BlockPos.containing(safePos);
if (destination.dimension() == PermanentPortalType.ELYTHIA.primaryDimension()) {
BlockPos supportPos = standPos.below();
if (!destination.hasChunkAt(standPos) || !destination.hasChunkAt(supportPos)) {
return false;
}
BlockState support = destination.getBlockState(supportPos);
if (!support.isFaceSturdy(destination, supportPos, Direction.UP)
|| support.is(BlockTags.LEAVES)
|| !support.getFluidState().isEmpty()) {
return false;
}
int nearbySupports = 0;
for (Direction direction : Direction.Plane.HORIZONTAL) {
BlockPos neighbor = supportPos.relative(direction);
if (!destination.hasChunkAt(neighbor)) {
continue;
}
for (int yOff = -2; yOff <= 2; yOff++) {
BlockPos nearby = neighbor.offset(0, yOff, 0);
BlockState nearbyState = destination.getBlockState(nearby);
if (nearbyState.isFaceSturdy(destination, nearby, Direction.UP)
&& !nearbyState.is(BlockTags.LEAVES)
&& nearbyState.getFluidState().isEmpty()) {
nearbySupports++;
break;
}
}
}
return nearbySupports >= 2;
}
if (destination.dimension() == AntarchySettings.termiteDestinationDimension()
|| destination.dimension() == AntarchySettings.redAntDestinationDimension()) {
if (!destination.hasChunkAt(standPos) || !destination.hasChunkAt(standPos.below())) {
@@ -28,11 +28,11 @@ public final class ThoraxisTransitionThroatFeature extends Feature<NoneFeatureCo
for (int y = undersideSurface; y <= upperSurface; y++) {
int layerOffsetX = noise(seed, Math.floorDiv(y, 5), 17) * 2;
int layerOffsetZ = noise(seed, Math.floorDiv(y, 5), 31) * 2;
double radiusX = 3.5D + noise01(seed, Math.floorDiv(y, 11), 43) * 2.0D;
double radiusZ = 3.5D + noise01(seed, Math.floorDiv(y, 13), 59) * 2.0D;
double radiusX = 5.0D + noise01(seed, Math.floorDiv(y, 11), 43) * 2.5D;
double radiusZ = 5.0D + noise01(seed, Math.floorDiv(y, 13), 59) * 2.5D;
for (int x = -7; x <= 7; x++) {
for (int z = -7; z <= 7; z++) {
for (int x = -9; x <= 9; x++) {
for (int z = -9; z <= 9; z++) {
double dx = x - layerOffsetX;
double dz = z - layerOffsetZ;
double distance = Math.sqrt((dx * dx) / (radiusX * radiusX) + (dz * dz) / (radiusZ * radiusZ));
@@ -32,7 +32,7 @@ public final class ThoraxisUndersideManager {
public static final int GRAVITY_FLIP_Y = 0;
private static final net.minecraft.resources.ResourceLocation THORAXIS_DIMENSION = net.minecraft.resources.ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "thoraxis");
private static final AntarchyGravityTransition TRANSITION = new AntarchyGravityTransition(12);
private static final AntarchyGravityTransition THROAT_TRANSITION = new AntarchyGravityTransition(20);
private static final AntarchyGravityTransition THROAT_TRANSITION = new AntarchyGravityTransition(12);
private static final int EFFECT_DURATION_TICKS = 50;
private static final int EFFECT_REFRESH_THRESHOLD_TICKS = 10;
private static final int DISCOVERY_INTERVAL_TICKS = 5;
@@ -43,7 +43,7 @@ public final class ThoraxisUndersideManager {
private static final int ENTER_UNDERSIDE_Y = GRAVITY_FLIP_Y - 4;
private static final int EXIT_UNDERSIDE_Y = GRAVITY_FLIP_Y + 4;
private static final int FLIP_COOLDOWN_TICKS = 40;
private static final int CROSSING_TIMEOUT_TICKS = 30;
private static final int CROSSING_TIMEOUT_TICKS = 50;
private static final int SETTLE_TIMEOUT_TICKS = 70;
private static final double MIN_CROSSING_SPEED = 0.48D;
private static final Map<ServerLevel, TrackingState> STATES = new WeakHashMap<>();
@@ -231,9 +231,7 @@ public final class ThoraxisUndersideManager {
if (!crossing.toUnderside) {
refreshInvertedEffect(player);
}
if (crossing.ticks > CROSSING_TIMEOUT_TICKS
|| (crossing.toUnderside && velocity.y > 0.08D)
|| (!crossing.toUnderside && velocity.y < -0.08D)) {
if (crossing.ticks > CROSSING_TIMEOUT_TICKS) {
tracking.playerCrossings.remove(player.getUUID());
return;
}
@@ -248,6 +246,11 @@ public final class ThoraxisUndersideManager {
? AntarchyGravityDirection.UP
: AntarchyGravityDirection.DOWN;
AntarchyGravityApi.setAirborneGravityDirection(player, destination, crossing.toUnderside, THROAT_TRANSITION);
double assistedVerticalVelocity = crossing.toUnderside
? Math.min(velocity.y, -0.7D)
: Math.max(velocity.y, 0.7D);
AntarchyGravityApi.setWorldVelocity(player,
new Vec3(velocity.x, assistedVerticalVelocity, velocity.z));
player.resetFallDistance();
crossing.flipped = true;
crossing.ticks = 0;
@@ -25,10 +25,23 @@ public final class AntarchyMixinPlugin implements IMixinConfigPlugin {
@Override
public boolean shouldApplyMixin(String targetClassName, String mixinClassName) {
String resourcePath = targetClassName.replace('.', '/') + ".class";
if (this.getClass().getClassLoader().getResource(resourcePath) == null) {
if (!classPresent(targetClassName)) {
return false;
}
if (mixinClassName.startsWith("com.craisinlord.antarchy.mixins.compat.scguns.")) {
return classPresent("top.ribs.scguns.entity.projectile.ProjectileEntity");
}
if (mixinClassName.startsWith("com.craisinlord.antarchy.mixins.compat.irons_spellbooks.")) {
return classPresent("io.redspace.ironsspellbooks.entity.spells.AbstractMagicProjectile");
}
if (mixinClassName.startsWith("com.craisinlord.antarchy.mixins.compat.ars_nouveau.")) {
return classPresent("com.hollingsworth.arsnouveau.common.entity.EntityProjectileSpell");
}
if (mixinClassName.startsWith("com.craisinlord.antarchy.mixins.compat.alexscaves.")) {
// The target class was found above. Checking the target itself avoids depending on
// whether a loader exposes Alex's Caves' entrypoint resource to this classloader.
return true;
}
if (mixinClassName.endsWith(".VillagerTradesMixin")) {
// Trade registration is platform-driven on every supported loader now. Keeping the
// static VillagerTrades class-init mixin around risks touching AntarchyObjects before
@@ -38,6 +51,16 @@ public final class AntarchyMixinPlugin implements IMixinConfigPlugin {
return true;
}
private static boolean classPresent(String className) {
String resourcePath = className.replace('.', '/') + ".class";
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
if (contextClassLoader != null && contextClassLoader.getResource(resourcePath) != null) {
return true;
}
ClassLoader mixinClassLoader = AntarchyMixinPlugin.class.getClassLoader();
return mixinClassLoader != null && mixinClassLoader.getResource(resourcePath) != null;
}
@Override
public void acceptTargets(Set<String> myTargets, Set<String> otherTargets) {
}
@@ -13,6 +13,8 @@ public abstract class CommandedMobMixin implements CommandedEntityAccess {
private UUID antarchy$commanderUuid;
@Unique
private boolean antarchy$commandedTargetOwned;
@Unique
private boolean antarchy$royalInvested;
@Override
public UUID antarchy$getCommanderUuid() {
@@ -34,11 +36,22 @@ public abstract class CommandedMobMixin implements CommandedEntityAccess {
antarchy$commandedTargetOwned = owned;
}
@Override
public boolean antarchy$isRoyalInvested() {
return antarchy$royalInvested;
}
@Override
public void antarchy$setRoyalInvested(boolean invested) {
antarchy$royalInvested = invested;
}
@org.spongepowered.asm.mixin.injection.Inject(method = "addAdditionalSaveData", at = @org.spongepowered.asm.mixin.injection.At("TAIL"))
private void antarchy$saveCommandedData(CompoundTag tag, org.spongepowered.asm.mixin.injection.callback.CallbackInfo ci) {
if (antarchy$commanderUuid != null) {
tag.putUUID("AntarchyCommander", antarchy$commanderUuid);
tag.putBoolean("AntarchyCommandedTargetOwned", antarchy$commandedTargetOwned);
tag.putBoolean("AntarchyRoyalInvested", antarchy$royalInvested);
}
}
@@ -47,6 +60,7 @@ public abstract class CommandedMobMixin implements CommandedEntityAccess {
if (tag.hasUUID("AntarchyCommander")) {
antarchy$commanderUuid = tag.getUUID("AntarchyCommander");
antarchy$commandedTargetOwned = tag.getBoolean("AntarchyCommandedTargetOwned");
antarchy$royalInvested = tag.getBoolean("AntarchyRoyalInvested");
}
}
}
@@ -0,0 +1,42 @@
package com.craisinlord.antarchy.mixins;
import com.craisinlord.antarchy.content.effect.JudgmentMarkAccess;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.world.entity.LivingEntity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(LivingEntity.class)
public abstract class JudgmentMarkEntityMixin implements JudgmentMarkAccess {
@Unique
private static final EntityDataAccessor<Boolean> ANTARCHY_JUDGMENT_MARKED =
SynchedEntityData.defineId(LivingEntity.class, EntityDataSerializers.BOOLEAN);
@Inject(method = "defineSynchedData", at = @At("TAIL"))
private void antarchy$defineJudgmentState(SynchedEntityData.Builder builder, CallbackInfo ci) {
builder.define(ANTARCHY_JUDGMENT_MARKED, false);
}
@Inject(method = "tick", at = @At("TAIL"))
private void antarchy$tickJudgmentState(CallbackInfo ci) {
com.craisinlord.antarchy.content.effect.JudgmentMarkManager.tickTarget((LivingEntity) (Object) this);
}
@Override
public boolean antarchy$isJudgmentMarked() {
return ((LivingEntity) (Object) this).getEntityData().get(ANTARCHY_JUDGMENT_MARKED);
}
@Override
public void antarchy$setJudgmentMarked(boolean marked) {
LivingEntity entity = (LivingEntity) (Object) this;
if (!entity.level().isClientSide && entity.getEntityData().get(ANTARCHY_JUDGMENT_MARKED) != marked) {
entity.getEntityData().set(ANTARCHY_JUDGMENT_MARKED, marked);
}
}
}
@@ -0,0 +1,21 @@
package com.craisinlord.antarchy.mixins;
import com.craisinlord.antarchy.content.effect.RoyalBoundaryManager;
import net.minecraft.world.entity.projectile.Projectile;
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(Projectile.class)
public abstract class ProjectileRoyalBoundaryMixin {
@Inject(method = "tick", at = @At("HEAD"))
private void antarchy$prepareRoyalBoundary(CallbackInfo ci) {
RoyalBoundaryManager.prepareProjectile((Projectile) (Object) this);
}
@Inject(method = "tick", at = @At("TAIL"))
private void antarchy$processRoyalBoundary(CallbackInfo ci) {
RoyalBoundaryManager.tickProjectile((Projectile) (Object) this);
}
}
@@ -0,0 +1,17 @@
package com.craisinlord.antarchy.mixins;
import com.craisinlord.antarchy.content.effect.RoyalBoundaryManager;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
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(Entity.class)
public abstract class RoyalBoundaryOwnerCleanupMixin {
@Inject(method = "remove", at = @At("HEAD"))
private void antarchy$removeRoyalBoundary(Entity.RemovalReason reason, CallbackInfo ci) {
if ((Object) this instanceof ServerPlayer player) RoyalBoundaryManager.clearOwner(player);
}
}
@@ -0,0 +1,23 @@
package com.craisinlord.antarchy.mixins;
import com.craisinlord.antarchy.content.item.RoyalGuardianSwordAbilities;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
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;
/** Captures attack charge before vanilla resets the player's attack-strength ticker. */
@Mixin(Player.class)
public abstract class RoyalGuardianPlayerAttackMixin {
@Inject(method = "attack", at = @At("HEAD"))
private void antarchy$beginGuardianAttack(Entity target, CallbackInfo ci) {
RoyalGuardianSwordAbilities.beginMeleeAttack((Player) (Object) this, target);
}
@Inject(method = "attack", at = @At("RETURN"))
private void antarchy$endGuardianAttack(Entity target, CallbackInfo ci) {
RoyalGuardianSwordAbilities.endMeleeAttack((Player) (Object) this);
}
}
@@ -0,0 +1,27 @@
package com.craisinlord.antarchy.mixins;
import com.craisinlord.antarchy.content.item.RoyalGuardianSwordAbilities;
import com.craisinlord.antarchy.content.item.RoyalGuardianSwordItem;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
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.CallbackInfoReturnable;
@Mixin(LivingEntity.class)
public abstract class RoyalGuardianSwordCombatMixin {
@Inject(method = "hurt", at = @At("RETURN"))
private void antarchy$releaseGuardianElement(DamageSource source, float amount,
CallbackInfoReturnable<Boolean> cir) {
if (!cir.getReturnValue() || amount <= 0.0F || RoyalGuardianSwordAbilities.isApplyingSecondaryDamage()) return;
Entity attacker = source.getEntity();
if (!(attacker instanceof Player player) || source.getDirectEntity() != player
|| !(player.getMainHandItem().getItem() instanceof RoyalGuardianSwordItem)
|| !RoyalGuardianSwordAbilities.isChargedMeleeAttack(player, (LivingEntity) (Object) this)
|| !RoyalGuardianSwordItem.isDischargeReady(player.getMainHandItem(), player.level().getGameTime())) return;
RoyalGuardianSwordAbilities.discharge(player, (LivingEntity) (Object) this, player.getMainHandItem());
}
}
@@ -0,0 +1,59 @@
package com.craisinlord.antarchy.mixins;
import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.effect.JudgmentMarkManager;
import com.craisinlord.antarchy.content.effect.CommandedEntityAccess;
import com.craisinlord.antarchy.content.item.RoyalGuardianArmorItem;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.projectile.Projectile;
import net.minecraft.world.entity.player.Player;
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.ModifyVariable;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(LivingEntity.class)
public abstract class RoyalJudgmentCombatMixin {
@ModifyVariable(method = "hurt", at = @At("HEAD"), argsOnly = true, ordinal = 0)
private float antarchy$boostMarkedTargetDamage(float amount, DamageSource source) {
if (com.craisinlord.antarchy.content.item.RoyalGuardianSwordAbilities.isApplyingSecondaryDamage()) return amount;
LivingEntity target = (LivingEntity) (Object) this;
if (target instanceof CommandedEntityAccess access && access.antarchy$isRoyalInvested()) {
double reduction = Math.max(0.0D, Math.min(1.0D, AntarchySettings.royalGuardianMusterDamageReduction()));
amount *= (float) (1.0D - reduction);
}
Player attacker = antarchy$resolvePlayer(source);
if (attacker != null && amount > 0.0F && JudgmentMarkManager.isMarkedBy(attacker, target)) {
return amount * (float) RoyalGuardianArmorItem.judgmentDamageMultiplier(attacker);
}
return amount;
}
@Inject(method = "hurt", at = @At("RETURN"))
private void antarchy$markAfterHit(DamageSource source, float amount,
CallbackInfoReturnable<Boolean> cir) {
if (!cir.getReturnValue() || amount <= 0.0F
|| com.craisinlord.antarchy.content.item.RoyalGuardianSwordAbilities.isApplyingSecondaryDamage()) {
return;
}
LivingEntity target = (LivingEntity) (Object) this;
Player attacker = antarchy$resolvePlayer(source);
if (attacker != null) {
JudgmentMarkManager.recordSuccessfulHit(attacker, target);
}
}
private static Player antarchy$resolvePlayer(DamageSource source) {
Entity attacker = source.getEntity();
if (attacker instanceof Player player) {
return player;
}
if (source.getDirectEntity() instanceof Projectile projectile && projectile.getOwner() instanceof Player player) {
return player;
}
return null;
}
}
@@ -0,0 +1,16 @@
package com.craisinlord.antarchy.mixins;
import com.craisinlord.antarchy.content.entity.royal.RoyalLootDropGlow;
import net.minecraft.world.entity.item.ItemEntity;
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(ItemEntity.class)
public abstract class RoyalLootItemGlowMixin {
@Inject(method = "tick", at = @At("HEAD"))
private void antarchy$tickRoyalLootGlow(CallbackInfo ci) {
RoyalLootDropGlow.tick((ItemEntity) (Object) this);
}
}
@@ -0,0 +1,16 @@
package com.craisinlord.antarchy.mixins;
import com.craisinlord.antarchy.content.effect.RoyalBoundaryManager;
import net.minecraft.server.level.ServerPlayer;
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(ServerPlayer.class)
public abstract class ServerPlayerRoyalBoundaryMixin {
@Inject(method = "tick", at = @At("TAIL"))
private void antarchy$tickRoyalBoundary(CallbackInfo ci) {
RoyalBoundaryManager.tickOwner((ServerPlayer) (Object) this);
}
}
@@ -64,4 +64,17 @@ public abstract class HoverboardRiderPoseMixin<T extends LivingEntity, M extends
humanoidModel.body.zRot += (float) Math.toRadians(HOVERBOARD_RIDER_TORSO_ROLL);
}
}
@Inject(
method = "render(Lnet/minecraft/world/entity/LivingEntity;FFLcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;I)V",
at = @At("RETURN")
)
private void antarchy$restoreHoverboardRiderPose(T entity, float entityYaw, float partialTick, PoseStack poseStack, MultiBufferSource bufferSource, int packedLight, CallbackInfo ci) {
if (entity.getVehicle() instanceof HoverboardEntity && this.getModel() instanceof HumanoidModel<?> humanoidModel) {
float correction = (float) Math.toRadians(HOVERBOARD_RIDER_RENDER_YAW);
humanoidModel.head.yRot -= correction;
humanoidModel.hat.yRot -= correction;
humanoidModel.body.zRot -= (float) Math.toRadians(HOVERBOARD_RIDER_TORSO_ROLL);
}
}
}
@@ -0,0 +1,52 @@
package com.craisinlord.antarchy.mixins.compat.alexscaves;
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.content.worldgen.thoraxis.ThoraxisUndersideManager;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.projectile.Projectile;
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.Inject;
import org.spongepowered.asm.mixin.injection.ModifyArg;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
/** Handles Alex's Caves' manually-launched Candy Cane Hook. */
@Mixin(targets = "com.github.alexmodguy.alexscaves.server.entity.item.CandyCaneHookEntity")
public abstract class AlexsCavesCandyCaneHookMixin {
private boolean antarchy$launchAdjusted;
@Inject(method = "tick", at = @At("HEAD"))
private void antarchy$rotateManualLaunch(CallbackInfo ci) {
Projectile self = (Projectile) (Object) this;
if (this.antarchy$launchAdjusted) {
return;
}
this.antarchy$launchAdjusted = true;
Entity owner = self.getOwner();
boolean ownerInverted = owner != null && AntarchyGravityApi.isGravityInverted(owner);
if (ownerInverted || ThoraxisUndersideManager.isInUnderside(self)) {
AntarchyGravityDirection direction = ownerInverted
? AntarchyGravityApi.getGravityDirection(owner)
: AntarchyGravityDirection.UP;
Vec3 launch = AntarchyGravityRotationUtil.vecPlayerToWorld(
self.getDeltaMovement(), direction);
self.setDeltaMovement(launch);
}
}
@ModifyArg(
method = "fling",
at = @At(value = "INVOKE", target = "Lnet/minecraft/world/phys/Vec3;add(DDD)Lnet/minecraft/world/phys/Vec3;"),
index = 1
)
private double antarchy$invertFlingBoost(double y) {
Projectile self = (Projectile) (Object) this;
Entity owner = self.getOwner();
return y != 0.0D && (ThoraxisUndersideManager.isInUnderside(self)
|| (owner != null && AntarchyGravityApi.isGravityInverted(owner))) ? -y : y;
}
}
@@ -0,0 +1,25 @@
package com.craisinlord.antarchy.mixins.compat.alexscaves;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi;
import com.craisinlord.antarchy.content.worldgen.thoraxis.ThoraxisUndersideManager;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.projectile.Projectile;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyArg;
/** Corrects Depth Charge's custom tick, which bypasses ThrowableProjectile.tick. */
@Mixin(targets = "com.github.alexmodguy.alexscaves.server.entity.item.DepthChargeEntity")
public abstract class AlexsCavesDepthChargeMixin {
@ModifyArg(
method = "tick",
at = @At(value = "INVOKE", target = "Lnet/minecraft/world/phys/Vec3;add(DDD)Lnet/minecraft/world/phys/Vec3;"),
index = 1
)
private double antarchy$invertManualGravity(double y) {
Projectile self = (Projectile) (Object) this;
Entity owner = self.getOwner();
return !self.isNoGravity() && (ThoraxisUndersideManager.isInUnderside(self)
|| (owner != null && AntarchyGravityApi.isGravityInverted(owner))) ? -y : y;
}
}
@@ -0,0 +1,25 @@
package com.craisinlord.antarchy.mixins.compat.alexscaves;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi;
import com.craisinlord.antarchy.content.worldgen.thoraxis.ThoraxisUndersideManager;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.projectile.Projectile;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyArg;
/** Corrects Tephra's explicit -0.1 Y gravity adjustment. */
@Mixin(targets = "com.github.alexmodguy.alexscaves.server.entity.item.TephraEntity")
public abstract class AlexsCavesTephraMixin {
@ModifyArg(
method = "tick",
at = @At(value = "INVOKE", target = "Lnet/minecraft/world/phys/Vec3;add(DDD)Lnet/minecraft/world/phys/Vec3;", ordinal = 1),
index = 1
)
private double antarchy$invertManualGravity(double y) {
Projectile self = (Projectile) (Object) this;
Entity owner = self.getOwner();
return !self.isNoGravity() && (ThoraxisUndersideManager.isInUnderside(self)
|| (owner != null && AntarchyGravityApi.isGravityInverted(owner))) ? -y : y;
}
}
@@ -0,0 +1,25 @@
package com.craisinlord.antarchy.mixins.compat.alexscaves;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi;
import com.craisinlord.antarchy.content.worldgen.thoraxis.ThoraxisUndersideManager;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.projectile.Projectile;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyArg;
/** Corrects Water Bolt's explicit -0.07 Y gravity adjustment. */
@Mixin(targets = "com.github.alexmodguy.alexscaves.server.entity.item.WaterBoltEntity")
public abstract class AlexsCavesWaterBoltMixin {
@ModifyArg(
method = "tick",
at = @At(value = "INVOKE", target = "Lnet/minecraft/world/phys/Vec3;add(DDD)Lnet/minecraft/world/phys/Vec3;", ordinal = 1),
index = 1
)
private double antarchy$invertManualGravity(double y) {
Projectile self = (Projectile) (Object) this;
Entity owner = self.getOwner();
return !self.isNoGravity() && (ThoraxisUndersideManager.isInUnderside(self)
|| (owner != null && AntarchyGravityApi.isGravityInverted(owner))) ? -y : y;
}
}
@@ -0,0 +1,43 @@
package com.craisinlord.antarchy.mixins.compat.ars_nouveau;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityDirection;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityTransition;
import com.craisinlord.antarchy.content.worldgen.thoraxis.ThoraxisUndersideManager;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.projectile.Projectile;
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;
/** Handles Ars Nouveau's explicit tickNextPosition gravity adjustment. */
@Mixin(targets = "com.hollingsworth.arsnouveau.common.entity.EntityProjectileSpell")
public abstract class ArsNouveauProjectileGravityMixin {
@Inject(method = "tick", at = @At("HEAD"))
private void antarchy$prepareArsProjectile(CallbackInfo ci) {
Projectile self = (Projectile) (Object) this;
Entity owner = self.getOwner();
if (owner != null && (ThoraxisUndersideManager.isInUnderside(self)
|| AntarchyGravityApi.isGravityInverted(owner))) {
AntarchyGravityApi.setAirborneGravityDirection(
self,
AntarchyGravityDirection.UP,
false,
AntarchyGravityTransition.INSTANT
);
}
}
@Inject(method = "tickNextPosition", at = @At("TAIL"))
private void antarchy$invertArsProjectileGravity(CallbackInfo ci) {
Projectile self = (Projectile) (Object) this;
if (!AntarchyGravityApi.isGravityInverted(self) || self.isNoGravity()) {
return;
}
// Ars applies -0.03 directly in tickNextPosition(). Correct that one
// application to +0.03 without changing damping or spell effects.
self.setDeltaMovement(self.getDeltaMovement().add(0.0D, 0.06D, 0.0D));
}
}
@@ -0,0 +1,65 @@
package com.craisinlord.antarchy.mixins.compat.irons_spellbooks;
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.content.gravity.AntarchyGravityTransition;
import com.craisinlord.antarchy.content.worldgen.thoraxis.ThoraxisUndersideManager;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.projectile.Projectile;
import net.minecraft.world.phys.Vec3;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
/** Handles Iron's Spellbooks' custom travel() gravity path. */
@Mixin(targets = "io.redspace.ironsspellbooks.entity.spells.AbstractMagicProjectile")
public abstract class IronsSpellbooksProjectileGravityMixin {
@Shadow public abstract float getSpeed();
@Shadow protected abstract double getDefaultGravity();
@Inject(method = "tick", at = @At("HEAD"))
private void antarchy$prepareMagicProjectile(CallbackInfo ci) {
Projectile self = (Projectile) (Object) this;
Entity owner = self.getOwner();
boolean inverted = ThoraxisUndersideManager.isInUnderside(self)
|| (owner != null && AntarchyGravityApi.isGravityInverted(owner));
if (!inverted) {
return;
}
AntarchyGravityApi.setAirborneGravityDirection(
self,
AntarchyGravityDirection.UP,
false,
AntarchyGravityTransition.INSTANT
);
}
@Inject(method = "shoot", at = @At("HEAD"), cancellable = true)
private void antarchy$rotateMagicProjectileLaunch(Vec3 rotation, CallbackInfo ci) {
Projectile self = (Projectile) (Object) this;
Entity owner = self.getOwner();
if (owner == null || (!ThoraxisUndersideManager.isInUnderside(self)
&& !AntarchyGravityApi.isGravityInverted(owner))) {
return;
}
self.setDeltaMovement(AntarchyGravityRotationUtil.vecPlayerToWorld(
rotation,
AntarchyGravityApi.getGravityDirection(owner)
).scale(this.getSpeed()));
ci.cancel();
}
@Inject(method = "travel", at = @At("TAIL"))
private void antarchy$invertMagicProjectileGravity(CallbackInfo ci) {
Projectile self = (Projectile) (Object) this;
if (!AntarchyGravityApi.isGravityInverted(self) || self.isNoGravity()) {
return;
}
// AbstractMagicProjectile.travel() already subtracted default gravity.
self.setDeltaMovement(self.getDeltaMovement().add(0.0D, 2.0D * this.getDefaultGravity(), 0.0D));
}
}
@@ -0,0 +1,26 @@
package com.craisinlord.antarchy.mixins.compat.scguns;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi;
import com.craisinlord.antarchy.content.worldgen.thoraxis.ThoraxisUndersideManager;
import net.minecraft.world.entity.projectile.AbstractArrow;
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;
/** Corrects Scorched Guns arrow-derived projectiles with custom tick gravity. */
@Mixin(targets = "top.ribs.scguns.entity.projectile.BrassBoltEntity")
public abstract class ScgunsArrowGravityMixin {
@Inject(method = "tick", at = @At("TAIL"))
private void antarchy$invertScgunsArrowGravity(CallbackInfo ci) {
AbstractArrow self = (AbstractArrow) (Object) this;
if (self.isNoGravity() || (!AntarchyGravityApi.isGravityInverted(self)
&& !ThoraxisUndersideManager.isInUnderside(self))) {
return;
}
// Brass Bolt adds +0.01 after AbstractArrow.tick(); invert that custom
// correction to -0.01, without touching normal arrows.
self.setDeltaMovement(self.getDeltaMovement().add(0.0D, -0.02D, 0.0D));
}
}
@@ -0,0 +1,82 @@
package com.craisinlord.antarchy.mixins.compat.scguns;
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.content.gravity.AntarchyGravityTransition;
import com.craisinlord.antarchy.content.worldgen.thoraxis.ThoraxisUndersideManager;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
/** Bridges Scorched Guns' Entity-based projectile physics into Antarchy gravity. */
@Mixin(targets = {
"top.ribs.scguns.entity.projectile.ProjectileEntity",
"top.ribs.scguns.entity.projectile.BearPackShellProjectileEntity",
"top.ribs.scguns.entity.projectile.KrahgRoundProjectileEntity",
"top.ribs.scguns.entity.projectile.LightningProjectileEntity",
"top.ribs.scguns.entity.projectile.OsborneSlugProjectileEntity",
"top.ribs.scguns.entity.projectile.ShotballProjectileEntity"
})
public abstract class ScgunsProjectileEntityGravityMixin {
@Shadow @Nullable protected LivingEntity shooter;
@Shadow protected double modifiedGravity;
@Shadow public abstract Vec3 getDeltaMovement();
@Shadow public abstract void setDeltaMovement(Vec3 movement);
@Unique private boolean antarchy$launchDirectionAdjusted;
@Inject(method = "tick", at = @At("HEAD"))
private void antarchy$prepareScgunsProjectile(CallbackInfo ci) {
Entity self = (Entity) (Object) this;
boolean inverted = ThoraxisUndersideManager.isInUnderside(self)
|| (this.shooter != null && AntarchyGravityApi.isGravityInverted(this.shooter));
if (!inverted) {
if (AntarchyGravityApi.isGravityInverted(self) && !AntarchyGravityApi.isGravityForced(self)) {
AntarchyGravityApi.setAirborneGravityDirection(
self,
AntarchyGravityDirection.DOWN,
false,
AntarchyGravityTransition.INSTANT
);
}
return;
}
AntarchyGravityApi.setAirborneGravityDirection(
self,
AntarchyGravityDirection.UP,
false,
AntarchyGravityTransition.INSTANT
);
if (!this.antarchy$launchDirectionAdjusted && self.tickCount <= 1) {
Vec3 motion = this.getDeltaMovement();
if (motion.lengthSqr() > 1.0E-8D) {
this.setDeltaMovement(AntarchyGravityRotationUtil.vecPlayerToWorld(
motion,
AntarchyGravityDirection.UP
));
}
this.antarchy$launchDirectionAdjusted = true;
}
}
@Inject(method = "tick", at = @At("TAIL"))
private void antarchy$invertScgunsGravity(CallbackInfo ci) {
Entity self = (Entity) (Object) this;
if (!AntarchyGravityApi.isGravityInverted(self) || this.modifiedGravity == 0.0D) {
return;
}
// Scorched Guns already adds modifiedGravity once near tick end. Replace
// that downward world-Y acceleration with its upward counterpart.
this.setDeltaMovement(this.getDeltaMovement().add(0.0D, -2.0D * this.modifiedGravity, 0.0D));
}
}
@@ -1,7 +1,7 @@
package com.craisinlord.antarchy.mixins.gravity;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityRotationUtil;
import com.craisinlord.antarchy.content.gravity.ProjectileLaunchGravity;
import com.craisinlord.antarchy.content.worldgen.thoraxis.ThoraxisUndersideManager;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
@@ -58,7 +58,7 @@ public abstract class ProjectileGravityMixin {
) {
Vec3 direction = new Vec3(x, y, z);
if (AntarchyGravityApi.isGravityInverted(shooter)) {
direction = AntarchyGravityRotationUtil.vecPlayerToWorld(
direction = ProjectileLaunchGravity.toWorld(
direction,
AntarchyGravityApi.getGravityDirection(shooter)
);
@@ -31,8 +31,13 @@
"LivingEntityJumpingAccessor",
"CommandedMobMixin",
"LivingEntityRoyalEffectsMixin",
"JudgmentMarkEntityMixin",
"RoyalJudgmentCombatMixin",
"RoyalGuardianPlayerAttackMixin",
"RoyalGuardianSwordCombatMixin",
"MobCommandedTargetMixin",
"RoyalNoRespiteMixin",
"RoyalLootItemGlowMixin",
"MantisClawWallClimbMixin",
"MilkBucketSizeResetMixin",
"MinecartItemMixin",
@@ -49,6 +54,9 @@
"VillagerTradesMixin",
"WaterlilyLumenMixin",
"ServerPlayerGameModeMixin",
"ServerPlayerRoyalBoundaryMixin",
"ProjectileRoyalBoundaryMixin",
"RoyalBoundaryOwnerCleanupMixin",
"PlayerAdvancementsMixin",
"SpongeAntiwaterMixin",
"SquidzookaEnchantMixin",
@@ -87,6 +95,14 @@
"gravity.ServerPlayerGravityMixin",
"gravity.ThrowableProjectileGravityMixin",
"gravity.TrackedEntityAccessor",
"compat.scguns.ScgunsProjectileEntityGravityMixin",
"compat.scguns.ScgunsArrowGravityMixin",
"compat.irons_spellbooks.IronsSpellbooksProjectileGravityMixin",
"compat.ars_nouveau.ArsNouveauProjectileGravityMixin",
"compat.alexscaves.AlexsCavesCandyCaneHookMixin",
"compat.alexscaves.AlexsCavesWaterBoltMixin",
"compat.alexscaves.AlexsCavesTephraMixin",
"compat.alexscaves.AlexsCavesDepthChargeMixin",
"multipart.EntityMultipartLifecycleMixin",
"time.EntityDeltaMovementTimeDilationMixin",
"time.EntityMoveTimeDilationMixin",
@@ -543,6 +543,15 @@
"item.antarchy.royal_guardian_boots": "Royal Guardian Boots",
"item.antarchy.royal_guardian_sword": "Royal Guardian Sword",
"item.antarchy.royal_guardian_shield": "Royal Guardian Shield",
"tooltip.antarchy.royal_guardian_sword.mode": "Attunement: %s",
"tooltip.antarchy.royal_guardian_sword.mode.fire": "Fire",
"tooltip.antarchy.royal_guardian_sword.mode.frost": "Frost",
"tooltip.antarchy.royal_guardian_sword.mode.storm": "Storm",
"tooltip.antarchy.royal_guardian_sword.fire": "Fully charged hits erupt in royal flame, burning the target and nearby enemies.",
"tooltip.antarchy.royal_guardian_sword.frost": "Fully charged hits freeze the target and raise a fan of royal ice spikes.",
"tooltip.antarchy.royal_guardian_sword.storm": "Fully charged hits call royal lightning that chains between nearby enemies.",
"tooltip.antarchy.royal_guardian_sword.controls": "Sneak-use to cycle modes. Fully charged hits unleash the selected element (%s-second cooldown).",
"message.antarchy.royal_guardian_sword.mode": "Royal Guardian attuned to %s",
"item.antarchy.royal_assailant_helmet": "Royal Assailant Helmet",
"item.antarchy.royal_assailant_chestplate": "Royal Assailant Chestplate",
"item.antarchy.royal_assailant_leggings": "Royal Assailant Leggings",
@@ -552,7 +561,12 @@
"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 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_guardian_shield.command": "Sneak-use to muster nearby mobs. Royal Investiture empowers their offense, speed, and resilience.",
"tooltip.antarchy.royal_guardian_helmet.judgment": "Marked for Judgment: +%s%% damage against marked targets",
"tooltip.antarchy.royal_guardian_chestplate.judgment": "Marked for Judgment: +%s%% damage against marked targets",
"tooltip.antarchy.royal_guardian_chestplate.boundary": "Royal Boundary (B by default): Freezes incoming hostile projectiles and grants Strength to allies.",
"tooltip.antarchy.royal_guardian_leggings.judgment": "Marked for Judgment: +%s%% damage against marked targets",
"tooltip.antarchy.royal_guardian_boots.judgment": "Marked for Judgment: +%s%% damage against marked targets",
"tooltip.antarchy.royal_assailant_chestplate.invert": "Press the inversion key to toggle inverted gravity on and off.",
"tooltip.antarchy.royal_assailant_boots.fall": "Cancels fall damage while falling upward.",
"item.antarchy.mantis_claw": "Mantis Claw",
@@ -562,6 +576,9 @@
"item.antarchy.jerry_nucleus": "Jerry Nucleus",
"tooltip.antarchy.jerry_nucleus.hatch": "While holding, interact with any bug egg or turtle egg to hatch it instantly.",
"key.antarchy.mount_special": "Mount: Special / Charge",
"key.antarchy.royal_boundary": "Royal Boundary",
"message.antarchy.royal_boundary.activated": "Royal Boundary raised",
"message.antarchy.royal_boundary.recharging": "Royal Boundary is recharging",
"key.antarchy.mount_flight_toggle": "Mount: Toggle Flight",
"item.antarchy.springbug_leg": "Springbug Leg",
"item.antarchy.springy_boots": "Springy Boots",
@@ -733,6 +750,7 @@
"message.antarchy.rainbow_ant_failed_dimension_create": "Failed to create dimension. This likely means something is wrong in your infinity config. Restart/Relog and try again.",
"message.antarchy.rainbow_ant_infinity_missing": "Infinity must be installed for rainbow ant dimensions to work.",
"message.antarchy.rainbow_ant_needs_reagent": "This rainbow ant needs a reagent before it can open a dimension.",
"message.antarchy.teleport_arrival_failed": "No safe arrival spot was found. Teleportation was canceled.",
"message.antarchy.red_ant_activated": "The red ant is ready to open its destination.",
"message.antarchy.red_ant_needs_reagent": "This red ant needs a reagent before it can open its destination.",
"message.antarchy.termite_activated": "The termite is ready to open its destination.",
@@ -1425,7 +1443,8 @@
,"decree.antarchy.close_quarters": "CLOSE QUARTERS"
,"decree.antarchy.close_quarters.subtitle": "Enter the royal circle. Projectiles are forbidden."
,"decree.antarchy.violated": "DECREE VIOLATED"
,"decree.antarchy.violated.subtitle": "Face the King's judgment."
,"decree.antarchy.violated.subtitle": "Your hearts are halved for 30. Face the King's judgment."
,"effect.antarchy.royal_vitality_penalty": "Royal Vitality Diminished"
,"decree.antarchy.come_no_closer": "COME NO CLOSER"
,"decree.antarchy.come_no_closer.subtitle": "Keep out of the King's inner radius."
,"decree.antarchy.fight_me_coward": "FIGHT ME, COWARD!"
@@ -1475,9 +1494,9 @@
,"guide.antarchy.entry.chronosphere.title": "Chronosphere"
,"guide.antarchy.entry.chronosphere.subtitle": "Temporal equipment record"
,"guide.antarchy.entry.chronosphere.description": "A record for the Chronosphere enchantment will be expanded here. Further data is pending."
,"guide.antarchy.entry.toreterror_arena.title": "Toreterror Arena"
,"guide.antarchy.entry.toreterror_arena.subtitle": "Structure location fragment"
,"guide.antarchy.entry.toreterror_arena.description": "The nearest arena is rumored to lie beyond the current horizon. Directional analysis is pending in this prototype."
,"guide.antarchy.entry.toreterror.title": "Toreterror"
,"guide.antarchy.entry.toreterror_arena.subtitle": "Elythia Ocean boss"
,"guide.antarchy.entry.toreterror_arena.description": "The nearest Toreterror Arena within search range is listed below. It is always placed in an Elythia Ocean."
,"guide.antarchy.entry.elythia.title": "Elythia"
,"guide.antarchy.entry.elythia.subtitle": "Dimension and portal access record"
,"guide.antarchy.entry.elythia.description": "Elythia is reached through an Elythia portal made with a valid frame. A sacrificed Brown Ant activates the portal. The dimension contains oceans, meadows, forests, coral spikes, and other surface biomes."
@@ -1505,6 +1524,27 @@
,"guide.antarchy.entry.high_fructose_corn_syrup.title": "High Fructose Corn Syrup"
,"guide.antarchy.entry.high_fructose_corn_syrup.subtitle": "Nutritional and operational notes"
,"guide.antarchy.entry.high_fructose_corn_syrup.description": "A sweet substance of uncertain strategic value. Doctoral enthusiasm surrounding it is statistically suspicious."
,"guide.antarchy.entry.rainbow_ant.title": "Rainbow Ant"
,"guide.antarchy.entry.rainbow_ant.subtitle": "Entity record"
,"guide.antarchy.entry.rainbow_ant.description": "When given an activation reagent, a Rainbow Ant can open a portal to its assigned dimension."
,"guide.antarchy.entry.easter_bunny.title": "Easter Bunny"
,"guide.antarchy.entry.easter_bunny.subtitle": "Entity record"
,"guide.antarchy.entry.easter_bunny.description": "Easter Bunnies periodically lay random spawn eggs."
,"guide.antarchy.entry.duplicator_tree.title": "Duplicator Tree"
,"guide.antarchy.entry.duplicator_tree.subtitle": "Block record"
,"guide.antarchy.entry.duplicator_tree.description": "Duplicator Trees copy nearby eligible blocks into open spaces around them."
,"guide.antarchy.disk.kraken.title": "KRAKEN SIGHTING NOTES"
,"guide.antarchy.entry.kraken.title": "Kraken"
,"guide.antarchy.entry.kraken.subtitle": "Overworld aquatic encounter"
,"guide.antarchy.entry.kraken.description": "Search open Overworld waters; the Kraken can only spawn in water. Some encounters require Bad Omen."
,"guide.antarchy.disk.king.title": "KING'S TREE FIELD NOTES"
,"guide.antarchy.entry.king.title": "King"
,"guide.antarchy.entry.king.subtitle": "Elythia royal entity record"
,"guide.antarchy.entry.king.description": "Seek the massive King's Tree in Elythia. The King spawns near its center and patrols the surrounding area."
,"guide.antarchy.disk.queen.title": "QUEEN HAVOC TRAIL NOTES"
,"guide.antarchy.entry.queen.title": "Queen"
,"guide.antarchy.entry.queen.subtitle": "Thoraxis royal entity record"
,"guide.antarchy.entry.queen.description": "Follow a Queen Havoc Trail through Thoraxis's underside to its chamber; the Queen appears when you approach."
,"screen.antarchy.computer.first_use.title": "INITIAL PASSWORD SETUP"
,"screen.antarchy.computer.first_use.prompt": "CREATE A PASSWORD TO CONTINUE"
,"screen.antarchy.computer.first_use.confirm": "CONFIRM PASSWORD"
@@ -3,8 +3,8 @@
"credit": "Made with Blockbench",
"texture_size": [128, 128],
"textures": {
"0": "antarchy/item/computer",
"particle": "antarchy/item/computer"
"0": "antarchy:item/computer",
"particle": "antarchy:item/computer"
},
"elements": [
{
@@ -0,0 +1,7 @@
The pencil, eraser, trash, undo, and redo icons are adapted from Pixelarticons
by Gerrit Halfmann (https://github.com/halfmage/pixelarticons), version 2.4.1.
The source SVG files are included in this directory. The PNGs were rasterized
at 24x24, recolored to Antarchy green, and given a one-pixel black outline.
These icons are licensed under the MIT License. See LICENSE-pixelarticons.txt.
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Gerrit Halfmann
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Binary file not shown.

After

Width:  |  Height:  |  Size: 233 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 24 24"><path d="M15 18h6v2H7v-2h6v-2h2v2Zm-8 0H5v-2h2v2Zm-2-2H3v-2h2v2Zm12 0h-2v-2h2v2ZM7 14H5v-2h2v2Zm8 0h-2v-2h2v2Zm4 0h-2v-2h2v2ZM9 12H7v-2h2v2Zm4 0h-2v-2h2v2Zm8 0h-2v-2h2v2Zm-10-2H9V8h2v2Zm8 0h-2V8h2v2Zm-6-2h-2V6h2v2Zm4 0h-2V6h2v2Zm-2-2h-2V4h2v2Z"/></svg>

After

Width:  |  Height:  |  Size: 332 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 B

Loaded 100 of 158 files, more files were not shown because too many files have changed in this diff. Show more