new icons
royal guardian first pass
This commit is contained in:
1 parent
82cea4c293
commit
c77d1da53b
158 files changed
+5313
-796
No files matched your search
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
+5
-1
@@ -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;
|
||||
}
|
||||
|
||||
+19
@@ -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);
|
||||
}
|
||||
}
|
||||
+8
-1
@@ -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);
|
||||
|
||||
+23
-6
@@ -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)));
|
||||
|
||||
+20
@@ -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();
|
||||
|
||||
+11
@@ -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 {
|
||||
|
||||
+7
@@ -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);
|
||||
}
|
||||
|
||||
+10
@@ -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];
|
||||
|
||||
+43
@@ -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");
|
||||
}
|
||||
}
|
||||
+36
@@ -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;
|
||||
}
|
||||
}
|
||||
+17
@@ -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);
|
||||
}
|
||||
}
|
||||
+32
@@ -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);
|
||||
}
|
||||
}
|
||||
+24
@@ -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);
|
||||
}
|
||||
}
|
||||
+79
@@ -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);
|
||||
}
|
||||
}
|
||||
+914
-384
File diff suppressed because it is too large.
Load diff
+6
-1
@@ -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)
|
||||
|
||||
+72
-50
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+36
-1
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
@@ -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);
|
||||
}
|
||||
+8
@@ -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);
|
||||
}
|
||||
+140
@@ -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<>();
|
||||
}
|
||||
}
|
||||
+269
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+34
@@ -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);
|
||||
}
|
||||
}
|
||||
+61
-99
@@ -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;
|
||||
}
|
||||
}
|
||||
+1
@@ -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);
|
||||
}
|
||||
|
||||
+4
@@ -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;
|
||||
|
||||
+43
-6
@@ -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");
|
||||
|
||||
+5
@@ -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);
|
||||
}
|
||||
|
||||
+492
-56
@@ -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) {
|
||||
|
||||
+569
-82
@@ -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"));
|
||||
|
||||
+5
@@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
+33
-2
@@ -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;
|
||||
}
|
||||
|
||||
+5
@@ -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) {
|
||||
|
||||
+62
-6
@@ -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
|
||||
|
||||
+37
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
@@ -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);
|
||||
}
|
||||
|
||||
+2
-1
@@ -8,5 +8,6 @@ public enum RoyalBeamElement {
|
||||
QUEEN_PURPLE,
|
||||
QUEEN_RED,
|
||||
QUEEN_BLACK,
|
||||
DREAM_FIRE
|
||||
DREAM_FIRE,
|
||||
KING_GOLD
|
||||
}
|
||||
+4
@@ -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);
|
||||
}
|
||||
|
||||
+22
-1
@@ -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();
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ public interface RoyalDecree {
|
||||
return Evaluation.COMPLIANT;
|
||||
}
|
||||
|
||||
default int countdownTicks(LivingEntity target) {
|
||||
default int countdownTicks(KingEntity king, LivingEntity target) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
+1
-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) {
|
||||
|
||||
+13
@@ -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);
|
||||
}
|
||||
}
|
||||
+107
-6
@@ -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) {
|
||||
|
||||
+15
@@ -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);
|
||||
|
||||
+18
@@ -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));
|
||||
|
||||
+4
@@ -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;
|
||||
}
|
||||
|
||||
+39
@@ -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();
|
||||
|
||||
+59
-11
@@ -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
|
||||
}
|
||||
}
|
||||
+211
@@ -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) {
|
||||
}
|
||||
}
|
||||
+86
@@ -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) {
|
||||
}
|
||||
|
||||
+18
@@ -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;
|
||||
}
|
||||
}
|
||||
+24
-10
@@ -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);
|
||||
}
|
||||
}
|
||||
+22
-1
@@ -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));
|
||||
}
|
||||
}
|
||||
+61
-5
@@ -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;
|
||||
|
||||
+2
@@ -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,
|
||||
|
||||
+4
@@ -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()));
|
||||
}
|
||||
}
|
||||
+20
@@ -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;
|
||||
}
|
||||
}
|
||||
+22
@@ -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);
|
||||
}
|
||||
}
|
||||
+59
-10
@@ -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())) {
|
||||
|
||||
+4
-4
@@ -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));
|
||||
|
||||
+8
-5
@@ -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
-2
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
+42
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
@@ -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);
|
||||
}
|
||||
}
|
||||
+17
@@ -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);
|
||||
}
|
||||
}
|
||||
+23
@@ -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);
|
||||
}
|
||||
}
|
||||
+27
@@ -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());
|
||||
}
|
||||
}
|
||||
+59
@@ -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;
|
||||
}
|
||||
}
|
||||
+16
@@ -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);
|
||||
}
|
||||
}
|
||||
+16
@@ -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);
|
||||
}
|
||||
}
|
||||
+13
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+52
@@ -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;
|
||||
}
|
||||
}
|
||||
+25
@@ -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;
|
||||
}
|
||||
}
|
||||
+25
@@ -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;
|
||||
}
|
||||
}
|
||||
+25
@@ -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;
|
||||
}
|
||||
}
|
||||
+43
@@ -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));
|
||||
}
|
||||
}
|
||||
+65
@@ -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));
|
||||
}
|
||||
}
|
||||
+26
@@ -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));
|
||||
}
|
||||
}
|
||||
+82
@@ -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));
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -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.
|
||||
+21
@@ -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
Reference in new issue
Block a user