big king and queen dev

This commit is contained in:
craisinlord committed 2026-09-07 21:56:21 -05:00
1 parent d34830a99b
commit 68eba61022
169 files changed
+4779 -942

No files matched your search

@@ -1368,6 +1368,8 @@ public final class AntarchySettings {
public static void setKrakensGraspAttackSpeed(double value) { krakensGraspAttackSpeed = value; } public static void setKrakensGraspAttackSpeed(double value) { krakensGraspAttackSpeed = value; }
public static void setKrakensGraspThrownDamage(double value) { krakensGraspThrownDamage = value; } public static void setKrakensGraspThrownDamage(double value) { krakensGraspThrownDamage = value; }
public static void setKrakensGraspLightningDamage(double value) { krakensGraspLightningDamage = value; } public static void setKrakensGraspLightningDamage(double value) { krakensGraspLightningDamage = value; }
public static double krakensGraspLightningDamage() { return krakensGraspLightningDamage; }
public static void setKrakensGraspInnateLoyalty(boolean value) { krakensGraspInnateLoyalty = value; } public static void setKrakensGraspInnateLoyalty(boolean value) { krakensGraspInnateLoyalty = value; }
public static void setKrakensGraspInnateLoyaltyLevel(int value) { krakensGraspInnateLoyaltyLevel = value; } public static void setKrakensGraspInnateLoyaltyLevel(int value) { krakensGraspInnateLoyaltyLevel = value; }
public static void setKrakensGraspTentacleDurationTicks(int value) { krakensGraspTentacleDurationTicks = value; } public static void setKrakensGraspTentacleDurationTicks(int value) { krakensGraspTentacleDurationTicks = value; }
@@ -73,6 +73,7 @@ import software.bernie.geckolib.core.animation.RawAnimation;
import software.bernie.geckolib.util.GeckoLibUtil; import software.bernie.geckolib.util.GeckoLibUtil;
public class ReverieEntity extends PathfinderMob implements FlyingAnimal, GeoEntity { public class ReverieEntity extends PathfinderMob implements FlyingAnimal, GeoEntity {
private static final int DAMAGE_REACTION_DURATION_TICKS = 40;
private static final EntityDataAccessor<Integer> MOOD = private static final EntityDataAccessor<Integer> MOOD =
SynchedEntityData.defineId(ReverieEntity.class, EntityDataSerializers.INT); SynchedEntityData.defineId(ReverieEntity.class, EntityDataSerializers.INT);
private static final EntityDataAccessor<Integer> ANIMATION_STATE = private static final EntityDataAccessor<Integer> ANIMATION_STATE =
@@ -309,7 +310,7 @@ public class ReverieEntity extends PathfinderMob implements FlyingAnimal, GeoEnt
this.releaseFocus(); this.releaseFocus();
this.clearNoticePlayer(); this.clearNoticePlayer();
this.ambientTarget = null; this.ambientTarget = null;
this.damageReactionTicks = 0; this.damageReactionTicks = DAMAGE_REACTION_DURATION_TICKS;
this.inversionReactionTicks = 0; this.inversionReactionTicks = 0;
this.transitionToMood(Mood.WHITE); this.transitionToMood(Mood.WHITE);
} }
@@ -15,6 +15,8 @@ import net.minecraft.world.entity.MobSpawnType;
import net.minecraft.world.entity.ai.attributes.AttributeSupplier; import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
import net.minecraft.world.entity.ai.attributes.Attributes; import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.ai.goal.FloatGoal; import net.minecraft.world.entity.ai.goal.FloatGoal;
import net.minecraft.world.entity.ai.goal.BreedGoal;
import net.minecraft.world.entity.ai.goal.TemptGoal;
import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal; import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal;
import net.minecraft.world.entity.ai.goal.PanicGoal; import net.minecraft.world.entity.ai.goal.PanicGoal;
import net.minecraft.world.entity.ai.goal.RandomLookAroundGoal; import net.minecraft.world.entity.ai.goal.RandomLookAroundGoal;
@@ -23,6 +25,8 @@ import net.minecraft.world.entity.animal.Animal;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
import net.minecraft.sounds.SoundEvent; import net.minecraft.sounds.SoundEvent;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.level.ServerLevelAccessor; import net.minecraft.world.level.ServerLevelAccessor;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -76,9 +80,11 @@ public class StinkBugEntity extends Animal implements GeoEntity {
protected void registerGoals() { protected void registerGoals() {
this.goalSelector.addGoal(0, new FloatGoal(this)); this.goalSelector.addGoal(0, new FloatGoal(this));
this.goalSelector.addGoal(1, new PanicGoal(this, 1.4D)); this.goalSelector.addGoal(1, new PanicGoal(this, 1.4D));
this.goalSelector.addGoal(2, new WaterAvoidingRandomStrollGoal(this, 0.9D)); this.goalSelector.addGoal(2, new TemptGoal(this, 1.0D, Ingredient.of(Items.ROTTEN_FLESH), false));
this.goalSelector.addGoal(3, new LookAtPlayerGoal(this, Player.class, 6.0F)); this.goalSelector.addGoal(3, new BreedGoal(this, 0.9D));
this.goalSelector.addGoal(4, new RandomLookAroundGoal(this)); this.goalSelector.addGoal(4, new WaterAvoidingRandomStrollGoal(this, 0.9D));
this.goalSelector.addGoal(5, new LookAtPlayerGoal(this, Player.class, 6.0F));
this.goalSelector.addGoal(6, new RandomLookAroundGoal(this));
} }
@Override @Override
@@ -108,7 +114,7 @@ public class StinkBugEntity extends Animal implements GeoEntity {
@Override @Override
public boolean isFood(ItemStack stack) { public boolean isFood(ItemStack stack) {
return false; return stack.is(Items.ROTTEN_FLESH);
} }
@Override @Override
@@ -1026,7 +1026,8 @@ public class KrakenEntity extends Monster implements GeoEntity, MultipartEntityO
for (int i = 0; i < strikeBolts; i++) { for (int i = 0; i < strikeBolts; i++) {
this.spawnVisualLightning(serverLevel, this.getTargetStormStrikePos(target)); this.spawnVisualLightning(serverLevel, this.getTargetStormStrikePos(target));
} }
target.hurt(AntarchyDamageSources.krakenLightning(serverLevel, this), this.isPhaseTwo() ? 12.0F : 8.0F); target.hurt(AntarchyDamageSources.krakenLightning(serverLevel, this),
this.isPhaseTwo() ? 12.0F : AntarchySettings.krakenLightningDamagePhaseOne());
} }
} else { } else {
this.lightningStrikeCooldown = this.isPhaseTwo() ? 30 : 60; this.lightningStrikeCooldown = this.isPhaseTwo() ? 30 : 60;
@@ -91,12 +91,14 @@ public class KrakensGraspItem extends TridentItem {
public static void strikeLightning(LivingEntity target, ServerLevel level) { public static void strikeLightning(LivingEntity target, ServerLevel level) {
strikeLightningAt(level, target.getX(), target.getY(), target.getZ()); strikeLightningAt(level, target.getX(), target.getY(), target.getZ());
target.hurt(level.damageSources().lightningBolt(), (float) AntarchySettings.krakensGraspLightningDamage());
level.sendParticles(ParticleTypes.ELECTRIC_SPARK, target.getX(), target.getY(0.7D), target.getZ(), 10, 0.25D, 0.25D, 0.25D, 0.02D); level.sendParticles(ParticleTypes.ELECTRIC_SPARK, target.getX(), target.getY(0.7D), target.getZ(), 10, 0.25D, 0.25D, 0.25D, 0.02D);
} }
public static void strikeLightningAt(ServerLevel level, double x, double y, double z) { public static void strikeLightningAt(ServerLevel level, double x, double y, double z) {
LightningBolt lightningBolt = new LightningBolt(net.minecraft.world.entity.EntityType.LIGHTNING_BOLT, level); LightningBolt lightningBolt = new LightningBolt(net.minecraft.world.entity.EntityType.LIGHTNING_BOLT, level);
lightningBolt.moveTo(x, y, z); lightningBolt.moveTo(x, y, z);
lightningBolt.setVisualOnly(true);
level.addFreshEntity(lightningBolt); level.addFreshEntity(lightningBolt);
level.sendParticles(ParticleTypes.ELECTRIC_SPARK, x, y + 0.2D, z, 10, 0.25D, 0.25D, 0.25D, 0.02D); level.sendParticles(ParticleTypes.ELECTRIC_SPARK, x, y + 0.2D, z, 10, 0.25D, 0.25D, 0.25D, 0.02D);
} }
@@ -0,0 +1,7 @@
{
"variants": {
"": {
"model": "minecraft:block/air"
}
}
}
@@ -0,0 +1,7 @@
{
"variants": {
"": {
"model": "minecraft:block/air"
}
}
}
@@ -0,0 +1,7 @@
{
"variants": {
"": {
"model": "minecraft:block/air"
}
}
}
@@ -1,5 +1,5 @@
{ {
"format_version": "1.21.110", "format_version": "1.12.0",
"minecraft:geometry": [ "minecraft:geometry": [
{ {
"description": { "description": {
@@ -1,5 +1,5 @@
{ {
"format_version": "1.21.110", "format_version": "1.12.0",
"minecraft:geometry": [ "minecraft:geometry": [
{ {
"description": { "description": {
@@ -1,5 +1,5 @@
{ {
"format_version": "1.21.110", "format_version": "1.12.0",
"minecraft:geometry": [ "minecraft:geometry": [
{ {
"description": { "description": {
@@ -1,5 +1,5 @@
{ {
"format_version": "1.21.110", "format_version": "1.12.0",
"minecraft:geometry": [ "minecraft:geometry": [
{ {
"description": { "description": {
@@ -1,5 +1,5 @@
{ {
"textures": { "textures": {
"particle": "antarchy:block/nadir/nadir_hanging" "particle": "antarchy:block/nadir/nadir_planks"
} }
} }
@@ -1,5 +1,5 @@
{ {
"textures": { "textures": {
"particle": "antarchy:block/nadir/nadir_hanging" "particle": "antarchy:block/nadir/nadir_planks"
} }
} }
@@ -91,11 +91,11 @@ public final class AntarchySettings {
private static double bigBerthaNoneModeDamageBonusPercent = 15.0D; private static double bigBerthaNoneModeDamageBonusPercent = 15.0D;
private static double bigBerthaNightmareDamageBonusPercent = 30.0D; private static double bigBerthaNightmareDamageBonusPercent = 30.0D;
private static double potentNyxiteInvertedDurationSeconds = 60.0D; private static double potentNyxiteInvertedDurationSeconds = 60.0D;
private static boolean dimensionalTearsSpawnNaturally = false; private static boolean dimensionalTearsSpawnNaturally = true;
private static int dimensionalTearLifetimeTicks = 24000; private static int dimensionalTearLifetimeTicks = 24000;
private static int dimensionalTearInvertedDurationTicks = 2400; private static int dimensionalTearInvertedDurationTicks = 2400;
private static int dimensionalTearEmergenceMinIntervalTicks = 2400; private static int dimensionalTearEmergenceMinIntervalTicks = 400;
private static int dimensionalTearEmergenceMaxIntervalTicks = 7200; private static int dimensionalTearEmergenceMaxIntervalTicks = 1200;
private static float dimensionalTearLucidEventChance = 0.65F; private static float dimensionalTearLucidEventChance = 0.65F;
private static int ultimateToolEnchantability = 25; private static int ultimateToolEnchantability = 25;
private static int ultimateArmorEnchantability = 10; private static int ultimateArmorEnchantability = 10;
@@ -250,6 +250,23 @@ public final class AntarchySettings {
private static double queenHealth = 6000.0D; private static double queenHealth = 6000.0D;
private static double kingAttackDamage = 50.0D; private static double kingAttackDamage = 50.0D;
private static double queenAttackDamage = 45.0D; private static double queenAttackDamage = 45.0D;
private static double royalBossSoundVolume = 4.5D;
private static double queenBlackHoleRadius = 14.0D;
private static int queenBlackHoleActiveTicks = 120;
private static double queenBlackHolePullStrength = 0.16D;
private static double queenFinalTimeFieldRadius = 20.0D;
private static double queenFinalTimeFieldRate = 0.05D;
private static int queenFinalTimeFieldDurationTicks = 45;
private static int queenFinalTimeFieldCooldownTicks = 80;
private static double kingFireballDamage = 32.0D;
private static double kingFireballRadius = 4.5D;
private static int kingFireballCooldownTicks = 150;
private static double kingIceballDamage = 18.0D;
private static double kingIceballRadius = 5.0D;
private static int kingIceballCooldownTicks = 190;
private static double kingIceSpikeDamage = 24.0D;
private static int kingIceSpikeCooldownTicks = 240;
private static int kingElementalTerrainCap = 256;
private static double royalBossArmor = 20.0D; private static double royalBossArmor = 20.0D;
private static double royalBossFollowRange = 128.0D; private static double royalBossFollowRange = 128.0D;
private static double royalBossMovementSpeed = 0.22D; private static double royalBossMovementSpeed = 0.22D;
@@ -391,7 +408,7 @@ public final class AntarchySettings {
private static double toreterrorDamageRange = 25.0D; private static double toreterrorDamageRange = 25.0D;
private static double brutalflyDamageRange = 25.0D; private static double brutalflyDamageRange = 25.0D;
private static double alphaMantisDamageRange = 25.0D; private static double alphaMantisDamageRange = 25.0D;
private static float krakenLightningDamagePhaseOne = 12.0F; private static float krakenLightningDamagePhaseOne = 6.0F;
private static double lucidHealth = 70.0D; private static double lucidHealth = 70.0D;
private static double lucidAttackDamage = 4.0D; private static double lucidAttackDamage = 4.0D;
private static double lucidMovementSpeed = 0.22D; private static double lucidMovementSpeed = 0.22D;
@@ -402,7 +419,7 @@ public final class AntarchySettings {
private static double vortexMovementSpeed = 0.22D; private static double vortexMovementSpeed = 0.22D;
private static double vortexFlyingSpeed = 0.35D; private static double vortexFlyingSpeed = 0.35D;
private static int vortexMaxActiveVortexes = 3; private static int vortexMaxActiveVortexes = 3;
private static int windVortexDurationTicks = 140; private static int windVortexDurationTicks = 240;
private static double windVortexPullStrength = 0.32D; private static double windVortexPullStrength = 0.32D;
private static double windVortexLaunchStrength = 1.0D; private static double windVortexLaunchStrength = 1.0D;
private static boolean eyeOfTheStormEnabled = true; private static boolean eyeOfTheStormEnabled = true;
@@ -534,6 +551,8 @@ public final class AntarchySettings {
private static double queenBeamDamage = 45.0D; private static double queenBeamDamage = 45.0D;
private static double queenBeamRange = 100.0D; private static double queenBeamRange = 100.0D;
private static int queenBeamDurationTicks = 30; private static int queenBeamDurationTicks = 30;
private static int queenBeamWindupTicks = 20;
private static int queenBeamTravelTicks = 12;
private static int queenBeamCooldownTicks = 90; private static int queenBeamCooldownTicks = 90;
private static double queenBeamTracking = 0.1D; private static double queenBeamTracking = 0.1D;
private static double queenBeamTerrainRadius = 5.0D; private static double queenBeamTerrainRadius = 5.0D;
@@ -541,6 +560,8 @@ public final class AntarchySettings {
private static double kingBeamDamage = 50.0D; private static double kingBeamDamage = 50.0D;
private static double kingBeamRange = 100.0D; private static double kingBeamRange = 100.0D;
private static int kingBeamDurationTicks = 30; private static int kingBeamDurationTicks = 30;
private static int kingBeamWindupTicks = 20;
private static int kingBeamTravelTicks = 12;
private static int kingBeamCooldownTicks = 90; private static int kingBeamCooldownTicks = 90;
private static double kingBeamTracking = 0.1D; private static double kingBeamTracking = 0.1D;
private static double kingBeamTerrainRadius = 5.0D; private static double kingBeamTerrainRadius = 5.0D;
@@ -803,6 +824,8 @@ public final class AntarchySettings {
return bigBerthaNightmareDamageBonusPercent; return bigBerthaNightmareDamageBonusPercent;
} }
public static double krakensGraspLightningDamage() { return krakensGraspLightningDamage; }
public static double potentNyxiteInvertedDurationSeconds() { public static double potentNyxiteInvertedDurationSeconds() {
return potentNyxiteInvertedDurationSeconds; return potentNyxiteInvertedDurationSeconds;
} }
@@ -2013,6 +2036,40 @@ public final class AntarchySettings {
public static double queenAttackDamage() { return queenAttackDamage; } public static double queenAttackDamage() { return queenAttackDamage; }
public static void setQueenAttackDamage(double v) { queenAttackDamage = v; } public static void setQueenAttackDamage(double v) { queenAttackDamage = v; }
public static double royalBossSoundVolume() { return royalBossSoundVolume; }
public static void setRoyalBossSoundVolume(double v) { royalBossSoundVolume = v; }
public static double queenBlackHoleRadius() { return queenBlackHoleRadius; }
public static void setQueenBlackHoleRadius(double v) { queenBlackHoleRadius = v; }
public static int queenBlackHoleActiveTicks() { return queenBlackHoleActiveTicks; }
public static void setQueenBlackHoleActiveTicks(int v) { queenBlackHoleActiveTicks = v; }
public static double queenBlackHolePullStrength() { return queenBlackHolePullStrength; }
public static void setQueenBlackHolePullStrength(double v) { queenBlackHolePullStrength = v; }
public static double queenFinalTimeFieldRadius() { return queenFinalTimeFieldRadius; }
public static void setQueenFinalTimeFieldRadius(double v) { queenFinalTimeFieldRadius = v; }
public static double queenFinalTimeFieldRate() { return queenFinalTimeFieldRate; }
public static void setQueenFinalTimeFieldRate(double v) { queenFinalTimeFieldRate = v; }
public static int queenFinalTimeFieldDurationTicks() { return queenFinalTimeFieldDurationTicks; }
public static void setQueenFinalTimeFieldDurationTicks(int v) { queenFinalTimeFieldDurationTicks = v; }
public static int queenFinalTimeFieldCooldownTicks() { return queenFinalTimeFieldCooldownTicks; }
public static void setQueenFinalTimeFieldCooldownTicks(int v) { queenFinalTimeFieldCooldownTicks = v; }
public static double kingFireballDamage() { return kingFireballDamage; }
public static void setKingFireballDamage(double v) { kingFireballDamage = v; }
public static double kingFireballRadius() { return kingFireballRadius; }
public static void setKingFireballRadius(double v) { kingFireballRadius = v; }
public static int kingFireballCooldownTicks() { return kingFireballCooldownTicks; }
public static void setKingFireballCooldownTicks(int v) { kingFireballCooldownTicks = v; }
public static double kingIceballDamage() { return kingIceballDamage; }
public static void setKingIceballDamage(double v) { kingIceballDamage = v; }
public static double kingIceballRadius() { return kingIceballRadius; }
public static void setKingIceballRadius(double v) { kingIceballRadius = v; }
public static int kingIceballCooldownTicks() { return kingIceballCooldownTicks; }
public static void setKingIceballCooldownTicks(int v) { kingIceballCooldownTicks = v; }
public static double kingIceSpikeDamage() { return kingIceSpikeDamage; }
public static void setKingIceSpikeDamage(double v) { kingIceSpikeDamage = v; }
public static int kingIceSpikeCooldownTicks() { return kingIceSpikeCooldownTicks; }
public static void setKingIceSpikeCooldownTicks(int v) { kingIceSpikeCooldownTicks = v; }
public static int kingElementalTerrainCap() { return kingElementalTerrainCap; }
public static void setKingElementalTerrainCap(int v) { kingElementalTerrainCap = v; }
public static double royalBossArmor() { return royalBossArmor; } public static double royalBossArmor() { return royalBossArmor; }
public static void setRoyalBossArmor(double v) { royalBossArmor = v; } public static void setRoyalBossArmor(double v) { royalBossArmor = v; }
@@ -2832,6 +2889,10 @@ public final class AntarchySettings {
public static void setQueenBeamRange(double v) { queenBeamRange = v; } public static void setQueenBeamRange(double v) { queenBeamRange = v; }
public static int queenBeamDurationTicks() { return queenBeamDurationTicks; } public static int queenBeamDurationTicks() { return queenBeamDurationTicks; }
public static void setQueenBeamDurationTicks(int v) { queenBeamDurationTicks = v; } public static void setQueenBeamDurationTicks(int v) { queenBeamDurationTicks = v; }
public static int queenBeamWindupTicks() { return queenBeamWindupTicks; }
public static void setQueenBeamWindupTicks(int v) { queenBeamWindupTicks = v; }
public static int queenBeamTravelTicks() { return queenBeamTravelTicks; }
public static void setQueenBeamTravelTicks(int v) { queenBeamTravelTicks = v; }
public static int queenBeamCooldownTicks() { return queenBeamCooldownTicks; } public static int queenBeamCooldownTicks() { return queenBeamCooldownTicks; }
public static void setQueenBeamCooldownTicks(int v) { queenBeamCooldownTicks = v; } public static void setQueenBeamCooldownTicks(int v) { queenBeamCooldownTicks = v; }
public static double queenBeamTracking() { return queenBeamTracking; } public static double queenBeamTracking() { return queenBeamTracking; }
@@ -2846,6 +2907,10 @@ public final class AntarchySettings {
public static void setKingBeamRange(double v) { kingBeamRange = v; } public static void setKingBeamRange(double v) { kingBeamRange = v; }
public static int kingBeamDurationTicks() { return kingBeamDurationTicks; } public static int kingBeamDurationTicks() { return kingBeamDurationTicks; }
public static void setKingBeamDurationTicks(int v) { kingBeamDurationTicks = v; } public static void setKingBeamDurationTicks(int v) { kingBeamDurationTicks = v; }
public static int kingBeamWindupTicks() { return kingBeamWindupTicks; }
public static void setKingBeamWindupTicks(int v) { kingBeamWindupTicks = v; }
public static int kingBeamTravelTicks() { return kingBeamTravelTicks; }
public static void setKingBeamTravelTicks(int v) { kingBeamTravelTicks = v; }
public static int kingBeamCooldownTicks() { return kingBeamCooldownTicks; } public static int kingBeamCooldownTicks() { return kingBeamCooldownTicks; }
public static void setKingBeamCooldownTicks(int v) { kingBeamCooldownTicks = v; } public static void setKingBeamCooldownTicks(int v) { kingBeamCooldownTicks = v; }
public static double kingBeamTracking() { return kingBeamTracking; } public static double kingBeamTracking() { return kingBeamTracking; }
@@ -157,6 +157,10 @@ public final class AntarchyObjects {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static Supplier<EntityType<com.craisinlord.antarchy.content.entity.royal.RoyalBoltEntity>> ROYAL_BOLT = (Supplier<EntityType<com.craisinlord.antarchy.content.entity.royal.RoyalBoltEntity>>) UNBOUND; public static Supplier<EntityType<com.craisinlord.antarchy.content.entity.royal.RoyalBoltEntity>> ROYAL_BOLT = (Supplier<EntityType<com.craisinlord.antarchy.content.entity.royal.RoyalBoltEntity>>) UNBOUND;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static Supplier<EntityType<com.craisinlord.antarchy.content.entity.royal.RoyalElementalProjectileEntity>> ROYAL_ELEMENTAL_PROJECTILE = (Supplier<EntityType<com.craisinlord.antarchy.content.entity.royal.RoyalElementalProjectileEntity>>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<EntityType<com.craisinlord.antarchy.content.entity.royal.RoyalIceSpikeEntity>> ROYAL_ICE_SPIKE = (Supplier<EntityType<com.craisinlord.antarchy.content.entity.royal.RoyalIceSpikeEntity>>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<EntityType<com.craisinlord.antarchy.content.entity.royal.KingEntity>> KING = (Supplier<EntityType<com.craisinlord.antarchy.content.entity.royal.KingEntity>>) UNBOUND; public static Supplier<EntityType<com.craisinlord.antarchy.content.entity.royal.KingEntity>> KING = (Supplier<EntityType<com.craisinlord.antarchy.content.entity.royal.KingEntity>>) UNBOUND;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static Supplier<EntityType<com.craisinlord.antarchy.content.entity.royal.QueenEntity>> QUEEN = (Supplier<EntityType<com.craisinlord.antarchy.content.entity.royal.QueenEntity>>) UNBOUND; public static Supplier<EntityType<com.craisinlord.antarchy.content.entity.royal.QueenEntity>> QUEEN = (Supplier<EntityType<com.craisinlord.antarchy.content.entity.royal.QueenEntity>>) UNBOUND;
@@ -725,6 +729,14 @@ public final class AntarchyObjects {
ROYAL_BOLT = supplier; ROYAL_BOLT = supplier;
} }
public static void setRoyalElementalProjectile(Supplier<EntityType<com.craisinlord.antarchy.content.entity.royal.RoyalElementalProjectileEntity>> supplier) {
ROYAL_ELEMENTAL_PROJECTILE = supplier;
}
public static void setRoyalIceSpike(Supplier<EntityType<com.craisinlord.antarchy.content.entity.royal.RoyalIceSpikeEntity>> supplier) {
ROYAL_ICE_SPIKE = supplier;
}
public static void setKing(Supplier<EntityType<com.craisinlord.antarchy.content.entity.royal.KingEntity>> supplier) { public static void setKing(Supplier<EntityType<com.craisinlord.antarchy.content.entity.royal.KingEntity>> supplier) {
KING = supplier; KING = supplier;
} }
@@ -202,6 +202,7 @@ public final class AntarchySoundEvents {
public static Supplier<SoundEvent> KING_ROAR = (Supplier<SoundEvent>) UNBOUND; public static Supplier<SoundEvent> KING_ROAR = (Supplier<SoundEvent>) UNBOUND;
public static Supplier<SoundEvent> KING_WING_FLAP = (Supplier<SoundEvent>) UNBOUND; public static Supplier<SoundEvent> KING_WING_FLAP = (Supplier<SoundEvent>) UNBOUND;
public static Supplier<SoundEvent> KING_FLY_LOOP = (Supplier<SoundEvent>) UNBOUND; public static Supplier<SoundEvent> KING_FLY_LOOP = (Supplier<SoundEvent>) UNBOUND;
public static Supplier<SoundEvent> QUEEN_FLY_LOOP = (Supplier<SoundEvent>) UNBOUND;
public static Supplier<SoundEvent> PRINCE_IDLE = (Supplier<SoundEvent>) UNBOUND; public static Supplier<SoundEvent> PRINCE_IDLE = (Supplier<SoundEvent>) UNBOUND;
public static Supplier<SoundEvent> PRINCE_BITE = (Supplier<SoundEvent>) UNBOUND; public static Supplier<SoundEvent> PRINCE_BITE = (Supplier<SoundEvent>) UNBOUND;
public static Supplier<SoundEvent> PRINCE_SHOOT = (Supplier<SoundEvent>) UNBOUND; public static Supplier<SoundEvent> PRINCE_SHOOT = (Supplier<SoundEvent>) UNBOUND;
@@ -406,6 +407,7 @@ public final class AntarchySoundEvents {
Supplier<SoundEvent> kingRoar, Supplier<SoundEvent> kingRoar,
Supplier<SoundEvent> kingWingFlap, Supplier<SoundEvent> kingWingFlap,
Supplier<SoundEvent> kingFlyLoop, Supplier<SoundEvent> kingFlyLoop,
Supplier<SoundEvent> queenFlyLoop,
Supplier<SoundEvent> princeIdle, Supplier<SoundEvent> princeIdle,
Supplier<SoundEvent> princeBite, Supplier<SoundEvent> princeBite,
Supplier<SoundEvent> princeShoot, Supplier<SoundEvent> princeShoot,
@@ -606,6 +608,7 @@ public final class AntarchySoundEvents {
KING_ROAR = kingRoar; KING_ROAR = kingRoar;
KING_WING_FLAP = kingWingFlap; KING_WING_FLAP = kingWingFlap;
KING_FLY_LOOP = kingFlyLoop; KING_FLY_LOOP = kingFlyLoop;
QUEEN_FLY_LOOP = queenFlyLoop;
PRINCE_IDLE = princeIdle; PRINCE_IDLE = princeIdle;
PRINCE_BITE = princeBite; PRINCE_BITE = princeBite;
PRINCE_SHOOT = princeShoot; PRINCE_SHOOT = princeShoot;
@@ -85,6 +85,8 @@ public final class AntarchyTags {
public static final TagKey<EntityType<?>> DIMENSIONAL_TEAR_COMMON_SPAWNS = TagKey.create(Registries.ENTITY_TYPE, id("dimensional_tear_common_spawns")); public static final TagKey<EntityType<?>> DIMENSIONAL_TEAR_COMMON_SPAWNS = TagKey.create(Registries.ENTITY_TYPE, id("dimensional_tear_common_spawns"));
public static final TagKey<EntityType<?>> DIMENSIONAL_TEAR_RARE_SPAWNS = TagKey.create(Registries.ENTITY_TYPE, id("dimensional_tear_rare_spawns")); public static final TagKey<EntityType<?>> DIMENSIONAL_TEAR_RARE_SPAWNS = TagKey.create(Registries.ENTITY_TYPE, id("dimensional_tear_rare_spawns"));
public static final TagKey<EntityType<?>> TIME_DILATION_IMMUNE = TagKey.create(Registries.ENTITY_TYPE, id("time_dilation_immune")); public static final TagKey<EntityType<?>> TIME_DILATION_IMMUNE = TagKey.create(Registries.ENTITY_TYPE, id("time_dilation_immune"));
public static final TagKey<EntityType<?>> COMMANDED_BLACKLIST = TagKey.create(Registries.ENTITY_TYPE, id("commanded_blacklist"));
public static final TagKey<EntityType<?>> DILATED_BLACKLIST = TagKey.create(Registries.ENTITY_TYPE, id("dilated_blacklist"));
private Entities() { private Entities() {
} }
@@ -12,17 +12,23 @@ import net.minecraft.util.RandomSource;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
public class DimensionalTearMarkerBlockEntity extends BlockEntity { public class DimensionalTearMarkerBlockEntity extends BlockEntity {
private static final int MAX_ATTEMPT_TICKS = 100; private static final int MAX_ATTEMPT_TICKS = 80;
private static final int MIN_LINK_RANGE = 50; private static final int MIN_LINK_RANGE = 50;
private static final int MAX_LINK_RANGE = 500; private static final int MAX_LINK_RANGE = 500;
private static final int PARTNER_ORIGIN_ATTEMPTS = 12; private static final int PARTNER_ORIGIN_ATTEMPTS = 8;
private static final int PARTNER_SEARCH_ATTEMPTS = 48; private static final int PARTNER_SEARCH_ATTEMPTS = 32;
private static final int LOCAL_SEARCH_ATTEMPTS = 18; private static final int LOCAL_SEARCH_ATTEMPTS = 12;
private static final int SEARCH_ATTEMPTS_PER_TICK = 2; private static final int SEARCH_ATTEMPTS_PER_TICK = 1;
private static final int MIN_VERTICAL_SEPARATION = 24;
private static final int MIN_PARTNER_VERTICAL_OFFSET = 48;
private static final int MAX_PARTNER_VERTICAL_OFFSET = 96;
private static final int NATURAL_TEAR_LOCAL_RADIUS = 512;
private static final int NATURAL_TEAR_LOCAL_CAP = 6;
private int attemptTicks; private int attemptTicks;
private int localAttempts; private int localAttempts;
@@ -64,6 +70,11 @@ public class DimensionalTearMarkerBlockEntity extends BlockEntity {
continue; continue;
} }
if (partnerOrigin == null && partnerOriginAttempts == 0
&& countNearbyTears(level, origin) >= NATURAL_TEAR_LOCAL_CAP) {
return false;
}
if (partnerOrigin == null || partnerSearchAttempts >= PARTNER_SEARCH_ATTEMPTS) { if (partnerOrigin == null || partnerSearchAttempts >= PARTNER_SEARCH_ATTEMPTS) {
if (partnerOriginAttempts >= PARTNER_ORIGIN_ATTEMPTS) { if (partnerOriginAttempts >= PARTNER_ORIGIN_ATTEMPTS) {
return false; return false;
@@ -75,7 +86,8 @@ public class DimensionalTearMarkerBlockEntity extends BlockEntity {
partnerSearchAttempts++; partnerSearchAttempts++;
BlockPos candidate = randomCandidate(partnerOrigin, random); BlockPos candidate = randomCandidate(partnerOrigin, random);
if (!isUsablePocket(level, candidate)) { if (Math.abs(candidate.getY() - firstPocket.getY()) < MIN_VERTICAL_SEPARATION
|| !isUsablePocket(level, candidate)) {
continue; continue;
} }
return spawnPair(level, random, firstPocket, candidate); return spawnPair(level, random, firstPocket, candidate);
@@ -111,10 +123,16 @@ public class DimensionalTearMarkerBlockEntity extends BlockEntity {
int distance = Mth.nextInt(random, MIN_LINK_RANGE, MAX_LINK_RANGE); int distance = Mth.nextInt(random, MIN_LINK_RANGE, MAX_LINK_RANGE);
int dx = Mth.floor(Math.cos(angle) * distance); int dx = Mth.floor(Math.cos(angle) * distance);
int dz = Mth.floor(Math.sin(angle) * distance); int dz = Mth.floor(Math.sin(angle) * distance);
int dy = Mth.nextInt(random, -36, 36); int verticalOffset = Mth.nextInt(random, MIN_PARTNER_VERTICAL_OFFSET, MAX_PARTNER_VERTICAL_OFFSET);
int dy = random.nextBoolean() ? verticalOffset : -verticalOffset;
return first.offset(dx, dy, dz); return first.offset(dx, dy, dz);
} }
private static int countNearbyTears(ServerLevel level, BlockPos origin) {
AABB area = new AABB(origin).inflate(NATURAL_TEAR_LOCAL_RADIUS);
return level.getEntitiesOfClass(DimensionalTearEntity.class, area).size();
}
private static boolean isUsablePocket(ServerLevel level, BlockPos pos) { private static boolean isUsablePocket(ServerLevel level, BlockPos pos) {
int minY = level.getMinBuildHeight() + 8; int minY = level.getMinBuildHeight() + 8;
int maxY = level.getMaxBuildHeight() - 8; int maxY = level.getMaxBuildHeight() - 8;
@@ -10,6 +10,7 @@ import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.item.ItemEntity; import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.util.Mth; import net.minecraft.util.Mth;
public final class ClientTimeDilationTicker { public final class ClientTimeDilationTicker {
@@ -32,6 +33,11 @@ public final class ClientTimeDilationTicker {
com.craisinlord.antarchy.content.time.TimeDilationApi.clearSyncedClientRates(); com.craisinlord.antarchy.content.time.TimeDilationApi.clearSyncedClientRates();
} }
// Entity rates come from the server. Field snapshots are used only for local effects. // Entity rates come from the server. Field snapshots are used only for local effects.
for (Player player : level.players()) {
if (player.isDeadOrDying() || rateAt(player.getX(), player.getY(), player.getZ()) >= TimeDilationMath.NORMAL_RATE) {
com.craisinlord.antarchy.content.time.TimeDilationApi.clearRate(player);
}
}
} }
public static float dilateAnimationTime(Entity entity, float vanillaTime) { public static float dilateAnimationTime(Entity entity, float vanillaTime) {
@@ -0,0 +1,23 @@
package com.craisinlord.antarchy.content.client.model;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.entity.royal.RoyalBlackHoleEntity;
import net.minecraft.resources.ResourceLocation;
import software.bernie.geckolib.model.GeoModel;
public class RoyalBlackHoleModel extends GeoModel<RoyalBlackHoleEntity> {
@Override
public ResourceLocation getModelResource(RoyalBlackHoleEntity animatable) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "geo/black_hole.geo.json");
}
@Override
public ResourceLocation getTextureResource(RoyalBlackHoleEntity animatable) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "textures/entity/queen/black_hole.png");
}
@Override
public ResourceLocation getAnimationResource(RoyalBlackHoleEntity animatable) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "animations/black_hole.animation.json");
}
}
@@ -0,0 +1,26 @@
package com.craisinlord.antarchy.content.client.model;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.entity.royal.RoyalElementalProjectileEntity;
import net.minecraft.resources.ResourceLocation;
import software.bernie.geckolib.model.GeoModel;
public class RoyalElementalProjectileModel extends GeoModel<RoyalElementalProjectileEntity> {
@Override
public ResourceLocation getModelResource(RoyalElementalProjectileEntity animatable) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID,
"geo/" + (animatable.isIceball() ? "ice_ball" : "fire_ball") + ".geo.json");
}
@Override
public ResourceLocation getTextureResource(RoyalElementalProjectileEntity animatable) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID,
"textures/entity/king/" + (animatable.isIceball() ? "ice_ball" : "fire_ball") + ".png");
}
@Override
public ResourceLocation getAnimationResource(RoyalElementalProjectileEntity animatable) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID,
"animations/" + (animatable.isIceball() ? "ice_ball" : "fire_ball") + ".animation.json");
}
}
@@ -0,0 +1,23 @@
package com.craisinlord.antarchy.content.client.model;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.entity.royal.RoyalIceSpikeEntity;
import net.minecraft.resources.ResourceLocation;
import software.bernie.geckolib.model.GeoModel;
public class RoyalIceSpikeModel extends GeoModel<RoyalIceSpikeEntity> {
@Override
public ResourceLocation getModelResource(RoyalIceSpikeEntity animatable) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "geo/ice_spike.geo.json");
}
@Override
public ResourceLocation getTextureResource(RoyalIceSpikeEntity animatable) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "textures/entity/king/ice_spike.png");
}
@Override
public ResourceLocation getAnimationResource(RoyalIceSpikeEntity animatable) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "animations/ice_spike.animation.json");
}
}
@@ -0,0 +1,30 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.content.client.model.RoyalBlackHoleModel;
import com.craisinlord.antarchy.content.entity.royal.RoyalBlackHoleEntity;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.Nullable;
import software.bernie.geckolib.renderer.GeoEntityRenderer;
public class RoyalBlackHoleRenderer extends GeoEntityRenderer<RoyalBlackHoleEntity> {
public RoyalBlackHoleRenderer(EntityRendererProvider.Context context) {
super(context, new RoyalBlackHoleModel());
this.shadowRadius = 0.0F;
}
@Override
public void render(RoyalBlackHoleEntity entity, float entityYaw, float partialTick, PoseStack poseStack,
MultiBufferSource bufferSource, int packedLight) {
super.render(entity, entityYaw, partialTick, poseStack, bufferSource, 0xF000F0);
}
@Override
public @Nullable RenderType getRenderType(RoyalBlackHoleEntity animatable, ResourceLocation texture,
@Nullable MultiBufferSource bufferSource, float partialTick) {
return RenderType.entityTranslucent(this.getTextureLocation(animatable));
}
}
@@ -58,8 +58,8 @@ public class RoyalBossRenderer extends GeoEntityRenderer<RoyalBossEntity> {
this.shadowRadius = 8.0F * RoyalBossEntity.MODEL_RENDER_SCALE; this.shadowRadius = 8.0F * RoyalBossEntity.MODEL_RENDER_SCALE;
} }
private static final float BEAM_OUTER_RADIUS = 0.55F; private static final float BEAM_OUTER_RADIUS = 1.10F;
private static final float BEAM_INNER_RADIUS = 0.26F; private static final float BEAM_INNER_RADIUS = 0.52F;
private static final float BEAM_TILE_BLOCKS = 3.0F; private static final float BEAM_TILE_BLOCKS = 3.0F;
private static final float BEAM_SCROLL_SPEED = 0.32F; private static final float BEAM_SCROLL_SPEED = 0.32F;
@@ -0,0 +1,38 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.content.client.model.RoyalElementalProjectileModel;
import com.craisinlord.antarchy.content.entity.royal.RoyalElementalProjectileEntity;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import com.mojang.math.Axis;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.util.Mth;
import software.bernie.geckolib.cache.object.BakedGeoModel;
import software.bernie.geckolib.renderer.GeoEntityRenderer;
public class RoyalElementalProjectileRenderer extends GeoEntityRenderer<RoyalElementalProjectileEntity> {
public RoyalElementalProjectileRenderer(EntityRendererProvider.Context context) {
super(context, new RoyalElementalProjectileModel());
this.shadowRadius = 0.0F;
}
@Override
public void preRender(PoseStack poseStack, RoyalElementalProjectileEntity animatable, BakedGeoModel model,
MultiBufferSource bufferSource, VertexConsumer buffer, boolean isReRender,
float partialTick, int packedLight, int packedOverlay, int colour) {
super.preRender(poseStack, animatable, model, bufferSource, buffer, isReRender, partialTick,
packedLight, packedOverlay, colour);
float scale = animatable.isIceball() ? 0.75F : 0.36F;
poseStack.scale(scale, scale, scale);
}
@Override
protected void applyRotations(RoyalElementalProjectileEntity entity, PoseStack poseStack, float ageInTicks,
float rotationYaw, float partialTick, float nativeScale) {
float yaw = Mth.rotLerp(partialTick, entity.yRotO, entity.getYRot());
float pitch = Mth.lerp(partialTick, entity.xRotO, entity.getXRot());
poseStack.mulPose(Axis.YP.rotationDegrees(180.0F - yaw));
poseStack.mulPose(Axis.XP.rotationDegrees(-pitch));
}
}
@@ -0,0 +1,14 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.content.client.model.RoyalIceSpikeModel;
import com.craisinlord.antarchy.content.entity.royal.RoyalIceSpikeEntity;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import software.bernie.geckolib.renderer.GeoEntityRenderer;
public class RoyalIceSpikeRenderer extends GeoEntityRenderer<RoyalIceSpikeEntity> {
public RoyalIceSpikeRenderer(EntityRendererProvider.Context context) {
super(context, new RoyalIceSpikeModel());
this.shadowRadius = 0.0F;
}
}
@@ -12,9 +12,8 @@ import net.minecraft.util.Mth;
import org.joml.Matrix4f; import org.joml.Matrix4f;
public class WindVortexRenderer extends EntityRenderer<WindVortexEntity> { public class WindVortexRenderer extends EntityRenderer<WindVortexEntity> {
private static final int RINGS = 10; private static final int RINGS = 11;
private static final int SEGMENTS_PER_RING = 12; private static final int SQUARES_PER_SIDE = 3;
private static final int ARC_STEPS = 6;
private static final float BASE_RADIUS = 0.35F; private static final float BASE_RADIUS = 0.35F;
public WindVortexRenderer(EntityRendererProvider.Context context) { public WindVortexRenderer(EntityRendererProvider.Context context) {
@@ -44,48 +43,72 @@ public class WindVortexRenderer extends EntityRenderer<WindVortexEntity> {
float radius = Mth.lerp(shapeProgress, BASE_RADIUS, topRadius); float radius = Mth.lerp(shapeProgress, BASE_RADIUS, topRadius);
float ringSpin = spinDirection * age * (0.13F + progress * 0.045F) + ring * 0.47F; float ringSpin = spinDirection * age * (0.13F + progress * 0.045F) + ring * 0.47F;
float alpha = alphaMultiplier; float alpha = alphaMultiplier;
float thickness = 0.025F + radius * 0.012F; float halfSide = radius * 0.92F;
float arcLength = 0.34F + progress * 0.08F; float tileSize = Math.max(0.035F, radius * 0.12F);
int red = push ? 70 : 64; int red = push ? 70 : 64;
int green = push ? 220 : 255; int green = push ? 220 : 255;
int blue = push ? 255 : 56; int blue = push ? 255 : 56;
drawSquareRing(consumer, pose, basis, halfSide, axisDistance, tileSize, ringSpin,
for (int segment = 0; segment < SEGMENTS_PER_RING; segment++) { alpha, red, green, blue);
float baseAngle = ringSpin + segment * Mth.TWO_PI / SEGMENTS_PER_RING;
float start = baseAngle - arcLength * 0.5F;
float end = baseAngle + arcLength * 0.5F;
drawArc(consumer, pose, basis, radius, axisDistance, thickness, start, end, alpha, red, green, blue);
}
} }
super.render(entity, entityYaw, partialTick, poseStack, bufferSource, packedLight); super.render(entity, entityYaw, partialTick, poseStack, bufferSource, packedLight);
} }
private static void drawArc(VertexConsumer consumer, Matrix4f pose, WindVortexEntity.Basis basis, float radius, private static void drawSquareRing(VertexConsumer consumer, Matrix4f pose, WindVortexEntity.Basis basis,
float axisDistance, float thickness, float start, float end, float alpha, int red, int green, int blue) { float halfSide, float axisDistance, float tileSize, float rotation, float alpha,
float previousAngle = start; int red, int green, int blue) {
for (int step = 1; step <= ARC_STEPS; step++) { for (int side = 0; side < 4; side++) {
float angle = Mth.lerp(step / (float) ARC_STEPS, start, end); for (int tile = 0; tile < SQUARES_PER_SIDE; tile++) {
addLineQuad(consumer, pose, basis, radius, axisDistance, thickness, previousAngle, angle, alpha, red, green, blue); float start = -halfSide + (tile + 0.18F) * (2.0F * halfSide / SQUARES_PER_SIDE);
previousAngle = angle; float end = -halfSide + (tile + 0.82F) * (2.0F * halfSide / SQUARES_PER_SIDE);
float sideAngle = rotation + side * (Mth.TWO_PI / 4.0F);
addSquareTile(consumer, pose, basis, halfSide, axisDistance, tileSize,
sideAngle, start, end, alpha, red, green, blue);
}
} }
} }
private static void addLineQuad(VertexConsumer consumer, Matrix4f pose, WindVortexEntity.Basis basis, float radius, private static void addSquareTile(VertexConsumer consumer, Matrix4f pose, WindVortexEntity.Basis basis,
float axisDistance, float thickness, float startAngle, float endAngle, float alpha, int red, int green, int blue) { float halfSide, float axisDistance, float tileSize, float sideAngle, float start, float end,
org.joml.Vector3f start = point(basis, radius, axisDistance, startAngle); float alpha, int red, int green, int blue) {
org.joml.Vector3f end = point(basis, radius, axisDistance, endAngle); org.joml.Vector3f center = squarePoint(basis, halfSide, axisDistance, sideAngle, (start + end) * 0.5F);
org.joml.Vector3f axisOffset = new org.joml.Vector3f((float) basis.axis.x, (float) basis.axis.y, (float) basis.axis.z).mul(thickness); org.joml.Vector3f tangent = new org.joml.Vector3f(
float middleAngle = (startAngle + endAngle) * 0.5F; (float) (-basis.sideA.x * Mth.sin(sideAngle) + basis.sideB.x * Mth.cos(sideAngle)),
org.joml.Vector3f radialOffset = new org.joml.Vector3f( (float) (-basis.sideA.y * Mth.sin(sideAngle) + basis.sideB.y * Mth.cos(sideAngle)),
(float) (basis.sideA.x * Mth.cos(middleAngle) + basis.sideB.x * Mth.sin(middleAngle)), (float) (-basis.sideA.z * Mth.sin(sideAngle) + basis.sideB.z * Mth.cos(sideAngle)));
(float) (basis.sideA.y * Mth.cos(middleAngle) + basis.sideB.y * Mth.sin(middleAngle)), org.joml.Vector3f axisOffset = new org.joml.Vector3f(
(float) (basis.sideA.z * Mth.cos(middleAngle) + basis.sideB.z * Mth.sin(middleAngle)) (float) basis.axis.x, (float) basis.axis.y, (float) basis.axis.z).mul(tileSize * 0.5F);
).mul(thickness); tangent.mul(tileSize * 0.5F);
int a = Mth.clamp((int) (alpha * 255.0F), 0, 255); int a = Mth.clamp((int) (alpha * 255.0F), 0, 255);
addVertex(consumer, pose, center.x - tangent.x - axisOffset.x, center.y - tangent.y - axisOffset.y,
center.z - tangent.z - axisOffset.z, red, green, blue, a);
addVertex(consumer, pose, center.x + tangent.x - axisOffset.x, center.y + tangent.y - axisOffset.y,
center.z + tangent.z - axisOffset.z, red, green, blue, a);
addVertex(consumer, pose, center.x + tangent.x + axisOffset.x, center.y + tangent.y + axisOffset.y,
center.z + tangent.z + axisOffset.z, red, green, blue, a);
addVertex(consumer, pose, center.x - tangent.x + axisOffset.x, center.y - tangent.y + axisOffset.y,
center.z - tangent.z + axisOffset.z, red, green, blue, a);
addVertex(consumer, pose, center.x - tangent.x + axisOffset.x, center.y - tangent.y + axisOffset.y,
center.z - tangent.z + axisOffset.z, red, green, blue, a);
addVertex(consumer, pose, center.x + tangent.x + axisOffset.x, center.y + tangent.y + axisOffset.y,
center.z + tangent.z + axisOffset.z, red, green, blue, a);
addVertex(consumer, pose, center.x + tangent.x - axisOffset.x, center.y + tangent.y - axisOffset.y,
center.z + tangent.z - axisOffset.z, red, green, blue, a);
addVertex(consumer, pose, center.x - tangent.x - axisOffset.x, center.y - tangent.y - axisOffset.y,
center.z - tangent.z - axisOffset.z, red, green, blue, a);
}
addQuad(consumer, pose, start, end, axisOffset, red, green, blue, a); private static org.joml.Vector3f squarePoint(WindVortexEntity.Basis basis, float halfSide,
addQuad(consumer, pose, start, end, radialOffset, red, green, blue, a); float axisDistance, float sideAngle, float along) {
float x = Mth.cos(sideAngle) * halfSide;
float z = Mth.sin(sideAngle) * halfSide;
float tangentX = -Mth.sin(sideAngle) * along;
float tangentZ = Mth.cos(sideAngle) * along;
return new org.joml.Vector3f(
(float) (basis.axis.x * axisDistance + basis.sideA.x * (x + tangentX) + basis.sideB.x * (z + tangentZ)),
(float) (basis.axis.y * axisDistance + basis.sideA.y * (x + tangentX) + basis.sideB.y * (z + tangentZ)),
(float) (basis.axis.z * axisDistance + basis.sideA.z * (x + tangentX) + basis.sideB.z * (z + tangentZ)));
} }
private static void addQuad(VertexConsumer consumer, Matrix4f pose, org.joml.Vector3f start, private static void addQuad(VertexConsumer consumer, Matrix4f pose, org.joml.Vector3f start,
@@ -94,15 +117,14 @@ public class WindVortexRenderer extends EntityRenderer<WindVortexEntity> {
addVertex(consumer, pose, end.x - offset.x, end.y - offset.y, end.z - offset.z, red, green, blue, alpha); addVertex(consumer, pose, end.x - offset.x, end.y - offset.y, end.z - offset.z, red, green, blue, alpha);
addVertex(consumer, pose, end.x + offset.x, end.y + offset.y, end.z + offset.z, red, green, blue, alpha); addVertex(consumer, pose, end.x + offset.x, end.y + offset.y, end.z + offset.z, red, green, blue, alpha);
addVertex(consumer, pose, start.x + offset.x, start.y + offset.y, start.z + offset.z, red, green, blue, alpha); addVertex(consumer, pose, start.x + offset.x, start.y + offset.y, start.z + offset.z, red, green, blue, alpha);
}
private static org.joml.Vector3f point(WindVortexEntity.Basis basis, float radius, float axisDistance, float angle) { // The lightning render type can cull one winding from some camera
double radialA = Mth.cos(angle) * radius; // angles. Emit the opposite winding as well so every ribbon is
double radialB = Mth.sin(angle) * radius; // visible from both sides.
double x = basis.axis.x * axisDistance + basis.sideA.x * radialA + basis.sideB.x * radialB; addVertex(consumer, pose, start.x + offset.x, start.y + offset.y, start.z + offset.z, red, green, blue, alpha);
double y = basis.axis.y * axisDistance + basis.sideA.y * radialA + basis.sideB.y * radialB; addVertex(consumer, pose, end.x + offset.x, end.y + offset.y, end.z + offset.z, red, green, blue, alpha);
double z = basis.axis.z * axisDistance + basis.sideA.z * radialA + basis.sideB.z * radialB; addVertex(consumer, pose, end.x - offset.x, end.y - offset.y, end.z - offset.z, red, green, blue, alpha);
return new org.joml.Vector3f((float) x, (float) y, (float) z); addVertex(consumer, pose, start.x - offset.x, start.y - offset.y, start.z - offset.z, red, green, blue, alpha);
} }
private static void addVertex(VertexConsumer consumer, Matrix4f pose, float x, float y, float z, private static void addVertex(VertexConsumer consumer, Matrix4f pose, float x, float y, float z,
@@ -594,7 +594,8 @@ public final class CreativeTabContents {
private static final List<String> ANTARCHY_POTION_FAMILIES = List.of( private static final List<String> ANTARCHY_POTION_FAMILIES = List.of(
"dread", "long_dread", "inversion", "long_inversion", "stinky", "long_stinky", "dread", "long_dread", "inversion", "long_inversion", "stinky", "long_stinky",
"paralysis", "long_paralysis", "haste", "strong_haste", "shrinking", "paralysis", "long_paralysis", "haste", "strong_haste", "shrinking",
"strong_shrinking", "extreme_shrinking", "growing", "strong_growing", "extreme_growing" "strong_shrinking", "extreme_shrinking", "growing", "strong_growing", "extreme_growing",
"command", "long_command", "time_dilation", "long_time_dilation"
); );
private static final Map<String, Integer> ORDER_INDEX = createOrderIndex(); private static final Map<String, Integer> ORDER_INDEX = createOrderIndex();
@@ -0,0 +1,99 @@
package com.craisinlord.antarchy.content.effect;
import java.util.UUID;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.player.Player;
public final class CommandedBehavior {
private CommandedBehavior() {
}
public static void tick(Mob mob) {
if (!(mob instanceof CommandedEntityAccess access)) {
return;
}
UUID commanderUuid = access.antarchy$getCommanderUuid();
if (commanderUuid == null || mob.level().getServer() == null) {
clear(mob);
return;
}
ServerPlayer commander = mob.level().getServer().getPlayerList().getPlayer(commanderUuid);
if (commander == null || commander.level() != mob.level() || !RoyalEffectEligibility.canApplyCommanded(mob)) {
mob.removeEffect(RoyalEffectHooks.commandedHolder());
clear(mob);
return;
}
LivingEntity target = mob.getTarget();
if (target != null && isProtectedAlly(mob, target)) {
mob.setTarget(null);
target = null;
}
if (mob.tickCount % 10 != 0 || target != null) {
return;
}
LivingEntity candidate = commander.getLastHurtByMob();
if (!isValidTarget(mob, commander, candidate)) {
candidate = commander.getLastHurtMob();
}
if (!isValidTarget(mob, commander, candidate)) {
candidate = mob.getLastHurtByMob();
}
if (!isValidTarget(mob, commander, candidate)) {
candidate = mob.level().getEntitiesOfClass(LivingEntity.class, mob.getBoundingBox().inflate(16.0D),
entity -> isValidTarget(mob, commander, entity)).stream()
.min((a, b) -> {
int distance = Double.compare(mob.distanceToSqr(a), mob.distanceToSqr(b));
return distance != 0 ? distance : Integer.compare(a.getId(), b.getId());
}).orElse(null);
}
if (candidate != null) {
mob.setTarget(candidate);
access.antarchy$setCommandedTargetOwned(true);
}
}
public static boolean isProtectedAlly(Mob mob, Entity entity) {
if (!(mob instanceof CommandedEntityAccess access) || entity == null) {
return false;
}
UUID commanderUuid = access.antarchy$getCommanderUuid();
if (commanderUuid == null) {
return false;
}
if (entity.getUUID().equals(commanderUuid)) {
return true;
}
if (entity instanceof Mob other && other instanceof CommandedEntityAccess otherAccess) {
return commanderUuid.equals(otherAccess.antarchy$getCommanderUuid());
}
if (entity instanceof LivingEntity living && living.isAlliedTo(mob)) {
return true;
}
if (mob.level().getServer() != null
&& mob.level().getServer().getPlayerList().getPlayer(commanderUuid) instanceof Player commander
&& entity instanceof LivingEntity living) {
return living.isAlliedTo(commander);
}
return false;
}
private static boolean isValidTarget(Mob mob, ServerPlayer commander, LivingEntity target) {
return target != null && target.isAlive() && target != mob && target != commander
&& !isProtectedAlly(mob, target) && !target.isAlliedTo(commander)
&& mob.canAttack(target);
}
public static void clear(Mob mob) {
if (!(mob instanceof CommandedEntityAccess access)) {
return;
}
if (access.antarchy$isCommandedTargetOwned()) {
mob.setTarget(null);
}
access.antarchy$setCommandedTargetOwned(false);
access.antarchy$setCommanderUuid(null);
}
}
@@ -0,0 +1,10 @@
package com.craisinlord.antarchy.content.effect;
import java.util.UUID;
public interface CommandedEntityAccess {
UUID antarchy$getCommanderUuid();
void antarchy$setCommanderUuid(UUID uuid);
boolean antarchy$isCommandedTargetOwned();
void antarchy$setCommandedTargetOwned(boolean owned);
}
@@ -0,0 +1,25 @@
package com.craisinlord.antarchy.content.effect;
import net.minecraft.world.effect.MobEffect;
import net.minecraft.world.effect.MobEffectCategory;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.Mob;
public final class CommandedMobEffect extends MobEffect {
public CommandedMobEffect() {
super(MobEffectCategory.HARMFUL, 0xD4AF37);
}
@Override
public boolean applyEffectTick(LivingEntity entity, int amplifier) {
if (!entity.level().isClientSide() && entity instanceof Mob mob) {
CommandedBehavior.tick(mob);
}
return true;
}
@Override
public boolean shouldApplyEffectTickThisTick(int duration, int amplifier) {
return true;
}
}
@@ -0,0 +1,23 @@
package com.craisinlord.antarchy.content.effect;
import net.minecraft.world.effect.MobEffect;
import net.minecraft.world.effect.MobEffectCategory;
import net.minecraft.world.entity.LivingEntity;
public final class DilatedMobEffect extends MobEffect {
public static final double RATE = 0.35D;
public DilatedMobEffect() {
super(MobEffectCategory.HARMFUL, 0x6C4AB6);
}
@Override
public boolean applyEffectTick(LivingEntity entity, int amplifier) {
return true;
}
@Override
public boolean shouldApplyEffectTickThisTick(int duration, int amplifier) {
return true;
}
}
@@ -1,11 +1,14 @@
package com.craisinlord.antarchy.content.effect; package com.craisinlord.antarchy.content.effect;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi; import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityDirection; import com.craisinlord.antarchy.content.gravity.AntarchyGravityDirection;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityTransition; import com.craisinlord.antarchy.content.gravity.AntarchyGravityTransition;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.effect.MobEffect; import net.minecraft.world.effect.MobEffect;
import net.minecraft.world.effect.MobEffectCategory; import net.minecraft.world.effect.MobEffectCategory;
import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
public final class InvertedMobEffect extends MobEffect { public final class InvertedMobEffect extends MobEffect {
private static final AntarchyGravityTransition EFFECT_TRANSITION = new AntarchyGravityTransition(12); private static final AntarchyGravityTransition EFFECT_TRANSITION = new AntarchyGravityTransition(12);
@@ -19,6 +22,19 @@ public final class InvertedMobEffect extends MobEffect {
public boolean applyEffectTick(LivingEntity entity, int amplifier) { public boolean applyEffectTick(LivingEntity entity, int amplifier) {
if (!entity.level().isClientSide()) { if (!entity.level().isClientSide()) {
if (AntarchyGravityApi.getGravityDirection(entity) != AntarchyGravityDirection.UP || !AntarchyGravityApi.isGravityForced(entity)) { if (AntarchyGravityApi.getGravityDirection(entity) != AntarchyGravityDirection.UP || !AntarchyGravityApi.isGravityForced(entity)) {
MobEffectInstance effect = entity.getEffect(com.craisinlord.antarchy.content.AntarchyObjects.INVERTED_EFFECT.get());
if (entity instanceof Player || com.craisinlord.antarchy.content.worldgen.thoraxis.ThoraxisUndersideManager.isThoraxis(entity.level())) {
Antarchy.LOGGER.info(
"[antarchy-gravity] applying inverted gravity from effect targetType={} uuid={} pos=({}, {}, {}) dim={} duration={} ambient={} visible={} forcedBefore={} gameTime={}",
entity.getType(), entity.getUUID(), entity.getX(), entity.getY(), entity.getZ(),
entity.level().dimension().location(),
effect != null ? effect.getDuration() : -1,
effect != null && effect.isAmbient(),
effect != null && effect.isVisible(),
AntarchyGravityApi.isGravityForced(entity),
entity.level().getGameTime()
);
}
AntarchyGravityApi.setForcedGravityDirection(entity, AntarchyGravityDirection.UP, EFFECT_TRANSITION); AntarchyGravityApi.setForcedGravityDirection(entity, AntarchyGravityDirection.UP, EFFECT_TRANSITION);
} }
} else { } else {
@@ -0,0 +1,57 @@
package com.craisinlord.antarchy.content.effect;
import com.craisinlord.antarchy.content.AntarchyTags;
import com.craisinlord.antarchy.content.item.RoyalAssailantArmorItem;
import com.craisinlord.antarchy.content.item.RoyalGuardianArmorItem;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.player.Player;
public final class RoyalEffectEligibility {
private RoyalEffectEligibility() {
}
public static boolean isCommanded(MobEffectInstance instance) {
return RoyalEffectHooks.commandedHolder() != null && instance.is(RoyalEffectHooks.commandedHolder());
}
public static boolean isDilated(MobEffectInstance instance) {
return RoyalEffectHooks.dilatedHolder() != null && instance.is(RoyalEffectHooks.dilatedHolder());
}
public static boolean canApplyCommanded(LivingEntity entity) {
return entity instanceof Mob mob
&& !(entity instanceof Player)
&& !entity.getType().is(AntarchyTags.Entities.COMMANDED_BLACKLIST)
&& !(mob.isNoAi())
&& !(entity.getItemBySlot(net.minecraft.world.entity.EquipmentSlot.HEAD).getItem() instanceof RoyalGuardianArmorItem)
&& !(entity.getItemBySlot(net.minecraft.world.entity.EquipmentSlot.CHEST).getItem() instanceof RoyalGuardianArmorItem)
&& !(entity.getItemBySlot(net.minecraft.world.entity.EquipmentSlot.LEGS).getItem() instanceof RoyalGuardianArmorItem)
&& !(entity.getItemBySlot(net.minecraft.world.entity.EquipmentSlot.FEET).getItem() instanceof RoyalGuardianArmorItem);
}
public static boolean canApplyDilated(LivingEntity entity) {
return !entity.getType().is(AntarchyTags.Entities.DILATED_BLACKLIST)
&& !entity.getType().is(AntarchyTags.Entities.TIME_DILATION_IMMUNE)
&& !(entity.getItemBySlot(net.minecraft.world.entity.EquipmentSlot.HEAD).getItem() instanceof RoyalAssailantArmorItem)
&& !(entity.getItemBySlot(net.minecraft.world.entity.EquipmentSlot.CHEST).getItem() instanceof RoyalAssailantArmorItem)
&& !(entity.getItemBySlot(net.minecraft.world.entity.EquipmentSlot.LEGS).getItem() instanceof RoyalAssailantArmorItem)
&& !(entity.getItemBySlot(net.minecraft.world.entity.EquipmentSlot.FEET).getItem() instanceof RoyalAssailantArmorItem);
}
public static boolean canApply(LivingEntity entity, MobEffectInstance instance) {
if (isCommanded(instance)) {
return canApplyCommanded(entity);
}
if (isDilated(instance)) {
return canApplyDilated(entity);
}
return true;
}
public static boolean canUseCommandSource(Entity source) {
return source instanceof Player;
}
}
@@ -0,0 +1,27 @@
package com.craisinlord.antarchy.content.effect;
import com.craisinlord.antarchy.Antarchy;
import net.minecraft.core.Holder;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.effect.MobEffect;
public final class RoyalEffectHooks {
private static final ResourceKey<MobEffect> COMMANDED_KEY = ResourceKey.create(Registries.MOB_EFFECT,
ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "commanded"));
private static final ResourceKey<MobEffect> DILATED_KEY = ResourceKey.create(Registries.MOB_EFFECT,
ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "dilated"));
private RoyalEffectHooks() {
}
public static Holder<MobEffect> commandedHolder() {
return BuiltInRegistries.MOB_EFFECT.getHolder(COMMANDED_KEY).orElse(null);
}
public static Holder<MobEffect> dilatedHolder() {
return BuiltInRegistries.MOB_EFFECT.getHolder(DILATED_KEY).orElse(null);
}
}
@@ -98,6 +98,8 @@ public class BedBugEntity extends Animal implements GeoEntity {
@Nullable @Nullable
private ItemEntity cachedRottenFlesh; private ItemEntity cachedRottenFlesh;
private int nextFoodSearchTick; private int nextFoodSearchTick;
private int ignoredRottenFleshId = -1;
private int ignoredRottenFleshUntilTick;
private int guardNestCooldown; private int guardNestCooldown;
public BedBugEntity(EntityType<? extends BedBugEntity> entityType, Level level) { public BedBugEntity(EntityType<? extends BedBugEntity> entityType, Level level) {
@@ -450,6 +452,24 @@ public class BedBugEntity extends Animal implements GeoEntity {
this.nextFoodSearchTick = 0; this.nextFoodSearchTick = 0;
} }
private void temporarilyIgnoreRottenFlesh(ItemEntity itemEntity) {
this.ignoredRottenFleshId = itemEntity.getId();
this.ignoredRottenFleshUntilTick = this.tickCount + 100;
this.invalidateFoodCache();
}
private boolean isTemporarilyIgnoredFood(ItemEntity itemEntity) {
if (itemEntity.getId() != this.ignoredRottenFleshId) {
return false;
}
if (this.tickCount >= this.ignoredRottenFleshUntilTick) {
this.ignoredRottenFleshId = -1;
this.ignoredRottenFleshUntilTick = 0;
return false;
}
return true;
}
@Nullable @Nullable
private ItemEntity findNearestRottenFlesh() { private ItemEntity findNearestRottenFlesh() {
return this.level().getEntitiesOfClass( return this.level().getEntitiesOfClass(
@@ -457,7 +477,8 @@ public class BedBugEntity extends Animal implements GeoEntity {
this.getBoundingBox().inflate(FOOD_SEARCH_RADIUS, 2.0D, FOOD_SEARCH_RADIUS), this.getBoundingBox().inflate(FOOD_SEARCH_RADIUS, 2.0D, FOOD_SEARCH_RADIUS),
itemEntity -> itemEntity.isAlive() itemEntity -> itemEntity.isAlive()
&& !itemEntity.getItem().isEmpty() && !itemEntity.getItem().isEmpty()
&& itemEntity.getItem().is(Items.ROTTEN_FLESH)) && itemEntity.getItem().is(Items.ROTTEN_FLESH)
&& !this.isTemporarilyIgnoredFood(itemEntity))
.stream() .stream()
.min(Comparator.comparingDouble(this::distanceToSqr)) .min(Comparator.comparingDouble(this::distanceToSqr))
.orElse(null); .orElse(null);
@@ -581,6 +602,7 @@ public class BedBugEntity extends Animal implements GeoEntity {
private ItemEntity targetFood; private ItemEntity targetFood;
private double lastDistanceSqr; private double lastDistanceSqr;
private int stuckTicks; private int stuckTicks;
private int pathRefreshCooldown;
private SeekRottenFleshGoal() { private SeekRottenFleshGoal() {
this.setFlags(EnumSet.of(Flag.MOVE, Flag.LOOK)); this.setFlags(EnumSet.of(Flag.MOVE, Flag.LOOK));
@@ -614,6 +636,7 @@ public class BedBugEntity extends Animal implements GeoEntity {
BedBugEntity.this.setTarget(null); BedBugEntity.this.setTarget(null);
this.lastDistanceSqr = Double.MAX_VALUE; this.lastDistanceSqr = Double.MAX_VALUE;
this.stuckTicks = 0; this.stuckTicks = 0;
this.pathRefreshCooldown = 0;
} }
@Override @Override
@@ -622,6 +645,7 @@ public class BedBugEntity extends Animal implements GeoEntity {
this.targetFood = null; this.targetFood = null;
this.lastDistanceSqr = Double.MAX_VALUE; this.lastDistanceSqr = Double.MAX_VALUE;
this.stuckTicks = 0; this.stuckTicks = 0;
this.pathRefreshCooldown = 0;
} }
@Override @Override
@@ -631,7 +655,14 @@ public class BedBugEntity extends Animal implements GeoEntity {
} }
BedBugEntity.this.getLookControl().setLookAt(this.targetFood, 30.0F, 30.0F); BedBugEntity.this.getLookControl().setLookAt(this.targetFood, 30.0F, 30.0F);
BedBugEntity.this.getNavigation().moveTo(this.targetFood, 1.0D); if (this.pathRefreshCooldown-- <= 0) {
if (!BedBugEntity.this.getNavigation().moveTo(this.targetFood, 1.0D)) {
BedBugEntity.this.temporarilyIgnoreRottenFlesh(this.targetFood);
this.targetFood = null;
return;
}
this.pathRefreshCooldown = 10;
}
double distanceSqr = BedBugEntity.this.distanceToSqr(this.targetFood); double distanceSqr = BedBugEntity.this.distanceToSqr(this.targetFood);
if (distanceSqr < this.lastDistanceSqr - 0.2D) { if (distanceSqr < this.lastDistanceSqr - 0.2D) {
@@ -639,7 +670,7 @@ public class BedBugEntity extends Animal implements GeoEntity {
this.stuckTicks = 0; this.stuckTicks = 0;
} else if (BedBugEntity.this.getNavigation().isDone() && distanceSqr > 4.0D) { } else if (BedBugEntity.this.getNavigation().isDone() && distanceSqr > 4.0D) {
if (++this.stuckTicks >= 20) { if (++this.stuckTicks >= 20) {
BedBugEntity.this.invalidateFoodCache(); BedBugEntity.this.temporarilyIgnoreRottenFlesh(this.targetFood);
this.targetFood = null; this.targetFood = null;
return; return;
} }
@@ -29,12 +29,14 @@ import org.jetbrains.annotations.Nullable;
import java.util.List; import java.util.List;
public class DiamondMinecartEntity extends Minecart { public class DiamondMinecartEntity extends Minecart {
private static final long INPUT_TIMEOUT_TICKS = 5L;
private static final EntityDataAccessor<Float> SYNCED_SPEED = private static final EntityDataAccessor<Float> SYNCED_SPEED =
SynchedEntityData.defineId(DiamondMinecartEntity.class, EntityDataSerializers.FLOAT); SynchedEntityData.defineId(DiamondMinecartEntity.class, EntityDataSerializers.FLOAT);
private static final EntityDataAccessor<Integer> SYNCED_FACING = private static final EntityDataAccessor<Integer> SYNCED_FACING =
SynchedEntityData.defineId(DiamondMinecartEntity.class, EntityDataSerializers.INT); SynchedEntityData.defineId(DiamondMinecartEntity.class, EntityDataSerializers.INT);
private byte inputFlags = 0; private byte inputFlags = 0;
private long lastInputGameTime = Long.MIN_VALUE;
private float currentSpeed = 0.0F; private float currentSpeed = 0.0F;
private Direction facingDir = Direction.SOUTH; private Direction facingDir = Direction.SOUTH;
private BlockPos lastBlockPos = null; private BlockPos lastBlockPos = null;
@@ -109,19 +111,38 @@ public class DiamondMinecartEntity extends Minecart {
@Override @Override
protected double getMaxSpeed() { protected double getMaxSpeed() {
return 0.4D; return AntarchySettings.diamondMinecartEnabled()
? AntarchySettings.diamondMinecartMaxSpeed()
: 0.4D;
} }
public void onInputReceived(byte flags) { public void onInputReceived(byte flags) {
this.inputFlags = flags; this.inputFlags = flags;
this.lastInputGameTime = this.level().getGameTime();
} }
@Override @Override
public void tick() { public void tick() {
super.tick(); super.tick();
if (!this.level().isClientSide) {
this.serverTick();
}
} }
private void serverTick() { private void serverTick() {
if (!AntarchySettings.diamondMinecartEnabled()) {
this.inputFlags = 0;
this.lastInputGameTime = Long.MIN_VALUE;
this.currentSpeed = 0.0F;
this.entityData.set(SYNCED_SPEED, 0.0F);
this.setDeltaMovement(0.0D, this.getDeltaMovement().y, 0.0D);
return;
}
if (this.level().getGameTime() - this.lastInputGameTime > INPUT_TIMEOUT_TICKS) {
this.inputFlags = 0;
}
Player rider = this.getRidingPlayer(); Player rider = this.getRidingPlayer();
boolean hasRider = rider != null; boolean hasRider = rider != null;
@@ -176,6 +197,17 @@ public class DiamondMinecartEntity extends Minecart {
if (AntarchySettings.diamondMinecartMobDamageEnabled() && this.currentSpeed > 0.15F && hasRider) { if (AntarchySettings.diamondMinecartMobDamageEnabled() && this.currentSpeed > 0.15F && hasRider) {
this.applyMobCollisionDamage(); this.applyMobCollisionDamage();
} }
this.applyControlledVelocity();
}
private void applyControlledVelocity() {
double speed = this.currentSpeed;
this.setDeltaMovement(
this.facingDir.getStepX() * speed,
this.getDeltaMovement().y,
this.facingDir.getStepZ() * speed
);
} }
private void onEnteredNewBlock(BlockPos currentPos, @Nullable Player rider) { private void onEnteredNewBlock(BlockPos currentPos, @Nullable Player rider) {
@@ -1,8 +1,11 @@
package com.craisinlord.antarchy.content.entity; package com.craisinlord.antarchy.content.entity;
import com.craisinlord.antarchy.config.AntarchySettings; import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.content.entity.royal.QueenEntity; import com.craisinlord.antarchy.content.entity.royal.QueenEntity;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi; import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityDirection;
import com.craisinlord.antarchy.content.worldgen.thoraxis.ThoraxisUndersideManager;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
@@ -89,6 +92,11 @@ public class ManticoreEntity extends Monster implements GeoEntity {
this.xpReward = 12; this.xpReward = 12;
} }
@Override
public boolean fireImmune() {
return true;
}
public static AttributeSupplier.Builder createAttributes() { public static AttributeSupplier.Builder createAttributes() {
return Mob.createMobAttributes() return Mob.createMobAttributes()
.add(Attributes.MAX_HEALTH, AntarchySettings.manticoreHealth()) .add(Attributes.MAX_HEALTH, AntarchySettings.manticoreHealth())
@@ -206,6 +214,8 @@ public class ManticoreEntity extends Monster implements GeoEntity {
return; return;
} }
this.tickQueenSummonInversionRule();
boolean flying = !this.onGround(); boolean flying = !this.onGround();
if (flying != this.wasFlyingLastTick) { if (flying != this.wasFlyingLastTick) {
this.triggerAnim("controller", flying ? "fly_start" : "fly_land"); this.triggerAnim("controller", flying ? "fly_start" : "fly_land");
@@ -367,6 +377,24 @@ public class ManticoreEntity extends Monster implements GeoEntity {
return AntarchyGravityApi.isGravityInverted(entity) ? new Vec3(0.0D, -1.0D, 0.0D) : new Vec3(0.0D, 1.0D, 0.0D); return AntarchyGravityApi.isGravityInverted(entity) ? new Vec3(0.0D, -1.0D, 0.0D) : new Vec3(0.0D, 1.0D, 0.0D);
} }
private void tickQueenSummonInversionRule() {
if (!this.isQueenSummoned() || this.canUseQueenSummonInversion()) {
return;
}
if (this.hasEffect(AntarchyObjects.INVERTED_EFFECT.get())) {
this.removeEffect(AntarchyObjects.INVERTED_EFFECT.get());
}
if (AntarchyGravityApi.isGravityInverted(this) && AntarchyGravityApi.isGravityForced(this)) {
AntarchyGravityApi.setGravityDirection(this, AntarchyGravityDirection.DOWN, false);
}
this.setNoGravity(false);
}
private boolean canUseQueenSummonInversion() {
return ThoraxisUndersideManager.isThoraxis(this.level())
&& this.getY() < ThoraxisUndersideManager.GRAVITY_FLIP_Y + 4;
}
void performSting(LivingEntity enemy) { void performSting(LivingEntity enemy) {
this.swing(InteractionHand.MAIN_HAND); this.swing(InteractionHand.MAIN_HAND);
if (enemy.hurt(this.damageSources().mobAttack(this), (float) this.getAttributeValue(Attributes.ATTACK_DAMAGE))) { if (enemy.hurt(this.damageSources().mobAttack(this), (float) this.getAttributeValue(Attributes.ATTACK_DAMAGE))) {
@@ -394,6 +422,14 @@ public class ManticoreEntity extends Monster implements GeoEntity {
return false; return false;
} }
@Override
public boolean canBeAffected(MobEffectInstance effectInstance) {
if (this.isQueenSummoned() && effectInstance.is(AntarchyObjects.INVERTED_EFFECT.get())) {
return this.canUseQueenSummonInversion();
}
return super.canBeAffected(effectInstance);
}
@Override @Override
public boolean isInWall() { public boolean isInWall() {
return false; return false;
@@ -75,6 +75,7 @@ import software.bernie.geckolib.animation.RawAnimation;
import software.bernie.geckolib.util.GeckoLibUtil; import software.bernie.geckolib.util.GeckoLibUtil;
public class ReverieEntity extends PathfinderMob implements FlyingAnimal, GeoEntity { public class ReverieEntity extends PathfinderMob implements FlyingAnimal, GeoEntity {
private static final int DAMAGE_REACTION_DURATION_TICKS = 40;
private static final EntityDataAccessor<Integer> MOOD = private static final EntityDataAccessor<Integer> MOOD =
SynchedEntityData.defineId(ReverieEntity.class, EntityDataSerializers.INT); SynchedEntityData.defineId(ReverieEntity.class, EntityDataSerializers.INT);
private static final EntityDataAccessor<Integer> ANIMATION_STATE = private static final EntityDataAccessor<Integer> ANIMATION_STATE =
@@ -318,7 +319,7 @@ public class ReverieEntity extends PathfinderMob implements FlyingAnimal, GeoEnt
this.releaseFocus(); this.releaseFocus();
this.clearNoticePlayer(); this.clearNoticePlayer();
this.ambientTarget = null; this.ambientTarget = null;
this.damageReactionTicks = 0; this.damageReactionTicks = DAMAGE_REACTION_DURATION_TICKS;
this.inversionReactionTicks = 0; this.inversionReactionTicks = 0;
this.transitionToMood(Mood.WHITE); this.transitionToMood(Mood.WHITE);
} }
@@ -38,6 +38,9 @@ import net.minecraft.world.entity.monster.Monster;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.level.ServerLevelAccessor; import net.minecraft.world.level.ServerLevelAccessor;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import software.bernie.geckolib.animatable.GeoEntity; import software.bernie.geckolib.animatable.GeoEntity;
import software.bernie.geckolib.animatable.instance.AnimatableInstanceCache; import software.bernie.geckolib.animatable.instance.AnimatableInstanceCache;
import software.bernie.geckolib.animation.AnimatableManager; import software.bernie.geckolib.animation.AnimatableManager;
@@ -51,6 +54,10 @@ import org.jetbrains.annotations.Nullable;
import java.util.UUID; import java.util.UUID;
public class ScorpionEntity extends Monster implements GeoEntity { public class ScorpionEntity extends Monster implements GeoEntity {
private static final ResourceKey<net.minecraft.world.level.biome.Biome> DREAM_DUNES = ResourceKey.create(
Registries.BIOME,
ResourceLocation.fromNamespaceAndPath("antarchy", "dream_dunes")
);
private static final byte ATTACK_ANIM_EVENT = 4; private static final byte ATTACK_ANIM_EVENT = 4;
private static final EntityDataAccessor<Integer> TEXTURE_VARIANT = private static final EntityDataAccessor<Integer> TEXTURE_VARIANT =
SynchedEntityData.defineId(ScorpionEntity.class, EntityDataSerializers.INT); SynchedEntityData.defineId(ScorpionEntity.class, EntityDataSerializers.INT);
@@ -81,6 +88,11 @@ public class ScorpionEntity extends Monster implements GeoEntity {
this.xpReward = 5; this.xpReward = 5;
} }
@Override
public boolean fireImmune() {
return true;
}
public static AttributeSupplier.Builder createAttributes() { public static AttributeSupplier.Builder createAttributes() {
return Mob.createMobAttributes() return Mob.createMobAttributes()
.add(Attributes.MAX_HEALTH, AntarchySettings.scorpionHealth()) .add(Attributes.MAX_HEALTH, AntarchySettings.scorpionHealth())
@@ -98,6 +110,12 @@ public class ScorpionEntity extends Monster implements GeoEntity {
|| spawnReason == MobSpawnType.COMMAND) { || spawnReason == MobSpawnType.COMMAND) {
return true; return true;
} }
// The Dream Dunes biome is the entire underside below Y=0. Its higher
// spawn weight is therefore targeted there without doubling the small
// surface-side Dream Dunes band as well.
if (pos.getY() >= 0 && level.getBiome(pos).is(DREAM_DUNES) && random.nextBoolean()) {
return false;
}
if (ThoraxisUndersideManager.shouldSpawnInvertedOnDreamSand(level, pos)) { if (ThoraxisUndersideManager.shouldSpawnInvertedOnDreamSand(level, pos)) {
return level.getDifficulty() != Difficulty.PEACEFUL; return level.getDifficulty() != Difficulty.PEACEFUL;
} }
@@ -182,7 +200,8 @@ public class ScorpionEntity extends Monster implements GeoEntity {
this.getLookControl().setLookAt(this.attackTarget, 30.0F, 30.0F); this.getLookControl().setLookAt(this.attackTarget, 30.0F, 30.0F);
if (!this.attackDamageApplied if (!this.attackDamageApplied
&& this.attackAnimTicks == ATTACK_HIT_TICK && this.attackAnimTicks > 0
&& this.attackAnimTicks <= ATTACK_HIT_TICK
&& this.distanceToSqr(this.attackTarget) <= this.getAttackReachSqr(this.attackTarget)) { && this.distanceToSqr(this.attackTarget) <= this.getAttackReachSqr(this.attackTarget)) {
this.attackDamageApplied = true; this.attackDamageApplied = true;
this.doHurtTarget(this.attackTarget); this.doHurtTarget(this.attackTarget);
@@ -328,6 +347,10 @@ public class ScorpionEntity extends Monster implements GeoEntity {
if (ScorpionEntity.this.attackTarget != null) { if (ScorpionEntity.this.attackTarget != null) {
ScorpionEntity.this.getLookControl().setLookAt(ScorpionEntity.this.attackTarget, 30.0F, 30.0F); ScorpionEntity.this.getLookControl().setLookAt(ScorpionEntity.this.attackTarget, 30.0F, 30.0F);
} }
if (ScorpionEntity.this.isAttackLocked()) {
ScorpionEntity.this.getNavigation().stop();
return;
}
super.tick(); super.tick();
} }
@@ -341,8 +364,10 @@ public class ScorpionEntity extends Monster implements GeoEntity {
if (ScorpionEntity.this.isAttackLocked()) { if (ScorpionEntity.this.isAttackLocked()) {
return; return;
} }
if (this.canPerformAttack(enemy) && this.isTimeToAttack() && ScorpionEntity.this.attackCooldownTicks <= 0) { // Inverted melee tick handling replaces vanilla MeleeAttackGoal.tick,
this.resetAttackCooldown(); // so vanilla's internal attack timer is not advanced. The Scorpion's
// own cooldown already covers its wind-up and hit timing.
if (this.canPerformAttack(enemy) && ScorpionEntity.this.attackCooldownTicks <= 0) {
ScorpionEntity.this.beginAttack(enemy); ScorpionEntity.this.beginAttack(enemy);
return; return;
} }
@@ -15,6 +15,8 @@ import net.minecraft.world.entity.MobSpawnType;
import net.minecraft.world.entity.ai.attributes.AttributeSupplier; import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
import net.minecraft.world.entity.ai.attributes.Attributes; import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.ai.goal.FloatGoal; import net.minecraft.world.entity.ai.goal.FloatGoal;
import net.minecraft.world.entity.ai.goal.BreedGoal;
import net.minecraft.world.entity.ai.goal.TemptGoal;
import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal; import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal;
import net.minecraft.world.entity.ai.goal.PanicGoal; import net.minecraft.world.entity.ai.goal.PanicGoal;
import net.minecraft.world.entity.ai.goal.RandomLookAroundGoal; import net.minecraft.world.entity.ai.goal.RandomLookAroundGoal;
@@ -23,6 +25,8 @@ import net.minecraft.world.entity.animal.Animal;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
import net.minecraft.sounds.SoundEvent; import net.minecraft.sounds.SoundEvent;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.level.ServerLevelAccessor; import net.minecraft.world.level.ServerLevelAccessor;
import net.minecraft.world.Difficulty; import net.minecraft.world.Difficulty;
@@ -86,9 +90,11 @@ public class StinkBugEntity extends Animal implements GeoEntity {
protected void registerGoals() { protected void registerGoals() {
this.goalSelector.addGoal(0, new FloatGoal(this)); this.goalSelector.addGoal(0, new FloatGoal(this));
this.goalSelector.addGoal(1, new PanicGoal(this, 1.4D)); this.goalSelector.addGoal(1, new PanicGoal(this, 1.4D));
this.goalSelector.addGoal(2, new WaterAvoidingRandomStrollGoal(this, 0.9D)); this.goalSelector.addGoal(2, new TemptGoal(this, 1.0D, Ingredient.of(Items.ROTTEN_FLESH), false));
this.goalSelector.addGoal(3, new LookAtPlayerGoal(this, Player.class, 6.0F)); this.goalSelector.addGoal(3, new BreedGoal(this, 0.9D));
this.goalSelector.addGoal(4, new RandomLookAroundGoal(this)); this.goalSelector.addGoal(4, new WaterAvoidingRandomStrollGoal(this, 0.9D));
this.goalSelector.addGoal(5, new LookAtPlayerGoal(this, Player.class, 6.0F));
this.goalSelector.addGoal(6, new RandomLookAroundGoal(this));
} }
@Override @Override
@@ -118,7 +124,7 @@ public class StinkBugEntity extends Animal implements GeoEntity {
@Override @Override
public boolean isFood(ItemStack stack) { public boolean isFood(ItemStack stack) {
return false; return stack.is(Items.ROTTEN_FLESH);
} }
@Override @Override
@@ -313,7 +313,10 @@ public final class AntTeleportHelper {
return new int[]{100, 200}; return new int[]{100, 200};
} }
if (dim == AntarchySettings.brownAntDestinationDimension()) { if (dim == AntarchySettings.brownAntDestinationDimension()) {
return new int[]{75, 120}; return new int[]{60, 240};
}
if (dim == AntarchySettings.redAntDestinationDimension()) {
return new int[]{0, 220};
} }
return null; return null;
} }
@@ -328,7 +331,7 @@ public final class AntTeleportHelper {
} }
int x = preferredPos.getX() + xOff; int x = preferredPos.getX() + xOff;
int z = preferredPos.getZ() + zOff; int z = preferredPos.getZ() + zOff;
for (int y = maxY; y >= minY + 1; y--) { for (int y = minY + 1; y <= maxY; y++) {
Vec3 safePos = tryFindSafeDismount(player, destination, new BlockPos(x, y, z)); Vec3 safePos = tryFindSafeDismount(player, destination, new BlockPos(x, y, z));
if (safePos != null && safePos.y >= minY && safePos.y <= maxY) { if (safePos != null && safePos.y >= minY && safePos.y <= maxY) {
return safePos; return safePos;
@@ -1035,7 +1035,8 @@ public class KrakenEntity extends Monster implements GeoEntity, MultipartEntityO
for (int i = 0; i < strikeBolts; i++) { for (int i = 0; i < strikeBolts; i++) {
this.spawnVisualLightning(serverLevel, this.getTargetStormStrikePos(target)); this.spawnVisualLightning(serverLevel, this.getTargetStormStrikePos(target));
} }
target.hurt(AntarchyDamageSources.krakenLightning(serverLevel, this), this.isPhaseTwo() ? 12.0F : 8.0F); target.hurt(AntarchyDamageSources.krakenLightning(serverLevel, this),
this.isPhaseTwo() ? 12.0F : AntarchySettings.krakenLightningDamagePhaseOne());
} }
} else { } else {
this.lightningStrikeCooldown = this.isPhaseTwo() ? 30 : 60; this.lightningStrikeCooldown = this.isPhaseTwo() ? 30 : 60;
@@ -1,5 +1,6 @@
package com.craisinlord.antarchy.content.entity.portal; package com.craisinlord.antarchy.content.entity.portal;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.config.AntarchySettings; import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.AntarchyObjects; import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.content.AntarchySoundEvents; import com.craisinlord.antarchy.content.AntarchySoundEvents;
@@ -176,6 +177,13 @@ public class DimensionalTearEntity extends Entity implements GeoEntity {
DimensionalTearEntity linked = this.findLinkedTear(); DimensionalTearEntity linked = this.findLinkedTear();
if (linked == null || !linked.isAlive()) { if (linked == null || !linked.isAlive()) {
Antarchy.LOGGER.warn(
"[antarchy-dimensional-tear] discarding unlinked tear uuid={} pos=({}, {}, {}) dim={} linkedTearId={} fallback=({}, {}, {}) ageTicks={} lifetimeTicks={}",
this.getUUID(), this.getX(), this.getY(), this.getZ(), this.level().dimension().location(),
this.linkedTearId,
this.linkedFallbackPos.x, this.linkedFallbackPos.y, this.linkedFallbackPos.z,
this.ageTicks, this.lifetimeTicks
);
this.discard(); this.discard();
return; return;
} }
@@ -264,7 +272,7 @@ public class DimensionalTearEntity extends Entity implements GeoEntity {
manticore.setTarget(target); manticore.setTarget(target);
} }
manticore.setDeltaMovement(this.getViewVector(1.0F).scale(0.2D).add(0.0D, 0.08D, 0.0D)); manticore.setDeltaMovement(this.getViewVector(1.0F).scale(0.2D).add(0.0D, 0.08D, 0.0D));
manticore.setNoGravity(true); manticore.setNoGravity(false);
level.addFreshEntity(manticore); level.addFreshEntity(manticore);
} }
} }
@@ -348,6 +356,7 @@ public class DimensionalTearEntity extends Entity implements GeoEntity {
private void teleportEntity(Entity entity, DimensionalTearEntity destination) { private void teleportEntity(Entity entity, DimensionalTearEntity destination) {
Vec3 motion = entity.getDeltaMovement(); Vec3 motion = entity.getDeltaMovement();
Vec3 startPos = entity.position();
Vec3 exitPos = destination.exitPosition(); Vec3 exitPos = destination.exitPosition();
TELEPORT_COOLDOWNS.put(entity.getUUID(), this.level().getGameTime() + TELEPORT_COOLDOWN_TICKS); TELEPORT_COOLDOWNS.put(entity.getUUID(), this.level().getGameTime() + TELEPORT_COOLDOWN_TICKS);
@@ -360,7 +369,15 @@ public class DimensionalTearEntity extends Entity implements GeoEntity {
entity.setDeltaMovement(motion); entity.setDeltaMovement(motion);
entity.hasImpulse = true; entity.hasImpulse = true;
if (entity instanceof LivingEntity living) { if (entity instanceof LivingEntity living) {
living.addEffect(new MobEffectInstance(AntarchyObjects.INVERTED_EFFECT.get(), AntarchySettings.dimensionalTearInvertedDurationTicks(), 0)); int invertedDuration = AntarchySettings.dimensionalTearInvertedDurationTicks();
Antarchy.LOGGER.info(
"[antarchy-dimensional-tear] applying inverted effect targetType={} uuid={} fromTear={} toTear={} startPos=({}, {}, {}) exitPos=({}, {}, {}) dim={} durationTicks={}",
living.getType(), living.getUUID(), this.getUUID(), destination.getUUID(),
startPos.x, startPos.y, startPos.z,
exitPos.x, exitPos.y, exitPos.z,
this.level().dimension().location(), invertedDuration
);
living.addEffect(new MobEffectInstance(AntarchyObjects.INVERTED_EFFECT.get(), invertedDuration, 0));
} }
this.level().playSound(null, this.blockPosition(), SoundEvents.ENDERMAN_TELEPORT, SoundSource.NEUTRAL, 0.7F, 0.65F + this.random.nextFloat() * 0.2F); this.level().playSound(null, this.blockPosition(), SoundEvents.ENDERMAN_TELEPORT, SoundSource.NEUTRAL, 0.7F, 0.65F + this.random.nextFloat() * 0.2F);
} }
@@ -516,6 +533,16 @@ public class DimensionalTearEntity extends Entity implements GeoEntity {
this.queenManticoreCount = tag.getInt("QueenManticoreCount"); this.queenManticoreCount = tag.getInt("QueenManticoreCount");
this.queenManticoreSpawned = tag.getBoolean("QueenManticoreSpawned"); this.queenManticoreSpawned = tag.getBoolean("QueenManticoreSpawned");
} }
if (!this.level().isClientSide) {
Antarchy.LOGGER.info(
"[antarchy-dimensional-tear] loaded tear uuid={} pos=({}, {}, {}) dim={} linkedTearId={} fallback=({}, {}, {}) ageTicks={} lifetimeTicks={} eventTicks={} nextEventTicks={} pendingEvent={} queenOwner={} queenManticoreCount={} queenManticoreSpawned={}",
this.getUUID(), this.getX(), this.getY(), this.getZ(), this.level().dimension().location(),
this.linkedTearId,
this.linkedFallbackPos.x, this.linkedFallbackPos.y, this.linkedFallbackPos.z,
this.ageTicks, this.lifetimeTicks, this.eventTicks, this.nextEventTicks, this.pendingEvent,
this.queenOwnerId, this.queenManticoreCount, this.queenManticoreSpawned
);
}
} }
@Override @Override
@@ -1,6 +1,7 @@
package com.craisinlord.antarchy.content.entity.royal; package com.craisinlord.antarchy.content.entity.royal;
import com.craisinlord.antarchy.config.AntarchySettings; import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.content.AntarchySoundEvents; import com.craisinlord.antarchy.content.AntarchySoundEvents;
import net.minecraft.sounds.SoundEvent; import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents; import net.minecraft.sounds.SoundEvents;
@@ -35,7 +36,6 @@ import org.jetbrains.annotations.Nullable;
import com.craisinlord.antarchy.content.entity.royal.beam.RoyalBeamSettings; 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.RoyalBeamTerrainMode;
import com.craisinlord.antarchy.content.entity.royal.beam.RoyalBeamElement; import com.craisinlord.antarchy.content.entity.royal.beam.RoyalBeamElement;
import com.craisinlord.antarchy.content.entity.royal.decree.AdvanceDecree;
import com.craisinlord.antarchy.content.entity.royal.decree.CloseQuartersDecree; import com.craisinlord.antarchy.content.entity.royal.decree.CloseQuartersDecree;
import com.craisinlord.antarchy.content.entity.royal.decree.ComeNoCloserDecree; import com.craisinlord.antarchy.content.entity.royal.decree.ComeNoCloserDecree;
import com.craisinlord.antarchy.content.entity.royal.decree.DoNotRunDecree; import com.craisinlord.antarchy.content.entity.royal.decree.DoNotRunDecree;
@@ -50,6 +50,7 @@ import com.craisinlord.antarchy.content.entity.royal.decree.ShowNoMercyDecree;
import com.craisinlord.antarchy.content.entity.royal.decree.SkyIsMineDecree; import com.craisinlord.antarchy.content.entity.royal.decree.SkyIsMineDecree;
import com.craisinlord.antarchy.content.entity.royal.decree.StandTallDecree; import com.craisinlord.antarchy.content.entity.royal.decree.StandTallDecree;
import com.craisinlord.antarchy.content.entity.royal.decree.StandYourGroundDecree; import com.craisinlord.antarchy.content.entity.royal.decree.StandYourGroundDecree;
import com.craisinlord.antarchy.content.entity.royal.attack.RoyalAttackLane;
import java.util.EnumMap; import java.util.EnumMap;
import java.util.List; import java.util.List;
import java.util.HashMap; import java.util.HashMap;
@@ -79,9 +80,10 @@ public class KingEntity extends RoyalBossEntity {
private int decreeCooldownTicks; private int decreeCooldownTicks;
private int activeDecreeTicks; private int activeDecreeTicks;
private int stompCooldownTicks; private int stompCooldownTicks;
private int stompWindupTicks;
private int wingGustCooldownTicks; private int wingGustCooldownTicks;
private int wingGustWindupTicks; private int fireballCooldownTicks = 80;
private int iceballCooldownTicks = 120;
private int iceSpikeCooldownTicks = 160;
private boolean decreeRetreatPressure; private boolean decreeRetreatPressure;
private final Map<UUID, Long> judgmentCooldowns = new HashMap<>(); private final Map<UUID, Long> judgmentCooldowns = new HashMap<>();
private final Map<UUID, Long> lastPlayerDamageTime = new HashMap<>(); private final Map<UUID, Long> lastPlayerDamageTime = new HashMap<>();
@@ -91,9 +93,9 @@ public class KingEntity extends RoyalBossEntity {
private RoyalDecree activeDecree; private RoyalDecree activeDecree;
private static final List<RoyalDecree> DECREES = List.of( private static final List<RoyalDecree> DECREES = List.of(
new SkyIsMineDecree(), new DoNotRunDecree(), new KneelDecree(), new CloseQuartersDecree(), new SkyIsMineDecree(), new DoNotRunDecree(), new KneelDecree(), new CloseQuartersDecree(),
new ComeNoCloserDecree(), new FightMeCowardDecree(), new NoRespiteDecree(), new NoRetreatDecree(), new ComeNoCloserDecree(), new FightMeCowardDecree(), new NoRespiteDecree(),
new KeepYourDistanceDecree(), new StandTallDecree(), new StandYourGroundDecree(), new StandTallDecree(), new StandYourGroundDecree(),
new HandsOffTheCrownDecree(), new AdvanceDecree(), new ShowNoMercyDecree()); new HandsOffTheCrownDecree(), new ShowNoMercyDecree());
public KingEntity(EntityType<? extends KingEntity> entityType, Level level) { public KingEntity(EntityType<? extends KingEntity> entityType, Level level) {
super(entityType, level); super(entityType, level);
@@ -133,6 +135,11 @@ public class KingEntity extends RoyalBossEntity {
return AntarchySoundEvents.KING_DEATH.get(); return AntarchySoundEvents.KING_DEATH.get();
} }
@Override
protected SoundEvent royalFlyLoopSound() {
return AntarchySoundEvents.KING_FLY_LOOP.get();
}
@Override @Override
protected SoundEvent royalBiteSound() { protected SoundEvent royalBiteSound() {
return AntarchySoundEvents.KING_BITE.get(); return AntarchySoundEvents.KING_BITE.get();
@@ -160,7 +167,7 @@ public class KingEntity extends RoyalBossEntity {
@Override @Override
protected RoyalBeamSettings royalBeamSettings() { protected RoyalBeamSettings royalBeamSettings() {
return new RoyalBeamSettings(AntarchySettings.kingBeamRange(), AntarchySettings.kingBeamTracking(), 7.5D, return new RoyalBeamSettings(AntarchySettings.kingBeamRange(), AntarchySettings.kingBeamTracking(), 7.5D,
AntarchySettings.kingBeamDurationTicks(), AntarchySettings.kingBeamCooldownTicks(), 6.0F, 6.0F, AntarchySettings.kingBeamDurationTicks(), AntarchySettings.kingBeamTravelTicks(), AntarchySettings.kingBeamCooldownTicks(), 6.0F, 6.0F,
(float) AntarchySettings.kingBeamDamage(), 1.0F, 3, 100.0D, (float) AntarchySettings.kingBeamDamage(), 1.0F, 3, 100.0D,
(float) AntarchySettings.kingBeamTerrainRadius(), 4.0F, AntarchySettings.kingBeamTerrainCap(), (float) AntarchySettings.kingBeamTerrainRadius(), 4.0F, AntarchySettings.kingBeamTerrainCap(),
1.0F, 0.08F, 15.0F, true, true); 1.0F, 0.08F, 15.0F, true, true);
@@ -194,13 +201,13 @@ public class KingEntity extends RoyalBossEntity {
return; return;
} }
var particle = switch (head.slot()) { var particle = switch (head.slot()) {
case LEFT -> ParticleTypes.FLAME; case LEFT -> null;
case CENTER -> ParticleTypes.ELECTRIC_SPARK; case CENTER -> ParticleTypes.ELECTRIC_SPARK;
case RIGHT -> ParticleTypes.SNOWFLAKE; case RIGHT -> ParticleTypes.SNOWFLAKE;
}; };
Vec3 direction = end.subtract(start); Vec3 direction = end.subtract(start);
double distance = direction.length(); double distance = direction.length();
if (distance > 0.001D) { if (particle != null && distance > 0.001D) {
direction = direction.scale(1.0D / distance); direction = direction.scale(1.0D / distance);
for (double walked = 0.0D; walked <= distance; walked += 2.5D) { for (double walked = 0.0D; walked <= distance; walked += 2.5D) {
Vec3 point = start.add(direction.scale(walked)); Vec3 point = start.add(direction.scale(walked));
@@ -311,7 +318,7 @@ public class KingEntity extends RoyalBossEntity {
} }
if (this.patrolCooldownTicks-- <= 0) { if (this.patrolCooldownTicks-- <= 0) {
this.patrolCooldownTicks = 100 + this.random.nextInt(100); this.patrolCooldownTicks = 100 + this.random.nextInt(100);
this.playSound(AntarchySoundEvents.KING_WING_FLAP.get(), 2.0F, 0.9F + this.random.nextFloat() * 0.15F); this.playRoyalSound(AntarchySoundEvents.KING_WING_FLAP.get(), 0.9F + this.random.nextFloat() * 0.15F);
double angle = this.random.nextDouble() * Math.PI * 2.0D; double angle = this.random.nextDouble() * Math.PI * 2.0D;
double radius = 12.0D + this.random.nextDouble() * 18.0D; double radius = 12.0D + this.random.nextDouble() * 18.0D;
double px = this.patrolCenter.x + Math.cos(angle) * radius; double px = this.patrolCenter.x + Math.cos(angle) * radius;
@@ -347,37 +354,41 @@ public class KingEntity extends RoyalBossEntity {
} }
private void tickKingWholeBody(ServerLevel level, LivingEntity target) { private void tickKingWholeBody(ServerLevel level, LivingEntity target) {
if (this.stompCooldownTicks > 0) this.stompCooldownTicks--;
if (this.wingGustCooldownTicks > 0) this.wingGustCooldownTicks--;
boolean skyIsMine = this.activeDecree instanceof SkyIsMineDecree; boolean skyIsMine = this.activeDecree instanceof SkyIsMineDecree;
boolean forceFar = this.decreeRetreatPressure boolean forceFar = this.decreeRetreatPressure
|| this.activeDecree instanceof ComeNoCloserDecree || this.activeDecree instanceof ComeNoCloserDecree
|| this.activeDecree instanceof KeepYourDistanceDecree; || this.activeDecree instanceof KeepYourDistanceDecree;
if (this.stompWindupTicks > 0) { {
if (--this.stompWindupTicks == 0) {
this.performStomp(level, target);
}
} else if (this.wingGustWindupTicks > 0) {
if (--this.wingGustWindupTicks == 0) {
this.performWingGust(level, target);
}
} else {
double distance = target.distanceTo(this); double distance = target.distanceTo(this);
if (this.stompCooldownTicks <= 0 && (distance <= STOMP_RADIUS || skyIsMine)) { if (this.attackScheduler.ready("stomp", RoyalAttackLane.BODY)
this.stompCooldownTicks = skyIsMine ? STOMP_COOLDOWN_TICKS / 2 : STOMP_COOLDOWN_TICKS; && (distance <= STOMP_RADIUS || skyIsMine)
this.stompWindupTicks = STOMP_WINDUP_TICKS; && this.beginRoyalAttack("stomp", RoyalAttackLane.BODY,
this.triggerAnim("body_action", "stomp"); skyIsMine ? STOMP_COOLDOWN_TICKS / 2 : STOMP_COOLDOWN_TICKS,
this.playSound(AntarchySoundEvents.KING_ROAR.get(), 3.0F, 0.7F); 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");
KingEntity.this.playRoyalSound(AntarchySoundEvents.KING_ROAR.get(), 0.7F);
}
@Override public void onActive(int elapsedTicks) {
KingEntity.this.performStomp(level, target);
}
})) {
if (skyIsMine) { if (skyIsMine) {
this.getMoveControl().setWantedPosition(this.getX(), target.getY(), this.getZ(), 1.2D); this.getMoveControl().setWantedPosition(this.getX(), target.getY(), this.getZ(), 1.2D);
} }
} else if (this.wingGustCooldownTicks <= 0 && distance <= WING_GUST_RADIUS && (forceFar || this.random.nextInt(3) == 0)) { } else if (this.attackScheduler.ready("wing_gust", RoyalAttackLane.BODY)
this.wingGustCooldownTicks = WING_GUST_COOLDOWN_TICKS; && distance <= WING_GUST_RADIUS && (forceFar || this.random.nextInt(3) == 0)
this.wingGustWindupTicks = WING_GUST_WINDUP_TICKS; && this.beginRoyalAttack("wing_gust", RoyalAttackLane.BODY, WING_GUST_COOLDOWN_TICKS,
this.triggerAnim("body_action", "wing_gust"); WING_GUST_WINDUP_TICKS, 1, 16, new com.craisinlord.antarchy.content.entity.royal.attack.RoyalAttackScheduler.Action() {
this.playSound(AntarchySoundEvents.KING_WING_FLAP.get(), 3.0F, 0.85F); @Override public void onStart() {
KingEntity.this.triggerAnim("body_action", "wing_gust");
KingEntity.this.playRoyalSound(AntarchySoundEvents.KING_WING_FLAP.get(), 0.85F);
}
@Override public void onActive(int elapsedTicks) {
KingEntity.this.performWingGust(level, target);
}
})) {
} }
} }
@@ -388,6 +399,101 @@ public class KingEntity extends RoyalBossEntity {
double ay = this.groundYBelow(ax, az) + FLYING_PREFERRED_HOVER + 6.0D; double ay = this.groundYBelow(ax, az) + FLYING_PREFERRED_HOVER + 6.0D;
this.getMoveControl().setWantedPosition(ax, ay, az, 1.3D); this.getMoveControl().setWantedPosition(ax, ay, az, 1.3D);
} }
this.tickKingElementalProjectiles(level, target);
}
private void tickKingElementalProjectiles(ServerLevel level, LivingEntity target) {
if (this.fireballCooldownTicks > 0) this.fireballCooldownTicks--;
if (this.iceballCooldownTicks > 0) this.iceballCooldownTicks--;
if (this.iceSpikeCooldownTicks > 0) this.iceSpikeCooldownTicks--;
double distance = this.distanceTo(target);
String[] candidates = {"fireball", "iceball", "ice_spikes"};
String selected = this.attackScheduler.chooseWeighted(candidates, id -> {
boolean ready = switch (id) {
case "fireball" -> distance >= 14.0D && distance <= 48.0D && this.fireballCooldownTicks <= 0
&& this.royalHead(RoyalHead.Slot.LEFT).readyToAttack();
case "iceball" -> distance >= 10.0D && distance <= 42.0D && this.iceballCooldownTicks <= 0
&& this.royalHead(RoyalHead.Slot.RIGHT).readyToAttack();
default -> distance <= 36.0D && this.iceSpikeCooldownTicks <= 0
&& this.royalHead(RoyalHead.Slot.RIGHT).readyToAttack();
};
if (!ready) return 0;
return switch (id) {
case "fireball" -> this.behaviorScore(Behavior.KEEPING_FAR) + 4;
case "iceball" -> this.behaviorScore(Behavior.RETREATING) + 4;
default -> this.behaviorScore(Behavior.HUGGING) + 3;
};
}, this.random);
if (selected == null) return;
if ("fireball".equals(selected)) {
if (!this.beginRoyalAttack("fireball", RoyalAttackLane.LEFT_HEAD,
AntarchySettings.kingFireballCooldownTicks(), 14, 1, 5,
new com.craisinlord.antarchy.content.entity.royal.attack.RoyalAttackScheduler.Action() {
@Override public void onStart() {
KingEntity.this.triggerAnim(RoyalHead.Slot.LEFT.controllerName(), "shoot");
}
@Override public void onActive(int elapsedTicks) {
Vec3 origin = KingEntity.this.headAnchor(KingEntity.this.royalHead(RoyalHead.Slot.LEFT));
Vec3 predicted = target.position().add(target.getDeltaMovement().scale(10.0D));
Vec3 direction = predicted.add(0.0D, target.getBbHeight() * 0.5D, 0.0D).subtract(origin).normalize();
RoyalElementalProjectileEntity fireball = RoyalElementalProjectileEntity.create(level, KingEntity.this,
RoyalBeamElement.FIRE, origin, direction);
level.addFreshEntity(fireball);
KingEntity.this.playRoyalSound(AntarchySoundEvents.KING_FIREBALL_SHOOT.get(), 0.9F);
}
})) return;
this.fireballCooldownTicks = AntarchySettings.kingFireballCooldownTicks();
} else if ("iceball".equals(selected)) {
if (!this.beginRoyalAttack("iceball", RoyalAttackLane.RIGHT_HEAD,
AntarchySettings.kingIceballCooldownTicks(), 16, 1, 5,
new com.craisinlord.antarchy.content.entity.royal.attack.RoyalAttackScheduler.Action() {
@Override public void onStart() {
KingEntity.this.triggerAnim(RoyalHead.Slot.RIGHT.controllerName(), "shoot");
}
@Override public void onActive(int elapsedTicks) {
Vec3 origin = KingEntity.this.headAnchor(KingEntity.this.royalHead(RoyalHead.Slot.RIGHT));
Vec3 direction = target.getEyePosition().subtract(origin).normalize();
RoyalElementalProjectileEntity iceball = RoyalElementalProjectileEntity.create(level, KingEntity.this,
RoyalBeamElement.ICE, origin, direction);
level.addFreshEntity(iceball);
KingEntity.this.playRoyalSound(AntarchySoundEvents.KING_BEAM_SHOOT.get(), 1.2F);
}
})) return;
this.iceballCooldownTicks = AntarchySettings.kingIceballCooldownTicks();
} else {
if (!this.beginRoyalAttack("ice_spikes", RoyalAttackLane.RIGHT_HEAD,
AntarchySettings.kingIceSpikeCooldownTicks(), 20, 1, 5,
new com.craisinlord.antarchy.content.entity.royal.attack.RoyalAttackScheduler.Action() {
@Override public void onStart() {
KingEntity.this.triggerAnim(RoyalHead.Slot.RIGHT.controllerName(), "shoot");
}
@Override public void onActive(int elapsedTicks) {
KingEntity.this.raiseIceSpikes(level, target);
KingEntity.this.playRoyalSound(AntarchySoundEvents.KING_BEAM_SHOOT.get(), 0.8F);
}
})) return;
this.iceSpikeCooldownTicks = AntarchySettings.kingIceSpikeCooldownTicks();
}
}
private void raiseIceSpikes(ServerLevel level, LivingEntity target) {
Vec3 start = this.position();
Vec3 end = target.position();
Vec3 line = end.subtract(start).multiply(1.0D, 0.0D, 1.0D);
if (line.lengthSqr() < 1.0E-4D) return;
line = line.normalize();
Vec3 side = new Vec3(-line.z, 0.0D, line.x);
for (int i = 1; i <= 5; i++) {
double distance = i * 4.0D;
for (int lane = -1; lane <= 1; lane++) {
Vec3 point = start.add(line.scale(distance)).add(side.scale(lane * (i % 2 == 0 ? 2.0D : 0.0D)));
double y = this.groundYBelow(point.x, point.z) + 0.1D;
RoyalIceSpikeEntity spike = RoyalIceSpikeEntity.create(level, new Vec3(point.x, y, point.z));
level.addFreshEntity(spike);
}
}
} }
private void performStomp(ServerLevel level, LivingEntity target) { private void performStomp(ServerLevel level, LivingEntity target) {
@@ -406,7 +512,7 @@ public class KingEntity extends RoyalBossEntity {
RoyalBlockDestruction.destroySphere(level, this, RoyalBlockDestruction.destroySphere(level, this,
new Vec3(this.getX(), this.getY(), this.getZ()), STOMP_RADIUS * 0.5D, 96, 80.0D, 0.15F); new Vec3(this.getX(), this.getY(), this.getZ()), STOMP_RADIUS * 0.5D, 96, 80.0D, 0.15F);
level.sendParticles(ParticleTypes.EXPLOSION_EMITTER, this.getX(), this.getY(), this.getZ(), 6, 4.0D, 0.5D, 4.0D, 0.0D); level.sendParticles(ParticleTypes.EXPLOSION_EMITTER, this.getX(), this.getY(), this.getZ(), 6, 4.0D, 0.5D, 4.0D, 0.0D);
this.playSound(AntarchySoundEvents.KING_STOMP.get(), 3.5F, 0.8F); this.playRoyalSound(AntarchySoundEvents.KING_STOMP.get(), 0.8F);
} }
private void performWingGust(ServerLevel level, LivingEntity target) { private void performWingGust(ServerLevel level, LivingEntity target) {
@@ -434,11 +540,15 @@ public class KingEntity extends RoyalBossEntity {
if (this.decreeCooldownTicks-- > 0) { if (this.decreeCooldownTicks-- > 0) {
return; return;
} }
if (!this.beginRoyalAttack("decree", RoyalAttackLane.DECREE,
AntarchySettings.royalDecreeCooldownTicks(), 300)) {
return;
}
this.activeDecreeTicks = 300; this.activeDecreeTicks = 300;
this.activeDecree = this.pickDecree(target); this.activeDecree = this.pickDecree(target);
this.decreeRetreatPressure = false; this.decreeRetreatPressure = false;
this.playSound(AntarchySoundEvents.KING_DECREE_CAST.get(), 3.0F, 0.9F + this.random.nextFloat() * 0.15F); this.playRoyalSound(AntarchySoundEvents.KING_DECREE_CAST.get(), 0.9F + this.random.nextFloat() * 0.15F);
this.playSound(AntarchySoundEvents.KING_ROAR.get(), 3.0F, 0.8F + this.random.nextFloat() * 0.12F); this.playRoyalSound(AntarchySoundEvents.KING_ROAR.get(), 0.8F + this.random.nextFloat() * 0.12F);
if (target instanceof ServerPlayer player) { if (target instanceof ServerPlayer player) {
player.connection.send(new ClientboundSetTitlesAnimationPacket(5, 300, 10)); player.connection.send(new ClientboundSetTitlesAnimationPacket(5, 300, 10));
player.connection.send(new ClientboundSetTitleTextPacket(Component.translatable(this.activeDecree.translationKey()) player.connection.send(new ClientboundSetTitleTextPacket(Component.translatable(this.activeDecree.translationKey())
@@ -452,6 +562,11 @@ public class KingEntity extends RoyalBossEntity {
level.playSound(null, target.blockPosition(), SoundEvents.BELL_BLOCK, SoundSource.HOSTILE, 0.8F, 1.0F + countdown / 500.0F); level.playSound(null, target.blockPosition(), SoundEvents.BELL_BLOCK, SoundSource.HOSTILE, 0.8F, 1.0F + countdown / 500.0F);
} }
this.activeDecree.apply(level, this, target); this.activeDecree.apply(level, this, target);
if (this.activeDecree != null
&& this.activeDecree.evaluate(level, this, target) == RoyalDecree.Evaluation.VIOLATED) {
this.failActiveDecree(target);
return;
}
if (this.activeDecree != null && --this.activeDecreeTicks <= 0) { if (this.activeDecree != null && --this.activeDecreeTicks <= 0) {
this.endDecree(target); this.endDecree(target);
} }
@@ -495,6 +610,15 @@ public class KingEntity extends RoyalBossEntity {
this.endDecree(target); this.endDecree(target);
} }
/** Resolves a decree violation once, using the existing bounded judgment while the new judgment system is pending. */
public void failActiveDecree(@Nullable LivingEntity target) {
if (this.activeDecree == null || target == null || !target.isAlive()) {
return;
}
this.invokeJudgment(target);
this.endDecree(target);
}
public boolean blocksHealing() { public boolean blocksHealing() {
return this.activeDecree instanceof NoRespiteDecree; return this.activeDecree instanceof NoRespiteDecree;
} }
@@ -520,11 +644,11 @@ public class KingEntity extends RoyalBossEntity {
boolean projectile = source.getDirectEntity() instanceof Projectile; boolean projectile = source.getDirectEntity() instanceof Projectile;
this.bumpBehavior(projectile ? Behavior.RANGED : Behavior.HUGGING, 2); this.bumpBehavior(projectile ? Behavior.RANGED : Behavior.HUGGING, 2);
if (this.activeDecree instanceof CloseQuartersDecree && projectile) { if (this.activeDecree instanceof CloseQuartersDecree && projectile) {
this.invokeJudgment(attacker); this.failActiveDecree(attacker);
} else if (this.activeDecree instanceof ComeNoCloserDecree && !projectile) { } else if (this.activeDecree instanceof ComeNoCloserDecree && !projectile) {
this.invokeJudgment(attacker); this.failActiveDecree(attacker);
} else if (this.activeDecree instanceof HandsOffTheCrownDecree) { } else if (this.activeDecree instanceof HandsOffTheCrownDecree) {
this.invokeJudgment(attacker); this.failActiveDecree(attacker);
} }
} }
return super.hurt(source, amount); return super.hurt(source, amount);
@@ -30,6 +30,8 @@ import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.Vec3;
import com.craisinlord.antarchy.content.entity.royal.beam.RoyalBeamSettings; 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.RoyalBeamTerrainMode;
import com.craisinlord.antarchy.content.entity.royal.attack.RoyalAttackLane;
import com.craisinlord.antarchy.content.entity.royal.attack.RoyalEffectController;
import com.craisinlord.antarchy.content.entity.portal.DimensionalTearEntity; import com.craisinlord.antarchy.content.entity.portal.DimensionalTearEntity;
import com.craisinlord.antarchy.content.time.TimeDilationApi; import com.craisinlord.antarchy.content.time.TimeDilationApi;
import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.damagesource.DamageSource;
@@ -61,13 +63,12 @@ public class QueenEntity extends RoyalBossEntity {
private int gravityStompCooldownTicks; private int gravityStompCooldownTicks;
private int timeFieldCooldownTicks; private int timeFieldCooldownTicks;
private int momentumLockCooldownTicks; private int momentumLockCooldownTicks;
private int momentumLockTicks;
private int crushingGravityCooldownTicks; private int crushingGravityCooldownTicks;
private int crushingGravityTicks;
private int accelerationCooldownTicks; private int accelerationCooldownTicks;
private int accelerationTicks;
private int blackHoleCooldownTicks; private int blackHoleCooldownTicks;
private int idleWanderCooldownTicks; private int idleWanderCooldownTicks;
private int finalTimeControlTicks;
private final RoyalEffectController royalEffects = new RoyalEffectController();
private final Map<UUID, Vec3> frozenVelocities = new HashMap<>(); private final Map<UUID, Vec3> frozenVelocities = new HashMap<>();
public QueenEntity(EntityType<? extends QueenEntity> entityType, Level level) { public QueenEntity(EntityType<? extends QueenEntity> entityType, Level level) {
@@ -85,7 +86,12 @@ public class QueenEntity extends RoyalBossEntity {
@Override @Override
protected boolean isFlyingBoss() { protected boolean isFlyingBoss() {
return false; return true;
}
@Override
protected SoundEvent royalFlyLoopSound() {
return AntarchySoundEvents.QUEEN_FLY_LOOP.get();
} }
@Override @Override
@@ -128,7 +134,7 @@ public class QueenEntity extends RoyalBossEntity {
@Override @Override
protected RoyalBeamSettings royalBeamSettings() { protected RoyalBeamSettings royalBeamSettings() {
return new RoyalBeamSettings(AntarchySettings.queenBeamRange(), AntarchySettings.queenBeamTracking(), 7.5D, return new RoyalBeamSettings(AntarchySettings.queenBeamRange(), AntarchySettings.queenBeamTracking(), 7.5D,
AntarchySettings.queenBeamDurationTicks(), AntarchySettings.queenBeamCooldownTicks(), 6.0F, 6.0F, AntarchySettings.queenBeamDurationTicks(), AntarchySettings.queenBeamTravelTicks(), AntarchySettings.queenBeamCooldownTicks(), 6.0F, 6.0F,
(float) AntarchySettings.queenBeamDamage(), 1.0F, 3, 100.0D, (float) AntarchySettings.queenBeamDamage(), 1.0F, 3, 100.0D,
(float) AntarchySettings.queenBeamTerrainRadius(), 4.0F, AntarchySettings.queenBeamTerrainCap(), (float) AntarchySettings.queenBeamTerrainRadius(), 4.0F, AntarchySettings.queenBeamTerrainCap(),
1.0F, 0.08F, 15.0F, true, true); 1.0F, 0.08F, 15.0F, true, true);
@@ -156,9 +162,8 @@ public class QueenEntity extends RoyalBossEntity {
} }
this.tickManticoreSummon(); this.tickManticoreSummon();
this.tickQueenAbilities(); this.tickQueenAbilities();
this.tickRoyalAcceleration(); this.tickFinalTimeControl();
this.tickMomentumLock(); this.royalEffects.tick();
this.tickCrushingGravity();
} }
private void tickIdleWander() { private void tickIdleWander() {
@@ -174,7 +179,7 @@ public class QueenEntity extends RoyalBossEntity {
} }
private int cooldown(int base) { private int cooldown(int base) {
return this.accelerationTicks > 0 ? Math.max(20, base / 2) : base; return this.royalEffects.active("acceleration") ? Math.max(20, base / 2) : base;
} }
private void tickManticoreSummon() { private void tickManticoreSummon() {
@@ -205,7 +210,7 @@ public class QueenEntity extends RoyalBossEntity {
: FAILED_SUMMON_RETRY_TICKS; : FAILED_SUMMON_RETRY_TICKS;
if (spawned > 0) { if (spawned > 0) {
this.triggerAnim("body_action", "minion_spawn"); this.triggerAnim("body_action", "minion_spawn");
this.playSound(AntarchySoundEvents.QUEEN_ROAR.get(), 3.0F, 0.8F + this.random.nextFloat() * 0.12F); this.playRoyalSound(AntarchySoundEvents.QUEEN_ROAR.get(), 0.8F + this.random.nextFloat() * 0.12F);
serverLevel.sendParticles(ParticleTypes.PORTAL, this.getX(), this.getY() + 2.0D, this.getZ(), serverLevel.sendParticles(ParticleTypes.PORTAL, this.getX(), this.getY() + 2.0D, this.getZ(),
36, 3.0D, 2.0D, 3.0D, 0.15D); 36, 3.0D, 2.0D, 3.0D, 0.15D);
} }
@@ -249,40 +254,97 @@ public class QueenEntity extends RoyalBossEntity {
} }
Phase phase = this.phase(); Phase phase = this.phase();
boolean majorBusy = this.momentumLockTicks > 0 || this.crushingGravityTicks > 0; boolean majorBusy = this.royalEffects.active("momentum_lock") || this.royalEffects.active("crushing_gravity");
boolean allowOverlap = phase != Phase.ONE; boolean allowOverlap = phase != Phase.ONE;
String[] candidates = phase == Phase.ONE
if (this.gravityStompCooldownTicks <= 0 && (allowOverlap || !majorBusy)) { ? new String[] {"gravity_stomp", "time_field", "black_hole"}
this.gravityStompCooldownTicks = this.cooldown(GRAVITY_STOMP_COOLDOWN); : new String[] {"gravity_stomp", "time_field", "black_hole", "momentum_lock", "crushing_gravity", "acceleration"};
this.performGravityStomp(level, target); String selected = 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;
};
}, this.random);
if (selected == null) return;
int cooldown = switch (selected) {
case "gravity_stomp" -> GRAVITY_STOMP_COOLDOWN;
case "time_field" -> TIME_FIELD_COOLDOWN;
case "black_hole" -> BLACK_HOLE_COOLDOWN;
case "momentum_lock" -> MOMENTUM_LOCK_COOLDOWN;
case "crushing_gravity" -> CRUSHING_GRAVITY_COOLDOWN;
default -> ACCELERATION_COOLDOWN;
};
if (!this.beginRoyalAttack(selected, RoyalAttackLane.HAZARD, this.cooldown(cooldown),
10, 1, 14, new com.craisinlord.antarchy.content.entity.royal.attack.RoyalAttackScheduler.Action() {
@Override public void onStart() {
if ("gravity_stomp".equals(selected)) {
QueenEntity.this.triggerAnim("body_action", "stomp");
} else {
QueenEntity.this.triggerAnim("body_action", "wing_gust");
} }
if (this.timeFieldCooldownTicks <= 0 && (allowOverlap || !majorBusy)) { }
this.timeFieldCooldownTicks = this.cooldown(TIME_FIELD_COOLDOWN); @Override public void onActive(int elapsedTicks) {
QueenEntity.this.performSelectedQueenAttack(selected, phase, cooldown, level, target);
}
})) return;
}
private void performSelectedQueenAttack(String selected, Phase phase, int cooldown,
ServerLevel level, LivingEntity target) {
switch (selected) {
case "gravity_stomp" -> { this.gravityStompCooldownTicks = cooldown; this.performGravityStomp(level, target); }
case "time_field" -> {
this.timeFieldCooldownTicks = cooldown;
double fieldRate = phase == Phase.THREE ? 0.1D : 0.4D; double fieldRate = phase == Phase.THREE ? 0.1D : 0.4D;
TimeDilationApi.createField(level, target.position(), 12.0D, fieldRate, 140 + phase.ordinal() * 40); TimeDilationApi.createField(level, target.position(), 12.0D, fieldRate, 140 + phase.ordinal() * 40);
this.triggerAnim("body_action", "wing_gust");
} }
if (this.blackHoleCooldownTicks <= 0 && (allowOverlap || !majorBusy)) { case "black_hole" -> { this.blackHoleCooldownTicks = cooldown; this.castBlackHole(level, target); }
this.blackHoleCooldownTicks = this.cooldown(BLACK_HOLE_COOLDOWN); case "momentum_lock" -> { this.momentumLockCooldownTicks = cooldown; this.startMomentumLock(level); }
this.castBlackHole(level, target); case "crushing_gravity" -> {
this.crushingGravityCooldownTicks = cooldown;
this.royalEffects.start("crushing_gravity", CRUSHING_GRAVITY_DURATION,
() -> {}, this::tickCrushingGravity, () -> {});
this.playRoyalSound(AntarchySoundEvents.QUEEN_ROAR.get(), 0.6F);
} }
if (this.momentumLockTicks <= 0 && this.momentumLockCooldownTicks <= 0 && (allowOverlap || !majorBusy)) { case "acceleration" -> { this.accelerationCooldownTicks = cooldown; this.startRoyalAcceleration(); }
this.startMomentumLock(level);
} }
if (this.crushingGravityTicks <= 0 && this.crushingGravityCooldownTicks <= 0 && phase != Phase.ONE) {
this.crushingGravityCooldownTicks = CRUSHING_GRAVITY_COOLDOWN;
this.crushingGravityTicks = CRUSHING_GRAVITY_DURATION;
this.playSound(AntarchySoundEvents.QUEEN_ROAR.get(), 3.0F, 0.6F);
} }
if (this.accelerationTicks <= 0 && this.accelerationCooldownTicks <= 0
&& (phase == Phase.THREE || (phase == Phase.TWO && this.random.nextInt(3) == 0))) { private boolean queenAttackReady(String id, Phase phase, boolean majorBusy, boolean allowOverlap) {
if (!allowOverlap && majorBusy) return false;
return switch (id) {
case "gravity_stomp" -> this.gravityStompCooldownTicks <= 0;
case "time_field" -> this.timeFieldCooldownTicks <= 0;
case "black_hole" -> this.blackHoleCooldownTicks <= 0;
case "momentum_lock" -> !this.royalEffects.active("momentum_lock") && this.momentumLockCooldownTicks <= 0;
case "crushing_gravity" -> phase != Phase.ONE && !this.royalEffects.active("crushing_gravity") && this.crushingGravityCooldownTicks <= 0;
case "acceleration" -> phase == Phase.THREE && !this.royalEffects.active("acceleration") && this.accelerationCooldownTicks <= 0;
default -> false;
};
}
private void tickFinalTimeControl() {
if (this.phase() != Phase.THREE || !(this.level() instanceof ServerLevel level)) {
return;
}
if (!this.royalEffects.active("acceleration") && this.accelerationCooldownTicks <= 0) {
this.startRoyalAcceleration(); this.startRoyalAcceleration();
} }
if (this.finalTimeControlTicks-- <= 0) {
this.finalTimeControlTicks = AntarchySettings.queenFinalTimeFieldCooldownTicks();
LivingEntity target = this.getTarget();
if (target != null && target.isAlive()) {
TimeDilationApi.createField(level, target.position(), AntarchySettings.queenFinalTimeFieldRadius(),
AntarchySettings.queenFinalTimeFieldRate(), AntarchySettings.queenFinalTimeFieldDurationTicks());
}
}
} }
private void performGravityStomp(ServerLevel level, LivingEntity target) { private void performGravityStomp(ServerLevel level, LivingEntity target) {
this.triggerAnim("body_action", "stomp"); this.triggerAnim("body_action", "stomp");
this.playSound(AntarchySoundEvents.QUEEN_ROAR.get(), 3.0F, 0.7F); this.playRoyalSound(AntarchySoundEvents.QUEEN_ROAR.get(), 0.7F);
DamageSource source = this.damageSources().mobAttack(this); DamageSource source = this.damageSources().mobAttack(this);
for (LivingEntity living : level.getEntitiesOfClass(LivingEntity.class, for (LivingEntity living : level.getEntitiesOfClass(LivingEntity.class,
this.getBoundingBox().inflate(18.0D), entity -> entity instanceof Player && entity.isAlive())) { this.getBoundingBox().inflate(18.0D), entity -> entity instanceof Player && entity.isAlive())) {
@@ -303,16 +365,14 @@ public class QueenEntity extends RoyalBossEntity {
RoyalBlackHoleEntity hole = RoyalBlackHoleEntity.create(level, anchor, this.getUUID()); RoyalBlackHoleEntity hole = RoyalBlackHoleEntity.create(level, anchor, this.getUUID());
level.addFreshEntity(hole); level.addFreshEntity(hole);
this.triggerAnim("body_action", "wing_gust"); this.triggerAnim("body_action", "wing_gust");
this.playSound(AntarchySoundEvents.QUEEN_ROAR.get(), 3.0F, 1.15F); this.playRoyalSound(AntarchySoundEvents.QUEEN_ROAR.get(), 1.0F);
level.sendParticles(ParticleTypes.REVERSE_PORTAL, anchor.x, anchor.y, anchor.z, 60, 1.0D, 1.0D, 1.0D, 0.4D); level.sendParticles(ParticleTypes.REVERSE_PORTAL, anchor.x, anchor.y, anchor.z, 60, 1.0D, 1.0D, 1.0D, 0.4D);
} }
private void startMomentumLock(ServerLevel level) { private void startMomentumLock(ServerLevel level) {
this.momentumLockCooldownTicks = MOMENTUM_LOCK_COOLDOWN; this.momentumLockCooldownTicks = MOMENTUM_LOCK_COOLDOWN;
this.momentumLockTicks = MOMENTUM_LOCK_DURATION; this.royalEffects.start("momentum_lock", MOMENTUM_LOCK_DURATION, () -> {
this.frozenVelocities.clear(); this.frozenVelocities.clear();
this.triggerAnim("body_action", "wing_gust");
this.playSound(AntarchySoundEvents.QUEEN_ROAR.get(), 3.0F, 0.5F);
for (Entity entity : level.getEntitiesOfClass(Entity.class, for (Entity entity : level.getEntitiesOfClass(Entity.class,
this.getBoundingBox().inflate(MOMENTUM_LOCK_RADIUS))) { this.getBoundingBox().inflate(MOMENTUM_LOCK_RADIUS))) {
if (entity == this || isFieldImmune(entity) || entity instanceof Player player && player.isCreative()) { if (entity == this || isFieldImmune(entity) || entity instanceof Player player && player.isCreative()) {
@@ -320,13 +380,21 @@ public class QueenEntity extends RoyalBossEntity {
} }
this.frozenVelocities.put(entity.getUUID(), entity.getDeltaMovement()); this.frozenVelocities.put(entity.getUUID(), entity.getDeltaMovement());
} }
}, this::tickMomentumLockEffect, () -> {
for (Map.Entry<UUID, Vec3> entry : this.frozenVelocities.entrySet()) {
Entity entity = level.getEntity(entry.getKey());
if (entity != null && entity.isAlive()) {
entity.setDeltaMovement(entry.getValue());
entity.hasImpulse = true;
}
}
this.frozenVelocities.clear();
});
this.triggerAnim("body_action", "wing_gust");
this.playRoyalSound(AntarchySoundEvents.QUEEN_ROAR.get(), 0.5F);
} }
private void tickMomentumLock() { private void tickMomentumLockEffect() {
if (this.momentumLockTicks <= 0) {
return;
}
this.momentumLockTicks--;
if (!(this.level() instanceof ServerLevel level)) { if (!(this.level() instanceof ServerLevel level)) {
return; return;
} }
@@ -335,29 +403,20 @@ public class QueenEntity extends RoyalBossEntity {
if (entity == null || !entity.isAlive()) { if (entity == null || !entity.isAlive()) {
continue; continue;
} }
if (this.momentumLockTicks <= 0) {
entity.setDeltaMovement(entry.getValue());
entity.hasImpulse = true;
} else {
entity.setDeltaMovement(Vec3.ZERO); entity.setDeltaMovement(Vec3.ZERO);
entity.fallDistance = 0.0F; entity.fallDistance = 0.0F;
entity.hasImpulse = true; entity.hasImpulse = true;
if (this.tickCount % 6 == 0 && level instanceof ServerLevel serverLevel) { if (this.tickCount % 6 == 0) {
serverLevel.sendParticles(ParticleTypes.END_ROD, entity.getX(), entity.getY() + entity.getBbHeight() * 0.5D, entity.getZ(), level.sendParticles(ParticleTypes.END_ROD, entity.getX(), entity.getY() + entity.getBbHeight() * 0.5D, entity.getZ(),
2, 0.2D, 0.2D, 0.2D, 0.0D); 2, 0.2D, 0.2D, 0.2D, 0.0D);
} }
} }
} }
if (this.momentumLockTicks <= 0) {
this.frozenVelocities.clear();
}
}
private void tickCrushingGravity() { private void tickCrushingGravity() {
if (this.crushingGravityTicks <= 0) { if (!this.royalEffects.active("crushing_gravity")) {
return; return;
} }
this.crushingGravityTicks--;
if (!(this.level() instanceof ServerLevel level)) { if (!(this.level() instanceof ServerLevel level)) {
return; return;
} }
@@ -378,11 +437,16 @@ public class QueenEntity extends RoyalBossEntity {
private void startRoyalAcceleration() { private void startRoyalAcceleration() {
this.accelerationCooldownTicks = ACCELERATION_COOLDOWN; this.accelerationCooldownTicks = ACCELERATION_COOLDOWN;
this.accelerationTicks = ACCELERATION_DURATION; this.royalEffects.start("acceleration", ACCELERATION_DURATION, () -> {
this.setRoyalAccelerated(true); this.setRoyalAccelerated(true);
this.playSound(AntarchySoundEvents.QUEEN_ROAR.get(), 3.5F, 1.4F); this.playRoyalSound(AntarchySoundEvents.QUEEN_ROAR.get(), 1.4F);
applyAccelModifier(this.getAttribute(Attributes.MOVEMENT_SPEED), ACCEL_SPEED_ID); applyAccelModifier(this.getAttribute(Attributes.MOVEMENT_SPEED), ACCEL_SPEED_ID);
applyAccelModifier(this.getAttribute(Attributes.FLYING_SPEED), ACCEL_FLY_ID); applyAccelModifier(this.getAttribute(Attributes.FLYING_SPEED), ACCEL_FLY_ID);
}, this::tickRoyalAccelerationEffect, () -> {
this.setRoyalAccelerated(false);
removeAccelModifier(this.getAttribute(Attributes.MOVEMENT_SPEED), ACCEL_SPEED_ID);
removeAccelModifier(this.getAttribute(Attributes.FLYING_SPEED), ACCEL_FLY_ID);
});
} }
private static void applyAccelModifier(AttributeInstance attribute, ResourceLocation id) { private static void applyAccelModifier(AttributeInstance attribute, ResourceLocation id) {
@@ -398,16 +462,10 @@ public class QueenEntity extends RoyalBossEntity {
} }
} }
private void tickRoyalAcceleration() { private void tickRoyalAccelerationEffect() {
if (this.accelerationTicks <= 0) { if (!this.isRoyalAccelerated()) {
if (this.isRoyalAccelerated()) {
this.setRoyalAccelerated(false);
removeAccelModifier(this.getAttribute(Attributes.MOVEMENT_SPEED), ACCEL_SPEED_ID);
removeAccelModifier(this.getAttribute(Attributes.FLYING_SPEED), ACCEL_FLY_ID);
}
return; return;
} }
this.accelerationTicks--;
if (this.level() instanceof ServerLevel level && this.tickCount % 2 == 0) { if (this.level() instanceof ServerLevel level && this.tickCount % 2 == 0) {
level.sendParticles(ACCEL_DUST, level.sendParticles(ACCEL_DUST,
this.getX() + (this.random.nextDouble() - 0.5D) * this.getBbWidth(), this.getX() + (this.random.nextDouble() - 0.5D) * this.getBbWidth(),
@@ -415,11 +473,6 @@ public class QueenEntity extends RoyalBossEntity {
this.getZ() + (this.random.nextDouble() - 0.5D) * this.getBbWidth(), this.getZ() + (this.random.nextDouble() - 0.5D) * this.getBbWidth(),
6, 0.4D, 0.6D, 0.4D, 0.02D); 6, 0.4D, 0.6D, 0.4D, 0.02D);
} }
if (this.accelerationTicks <= 0) {
this.setRoyalAccelerated(false);
removeAccelModifier(this.getAttribute(Attributes.MOVEMENT_SPEED), ACCEL_SPEED_ID);
removeAccelModifier(this.getAttribute(Attributes.FLYING_SPEED), ACCEL_FLY_ID);
}
} }
@Override @Override
@@ -427,6 +480,8 @@ public class QueenEntity extends RoyalBossEntity {
if (!this.level().isClientSide && this.level() instanceof ServerLevel level) { if (!this.level().isClientSide && this.level() instanceof ServerLevel level) {
DimensionalTearEntity.discardQueenOwnedTears(level, this.getUUID()); DimensionalTearEntity.discardQueenOwnedTears(level, this.getUUID());
} }
this.royalEffects.clear();
this.frozenVelocities.clear();
removeAccelModifier(this.getAttribute(Attributes.MOVEMENT_SPEED), ACCEL_SPEED_ID); removeAccelModifier(this.getAttribute(Attributes.MOVEMENT_SPEED), ACCEL_SPEED_ID);
removeAccelModifier(this.getAttribute(Attributes.FLYING_SPEED), ACCEL_FLY_ID); removeAccelModifier(this.getAttribute(Attributes.FLYING_SPEED), ACCEL_FLY_ID);
super.die(damageSource); super.die(damageSource);
@@ -468,8 +523,6 @@ public class QueenEntity extends RoyalBossEntity {
tag.putInt(SUMMON_COOLDOWN_KEY, this.manticoreSummonCooldownTicks); tag.putInt(SUMMON_COOLDOWN_KEY, this.manticoreSummonCooldownTicks);
tag.putInt("GravityStompCooldownTicks", this.gravityStompCooldownTicks); tag.putInt("GravityStompCooldownTicks", this.gravityStompCooldownTicks);
tag.putInt("TimeFieldCooldownTicks", this.timeFieldCooldownTicks); tag.putInt("TimeFieldCooldownTicks", this.timeFieldCooldownTicks);
tag.putInt("MomentumLockCooldownTicks", this.momentumLockCooldownTicks);
tag.putInt("CrushingGravityCooldownTicks", this.crushingGravityCooldownTicks);
tag.putInt("AccelerationCooldownTicks", this.accelerationCooldownTicks); tag.putInt("AccelerationCooldownTicks", this.accelerationCooldownTicks);
tag.putInt("BlackHoleCooldownTicks", this.blackHoleCooldownTicks); tag.putInt("BlackHoleCooldownTicks", this.blackHoleCooldownTicks);
} }
@@ -1,6 +1,8 @@
package com.craisinlord.antarchy.content.entity.royal; package com.craisinlord.antarchy.content.entity.royal;
import com.craisinlord.antarchy.content.AntarchyObjects; import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.content.time.TimeDilationApi;
import com.craisinlord.antarchy.config.AntarchySettings;
import java.util.UUID; import java.util.UUID;
import net.minecraft.core.particles.ParticleTypes; import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
@@ -17,8 +19,16 @@ import net.minecraft.world.level.Level;
import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import software.bernie.geckolib.animatable.GeoEntity;
import software.bernie.geckolib.animatable.instance.AnimatableInstanceCache;
import software.bernie.geckolib.animation.AnimatableManager;
import software.bernie.geckolib.animation.AnimationController;
import software.bernie.geckolib.animation.AnimationState;
import software.bernie.geckolib.animation.PlayState;
import software.bernie.geckolib.animation.RawAnimation;
import software.bernie.geckolib.util.GeckoLibUtil;
public class RoyalBlackHoleEntity extends Entity { public class RoyalBlackHoleEntity extends Entity implements GeoEntity {
private static final EntityDataAccessor<Float> RADIUS = private static final EntityDataAccessor<Float> RADIUS =
SynchedEntityData.defineId(RoyalBlackHoleEntity.class, EntityDataSerializers.FLOAT); SynchedEntityData.defineId(RoyalBlackHoleEntity.class, EntityDataSerializers.FLOAT);
private static final EntityDataAccessor<Boolean> COLLAPSING = private static final EntityDataAccessor<Boolean> COLLAPSING =
@@ -36,6 +46,7 @@ public class RoyalBlackHoleEntity extends Entity {
private UUID ownerId; private UUID ownerId;
private int age; private int age;
private int activeTicks = DEFAULT_ACTIVE_TICKS; private int activeTicks = DEFAULT_ACTIVE_TICKS;
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
public RoyalBlackHoleEntity(EntityType<? extends RoyalBlackHoleEntity> entityType, Level level) { public RoyalBlackHoleEntity(EntityType<? extends RoyalBlackHoleEntity> entityType, Level level) {
super(entityType, level); super(entityType, level);
@@ -46,14 +57,14 @@ public class RoyalBlackHoleEntity extends Entity {
public static RoyalBlackHoleEntity create(ServerLevel level, Vec3 center, double radius, int activeTicks, @Nullable UUID ownerId) { public static RoyalBlackHoleEntity create(ServerLevel level, Vec3 center, double radius, int activeTicks, @Nullable UUID ownerId) {
RoyalBlackHoleEntity hole = new RoyalBlackHoleEntity(AntarchyObjects.ROYAL_BLACK_HOLE.get(), level); RoyalBlackHoleEntity hole = new RoyalBlackHoleEntity(AntarchyObjects.ROYAL_BLACK_HOLE.get(), level);
hole.setPos(center.x, center.y, center.z); hole.setPos(center.x, center.y, center.z);
hole.setRadius(radius); hole.setRadius(radius > 0.0D ? radius : AntarchySettings.queenBlackHoleRadius());
hole.activeTicks = Math.max(20, activeTicks); hole.activeTicks = activeTicks > 0 ? Math.max(20, activeTicks) : AntarchySettings.queenBlackHoleActiveTicks();
hole.ownerId = ownerId; hole.ownerId = ownerId;
return hole; return hole;
} }
public static RoyalBlackHoleEntity create(ServerLevel level, Vec3 center, @Nullable UUID ownerId) { public static RoyalBlackHoleEntity create(ServerLevel level, Vec3 center, @Nullable UUID ownerId) {
return create(level, center, DEFAULT_RADIUS, DEFAULT_ACTIVE_TICKS, ownerId); return create(level, center, AntarchySettings.queenBlackHoleRadius(), AntarchySettings.queenBlackHoleActiveTicks(), ownerId);
} }
@Override @Override
@@ -84,6 +95,12 @@ public class RoyalBlackHoleEntity extends Entity {
this.age++; this.age++;
if (!this.isCollapsing()) { if (!this.isCollapsing()) {
this.applyPull(); this.applyPull();
if (this.age % 10 == 0 && this.level() instanceof ServerLevel level) {
TimeDilationApi.createField(level, this.position(), Math.max(2.0D, this.radius() * 0.55D), 0.18D, 14);
if (this.age % 20 == 0 && level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
RoyalBlockDestruction.destroySphere(level, this, this.position(), Math.min(2.0D, this.radius() * 0.2D), 8, 30.0D, 0.0F);
}
}
if (this.age >= this.activeTicks) { if (this.age >= this.activeTicks) {
this.entityData.set(COLLAPSING, true); this.entityData.set(COLLAPSING, true);
this.age = 0; this.age = 0;
@@ -108,7 +125,7 @@ public class RoyalBlackHoleEntity extends Entity {
if (distance < 0.5D || distance > radius) { if (distance < 0.5D || distance > radius) {
continue; continue;
} }
double strength = 0.16D * (1.0D - distance / radius); double strength = AntarchySettings.queenBlackHolePullStrength() * (1.0D - distance / radius);
Vec3 pull = toCenter.scale(strength / distance); Vec3 pull = toCenter.scale(strength / distance);
entity.setDeltaMovement(entity.getDeltaMovement().scale(0.86D).add(pull)); entity.setDeltaMovement(entity.getDeltaMovement().scale(0.86D).add(pull));
entity.hasImpulse = true; entity.hasImpulse = true;
@@ -201,4 +218,18 @@ public class RoyalBlackHoleEntity extends Entity {
tag.putUUID(OWNER_KEY, this.ownerId); tag.putUUID(OWNER_KEY, this.ownerId);
} }
} }
@Override
public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
controllers.add(new AnimationController<>(this, "black_hole_controller", 0, this::blackHoleController));
}
private PlayState blackHoleController(AnimationState<RoyalBlackHoleEntity> state) {
return state.setAndContinue(RawAnimation.begin().thenLoop("animation"));
}
@Override
public AnimatableInstanceCache getAnimatableInstanceCache() {
return this.geoCache;
}
} }
@@ -8,6 +8,10 @@ import com.craisinlord.antarchy.content.entity.royal.beam.RoyalBeamController;
import com.craisinlord.antarchy.content.entity.royal.beam.RoyalBeamSettings; import com.craisinlord.antarchy.content.entity.royal.beam.RoyalBeamSettings;
import com.craisinlord.antarchy.content.entity.royal.beam.RoyalBeamTerrainMode; import com.craisinlord.antarchy.content.entity.royal.beam.RoyalBeamTerrainMode;
import com.craisinlord.antarchy.content.entity.royal.beam.RoyalBeamElement; import com.craisinlord.antarchy.content.entity.royal.beam.RoyalBeamElement;
import com.craisinlord.antarchy.content.entity.royal.attack.RoyalAttackLane;
import com.craisinlord.antarchy.content.entity.royal.attack.RoyalAttackScheduler;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi;
import com.craisinlord.antarchy.content.worldgen.thoraxis.ThoraxisUndersideManager;
import java.util.List; import java.util.List;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.network.syncher.EntityDataAccessor; import net.minecraft.network.syncher.EntityDataAccessor;
@@ -89,6 +93,11 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
private static final int CRUSH_MAX_BLOCKS = 24; private static final int CRUSH_MAX_BLOCKS = 24;
private static final double CRUSH_MAX_RESISTANCE = 60.0D; private static final double CRUSH_MAX_RESISTANCE = 60.0D;
private static final float CRUSH_DROP_CHANCE = 0.1F; private static final float CRUSH_DROP_CHANCE = 0.1F;
private static final int OBSTRUCTION_CLEAR_INTERVAL_TICKS = 5;
private static final double OBSTRUCTION_CLEAR_RADIUS = 3.5D;
private static final int OBSTRUCTION_CLEAR_MAX_BLOCKS = 24;
private static final double OBSTRUCTION_CLEAR_MAX_RESISTANCE = 60.0D;
private static final float OBSTRUCTION_CLEAR_DROP_CHANCE = 0.1F;
protected static final double FLYING_MIN_HOVER = 2.5D; protected static final double FLYING_MIN_HOVER = 2.5D;
protected static final double FLYING_PREFERRED_HOVER = 6.0D; protected static final double FLYING_PREFERRED_HOVER = 6.0D;
@@ -116,6 +125,7 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this); private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
protected final RoyalBeamController beamController = new RoyalBeamController(this); protected final RoyalBeamController beamController = new RoyalBeamController(this);
protected final RoyalAttackScheduler attackScheduler = new RoyalAttackScheduler();
private final ServerBossEvent bossEvent; private final ServerBossEvent bossEvent;
private final RoyalHead[] heads = { private final RoyalHead[] heads = {
new RoyalHead(RoyalHead.Slot.LEFT), new RoyalHead(RoyalHead.Slot.LEFT),
@@ -127,6 +137,7 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
private RoyalHead activeBeamHead; private RoyalHead activeBeamHead;
private int beamLoopSoundTicks; private int beamLoopSoundTicks;
private int beamHeadCursor; private int beamHeadCursor;
private boolean beamStarted;
@Nullable @Nullable
private Entity[] multipartParts; private Entity[] multipartParts;
@@ -152,6 +163,11 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
protected abstract SoundEvent royalDeathSound(); protected abstract SoundEvent royalDeathSound();
@Nullable
protected SoundEvent royalFlyLoopSound() {
return null;
}
protected abstract SoundEvent royalBiteSound(); protected abstract SoundEvent royalBiteSound();
protected abstract SoundEvent royalBeamShootSound(); protected abstract SoundEvent royalBeamShootSound();
@@ -275,7 +291,11 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
@Override @Override
protected float getSoundVolume() { protected float getSoundVolume() {
return 2.5F; return (float) AntarchySettings.royalBossSoundVolume();
}
protected void playRoyalSound(SoundEvent sound, float pitch) {
this.playSound(sound, (float) AntarchySettings.royalBossSoundVolume(), pitch);
} }
@Override @Override
@@ -292,8 +312,21 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
@Override @Override
public void tick() { public void tick() {
super.tick(); super.tick();
if (!this.level().isClientSide
&& !this.isDeadOrDying()
&& this.tickCount % OBSTRUCTION_CLEAR_INTERVAL_TICKS == 0
&& this.isInWall()) {
RoyalBlockDestruction.destroySphere(
(ServerLevel) this.level(),
this,
this.position().add(0.0D, this.getBbHeight() * 0.5D, 0.0D),
OBSTRUCTION_CLEAR_RADIUS,
OBSTRUCTION_CLEAR_MAX_BLOCKS,
OBSTRUCTION_CLEAR_MAX_RESISTANCE,
OBSTRUCTION_CLEAR_DROP_CHANCE);
}
if (this.isFlyingBoss()) { if (this.isFlyingBoss()) {
this.setNoGravity(true); this.setNoGravity(!AntarchyGravityApi.isGravityInverted(this));
} }
if (this.level().isClientSide) { if (this.level().isClientSide) {
return; return;
@@ -303,9 +336,10 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
} }
this.bossEvent.setProgress(this.getHealth() / this.getMaxHealth()); this.bossEvent.setProgress(this.getHealth() / this.getMaxHealth());
this.attackScheduler.tick();
for (RoyalHead head : this.heads) { if (this.isFlyingBoss() && this.tickCount % 40 == 0 && this.royalFlyLoopSound() != null) {
head.tick(); this.playRoyalSound(this.royalFlyLoopSound(), 0.92F + this.random.nextFloat() * 0.12F);
} }
if (this.isDeadOrDying()) { if (this.isDeadOrDying()) {
@@ -326,16 +360,10 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
this.steerTowardTarget(primaryTarget); this.steerTowardTarget(primaryTarget);
this.tickBodyCrush(); this.tickBodyCrush();
for (RoyalHead head : this.heads) {
if (head.tickBiteHit()) {
this.applyBiteHit(head);
}
}
Phase phase = this.phase(); Phase phase = this.phase();
int activeHeadAttacks = 0; int activeHeadAttacks = 0;
for (RoyalHead head : this.heads) { for (RoyalHead head : this.heads) {
if (head.busy()) { if (this.attackScheduler.laneBusy(this.headLane(head))) {
activeHeadAttacks++; activeHeadAttacks++;
} }
} }
@@ -344,7 +372,7 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
if (activeHeadAttacks >= phase.maxConcurrentHeadAttacks()) { if (activeHeadAttacks >= phase.maxConcurrentHeadAttacks()) {
break; break;
} }
if (!head.readyToAttack()) { if (!head.readyToAttack() || this.attackScheduler.laneBusy(this.headLane(head))) {
continue; continue;
} }
LivingEntity headTarget = head.target(this.level()); LivingEntity headTarget = head.target(this.level());
@@ -355,8 +383,14 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
} }
} }
@Override
public boolean isInvulnerableTo(DamageSource source) {
return source.is(net.minecraft.world.damagesource.DamageTypes.IN_WALL)
|| super.isInvulnerableTo(source);
}
private void tickRoyalBeam(LivingEntity primaryTarget) { private void tickRoyalBeam(LivingEntity primaryTarget) {
if (this.beamCooldownTicks > 0) { if (this.beamCooldownTicks > 0 || this.attackScheduler.laneBusy(this.beamLane())) {
this.beamCooldownTicks--; this.beamCooldownTicks--;
} }
@@ -364,10 +398,12 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
LivingEntity beamTarget = this.activeBeamHead.target(this.level()); LivingEntity beamTarget = this.activeBeamHead.target(this.level());
if (!this.activeBeamHead.shooting() || beamTarget == null || !beamTarget.isAlive()) { if (!this.activeBeamHead.shooting() || beamTarget == null || !beamTarget.isAlive()) {
this.stopRoyalBeam(); this.stopRoyalBeam();
} else if (!this.beamStarted) {
return;
} else { } else {
if (this.beamLoopSoundTicks-- <= 0) { if (this.beamLoopSoundTicks-- <= 0) {
this.beamLoopSoundTicks = 20; this.beamLoopSoundTicks = 20;
this.playSound(this.royalBeamLoopSound(), 2.5F, 1.0F); this.playRoyalSound(this.royalBeamLoopSound(), 1.0F);
} }
this.beamController.tick( this.beamController.tick(
this.headAnchor(this.activeBeamHead), this.headAnchor(this.activeBeamHead),
@@ -393,24 +429,56 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
for (int offset = 0; offset < this.heads.length; offset++) { for (int offset = 0; offset < this.heads.length; offset++) {
RoyalHead head = this.heads[(this.beamHeadCursor + offset) % this.heads.length]; RoyalHead head = this.heads[(this.beamHeadCursor + offset) % this.heads.length];
LivingEntity target = head.target(this.level()); LivingEntity target = head.target(this.level());
if (!head.readyToAttack() || target == null || !this.canAttack(target) if (!head.readyToAttack() || this.attackScheduler.laneBusy(this.headLane(head))
|| target == null || !this.canAttack(target)
|| this.headWithinBiteReach(head, target)) { || this.headWithinBiteReach(head, target)) {
continue; continue;
} }
int duration = Math.max(1, this.royalBeamSettings().durationTicks()); RoyalBeamSettings beamSettings = this.royalBeamSettings();
int cooldown = Math.max(1, Mth.floor(this.royalBeamSettings().cooldownTicks() * phase.cooldownScale())); int duration = Math.max(1, beamSettings.durationTicks());
head.startShoot(duration, cooldown); int cooldown = Math.max(1, Mth.floor(beamSettings.cooldownTicks() * phase.cooldownScale()));
int windup = Math.max(0, AntarchySettings.queenBeamWindupTicks());
if (this instanceof KingEntity) {
windup = Math.max(0, AntarchySettings.kingBeamWindupTicks());
}
LivingEntity committedTarget = target;
if (!this.attackScheduler.start("beam_" + head.slot().name(), this.beamLane(), cooldown,
windup, duration, 8, new RoyalAttackScheduler.Action() {
@Override
public void onStart() {
head.startShoot();
}
@Override
public void onActive(int elapsedTicks) {
if (elapsedTicks != 0 || RoyalBossEntity.this.beamStarted) {
return;
}
RoyalBossEntity.this.beamStarted = true;
RoyalBossEntity.this.beamController.start(
RoyalBossEntity.this.headAnchor(head), committedTarget.getEyePosition(), duration);
RoyalBossEntity.this.entityData.set(BEAM_ELEMENT, RoyalBossEntity.this.royalBeamElement(head).ordinal());
RoyalBossEntity.this.setFiringRoyalBeam(true);
RoyalBossEntity.this.triggerAnim(head.slot().controllerName(), "shoot");
RoyalBossEntity.this.beamLoopSoundTicks = 20;
RoyalBossEntity.this.playRoyalSound(RoyalBossEntity.this.royalBeamStartSound(head), 1.0F);
RoyalBossEntity.this.playRoyalSound(RoyalBossEntity.this.royalBeamShootSound(), 1.0F);
}
@Override
public void onComplete() {
if (RoyalBossEntity.this.activeBeamHead == head) {
RoyalBossEntity.this.stopRoyalBeam();
}
}
})) {
continue;
}
head.setBeamActive(true); head.setBeamActive(true);
this.activeBeamHead = head; this.activeBeamHead = head;
this.beamHeadCursor = (head.slot().partIndex()) % this.heads.length; this.beamHeadCursor = (head.slot().partIndex()) % this.heads.length;
this.beamCooldownTicks = cooldown; this.beamCooldownTicks = cooldown;
this.beamController.start(this.headAnchor(head), target.getEyePosition(), duration); this.beamStarted = false;
this.entityData.set(BEAM_ELEMENT, this.royalBeamElement(head).ordinal());
this.setFiringRoyalBeam(true);
this.triggerAnim(head.slot().controllerName(), "shoot");
this.beamLoopSoundTicks = 20;
this.playSound(this.royalBeamStartSound(head), 3.0F, 1.0F);
this.playSound(this.royalBeamShootSound(), 3.0F, 1.0F);
return; return;
} }
} }
@@ -418,11 +486,14 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
private void stopRoyalBeam() { private void stopRoyalBeam() {
if (this.activeBeamHead != null) { if (this.activeBeamHead != null) {
this.activeBeamHead.setBeamActive(false); this.activeBeamHead.setBeamActive(false);
this.activeBeamHead.stopShoot();
} }
this.activeBeamHead = null; this.activeBeamHead = null;
this.beamStarted = false;
this.attackScheduler.cancel(this.beamLane());
this.beamController.stop(); this.beamController.stop();
this.entityData.set(BEAM_ELEMENT, RoyalBeamElement.GENERIC.ordinal()); this.entityData.set(BEAM_ELEMENT, RoyalBeamElement.GENERIC.ordinal());
this.playSound(this.royalBeamEndSound(), 2.5F, 1.0F); this.playRoyalSound(this.royalBeamEndSound(), 1.0F);
this.setFiringRoyalBeam(false); this.setFiringRoyalBeam(false);
this.setRoyalBeamEndPosition(null); this.setRoyalBeamEndPosition(null);
} }
@@ -484,8 +555,11 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
return; return;
} }
boolean inverted = AntarchyGravityApi.isGravityInverted(this);
double groundBelowTarget = this.groundYBelow(target.getX(), target.getZ()); double groundBelowTarget = this.groundYBelow(target.getX(), target.getZ());
double wantedY = Mth.clamp(target.getY() + 3.0D, double wantedY = inverted
? target.getY() - 3.0D
: Mth.clamp(target.getY() + 3.0D,
groundBelowTarget + FLYING_MIN_HOVER, groundBelowTarget + FLYING_MIN_HOVER,
groundBelowTarget + FLYING_MAX_HOVER_ABOVE_GROUND); groundBelowTarget + FLYING_MAX_HOVER_ABOVE_GROUND);
boolean closeVertically = Math.abs(wantedY - this.getY()) <= 2.5D; boolean closeVertically = Math.abs(wantedY - this.getY()) <= 2.5D;
@@ -502,6 +576,9 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
} }
private void tickFlyingAltitude() { private void tickFlyingAltitude() {
if (AntarchyGravityApi.isGravityInverted(this)) {
return;
}
if (this.onGround()) { if (this.onGround()) {
this.setDeltaMovement(this.getDeltaMovement().add(0.0D, 0.2D, 0.0D)); this.setDeltaMovement(this.getDeltaMovement().add(0.0D, 0.2D, 0.0D));
} }
@@ -535,9 +612,44 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
private void startBite(RoyalHead head, Phase phase) { private void startBite(RoyalHead head, Phase phase) {
int cooldown = Math.max(10, Mth.floor(AntarchySettings.royalBossBiteCooldownTicks() * phase.cooldownScale())); int cooldown = Math.max(10, Mth.floor(AntarchySettings.royalBossBiteCooldownTicks() * phase.cooldownScale()));
head.startBite(BITE_DURATION_TICKS, BITE_HIT_TICK, cooldown); if (!this.attackScheduler.start("bite_" + head.slot().name(), headLane(head), cooldown,
this.triggerAnim(head.slot().controllerName(), "bite"); BITE_HIT_TICK, 1, BITE_DURATION_TICKS - BITE_HIT_TICK,
this.playSound(this.royalBiteSound(), 3.0F, 0.9F + this.random.nextFloat() * 0.2F); new RoyalAttackScheduler.Action() {
@Override
public void onStart() {
RoyalBossEntity.this.triggerAnim(head.slot().controllerName(), "bite");
RoyalBossEntity.this.playRoyalSound(RoyalBossEntity.this.royalBiteSound(),
0.9F + RoyalBossEntity.this.random.nextFloat() * 0.2F);
}
@Override
public void onActive(int elapsedTicks) {
RoyalBossEntity.this.applyBiteHit(head);
}
})) {
return;
}
}
protected RoyalAttackLane headLane(RoyalHead head) {
return switch (head.slot()) {
case LEFT -> RoyalAttackLane.LEFT_HEAD;
case CENTER -> RoyalAttackLane.CENTER_HEAD;
case RIGHT -> RoyalAttackLane.RIGHT_HEAD;
};
}
protected RoyalAttackLane beamLane() {
return RoyalAttackLane.BODY;
}
protected boolean beginRoyalAttack(String id, RoyalAttackLane lane, int cooldownTicks, int activeTicks) {
return this.attackScheduler.start(id, lane, cooldownTicks, activeTicks);
}
protected boolean beginRoyalAttack(String id, RoyalAttackLane lane, int cooldownTicks, int windupTicks,
int activeTicks, int recoveryTicks, RoyalAttackScheduler.Action action) {
return this.attackScheduler.start(id, lane, cooldownTicks, windupTicks, activeTicks, recoveryTicks, action);
} }
private void applyBiteHit(RoyalHead head) { private void applyBiteHit(RoyalHead head) {
@@ -578,7 +690,7 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
return AntarchySettings.royalBossBiteReach(); return AntarchySettings.royalBossBiteReach();
} }
private Vec3 headAnchor(RoyalHead head) { protected Vec3 headAnchor(RoyalHead head) {
int partIndex = head.slot().partIndex(); int partIndex = head.slot().partIndex();
Entity[] parts = this.multipartParts; Entity[] parts = this.multipartParts;
if (parts != null && partIndex < parts.length && parts[partIndex] != null) { if (parts != null && partIndex < parts.length && parts[partIndex] != null) {
@@ -596,6 +708,10 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
this.getZ() + forward.z * spec.forwardOffset() + right.z * spec.lateralOffset()); this.getZ() + forward.z * spec.forwardOffset() + right.z * spec.lateralOffset());
} }
protected RoyalHead royalHead(RoyalHead.Slot slot) {
return this.heads[slot.ordinal()];
}
@Override @Override
public void die(DamageSource damageSource) { public void die(DamageSource damageSource) {
if (!this.level().isClientSide) { if (!this.level().isClientSide) {
@@ -667,7 +783,7 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
@Override @Override
public boolean canBeAffected(net.minecraft.world.effect.MobEffectInstance effectInstance) { public boolean canBeAffected(net.minecraft.world.effect.MobEffectInstance effectInstance) {
if (effectInstance.is(com.craisinlord.antarchy.content.AntarchyObjects.INVERTED_EFFECT.get())) { if (effectInstance.is(com.craisinlord.antarchy.content.AntarchyObjects.INVERTED_EFFECT.get())) {
return false; return ThoraxisUndersideManager.isThoraxis(this.level());
} }
return super.canBeAffected(effectInstance); return super.canBeAffected(effectInstance);
} }
@@ -0,0 +1,199 @@
package com.craisinlord.antarchy.content.entity.royal;
import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.content.entity.royal.beam.RoyalBeamElement;
import com.craisinlord.antarchy.config.AntarchySettings;
import net.minecraft.core.BlockPos;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.projectile.ThrowableProjectile;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.EntityHitResult;
import net.minecraft.world.phys.HitResult;
import net.minecraft.world.phys.Vec3;
import software.bernie.geckolib.animatable.GeoEntity;
import software.bernie.geckolib.animatable.instance.AnimatableInstanceCache;
import software.bernie.geckolib.animation.AnimatableManager;
import software.bernie.geckolib.animation.AnimationController;
import software.bernie.geckolib.animation.AnimationState;
import software.bernie.geckolib.animation.PlayState;
import software.bernie.geckolib.animation.RawAnimation;
import software.bernie.geckolib.util.GeckoLibUtil;
/** Shared travelling shell for the King's fireball and iceball. Impact rules stay element-specific. */
public class RoyalElementalProjectileEntity extends ThrowableProjectile implements GeoEntity {
private static final EntityDataAccessor<Integer> ATTACK_TYPE =
SynchedEntityData.defineId(RoyalElementalProjectileEntity.class, EntityDataSerializers.INT);
private static final int FIREBALL = 0;
private static final int ICEBALL = 1;
private static final int MAX_LIFETIME = 100;
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
public RoyalElementalProjectileEntity(EntityType<? extends RoyalElementalProjectileEntity> type, Level level) {
super(type, level);
this.noCulling = true;
}
public static RoyalElementalProjectileEntity create(Level level, LivingEntity owner, RoyalBeamElement element,
Vec3 position, Vec3 direction) {
RoyalElementalProjectileEntity projectile = new RoyalElementalProjectileEntity(
AntarchyObjects.ROYAL_ELEMENTAL_PROJECTILE.get(), level);
projectile.setOwner(owner);
projectile.setPos(position.x, position.y, position.z);
projectile.setAttackType(element == RoyalBeamElement.ICE ? ICEBALL : FIREBALL);
projectile.shoot(direction.x, direction.y, direction.z, element == RoyalBeamElement.ICE ? 0.8F : 1.05F, 0.0F);
return projectile;
}
@Override
protected void defineSynchedData(SynchedEntityData.Builder builder) {
builder.define(ATTACK_TYPE, FIREBALL);
}
public boolean isIceball() {
return this.entityData.get(ATTACK_TYPE) == ICEBALL;
}
private void setAttackType(int type) {
this.entityData.set(ATTACK_TYPE, type);
}
@Override
protected double getDefaultGravity() {
return this.isIceball() ? 0.012D : 0.018D;
}
protected float getInertia() {
return this.isIceball() ? 0.985F : 0.96F;
}
@Override
public void tick() {
if (!this.level().isClientSide && this.tickCount > MAX_LIFETIME) {
this.discard();
return;
}
super.tick();
if (this.level().isClientSide) {
this.level().addParticle(this.isIceball() ? ParticleTypes.SNOWFLAKE : ParticleTypes.FLAME,
this.getX(), this.getY(), this.getZ(), 0.0D, 0.0D, 0.0D);
}
}
@Override
protected void onHitEntity(EntityHitResult result) {
if (!this.level().isClientSide && this.level() instanceof ServerLevel level) {
this.impact(level, result.getLocation());
}
this.discard();
}
@Override
protected void onHitBlock(BlockHitResult result) {
if (!this.level().isClientSide && this.level() instanceof ServerLevel level) {
this.impact(level, result.getLocation());
}
this.discard();
}
@Override
protected void onHit(HitResult result) {
super.onHit(result);
if (!this.level().isClientSide && result.getType() == HitResult.Type.MISS) {
this.discard();
}
}
private void impact(ServerLevel level, Vec3 center) {
if (this.isIceball()) {
impactIce(level, center);
} else {
impactFire(level, center);
}
}
private void impactFire(ServerLevel level, Vec3 center) {
DamageSource source = this.damageSources().mobProjectile(this, this.getOwner() instanceof LivingEntity living ? living : null);
for (LivingEntity living : level.getEntitiesOfClass(LivingEntity.class, new AABB(center, center).inflate(5.0D),
entity -> entity.isAlive() && entity != this.getOwner())) {
double distance = Math.max(0.5D, living.position().distanceTo(center));
float falloff = (float) Math.max(0.25D, 1.0D - distance / 5.0D);
living.hurt(source, (float) (AntarchySettings.kingFireballDamage() * falloff));
living.setRemainingFireTicks(Math.max(living.getRemainingFireTicks(), 120));
}
RoyalBlockDestruction.destroySphere(level, this, center, AntarchySettings.kingFireballRadius(), AntarchySettings.kingElementalTerrainCap(), 80.0D, 0.15F);
level.sendParticles(ParticleTypes.EXPLOSION_EMITTER, center.x, center.y, center.z, 4, 1.5D, 1.5D, 1.5D, 0.0D);
}
private void impactIce(ServerLevel level, Vec3 center) {
DamageSource source = this.damageSources().mobProjectile(this, this.getOwner() instanceof LivingEntity living ? living : null);
for (LivingEntity living : level.getEntitiesOfClass(LivingEntity.class, new AABB(center, center).inflate(5.0D),
entity -> entity.isAlive() && entity != this.getOwner())) {
living.hurt(source, (float) AntarchySettings.kingIceballDamage());
living.setTicksFrozen(Math.min(living.getTicksRequiredToFreeze() + 80, living.getTicksFrozen() + 180));
living.addEffect(new net.minecraft.world.effect.MobEffectInstance(net.minecraft.world.effect.MobEffects.MOVEMENT_SLOWDOWN, 120, 3));
}
coverInIce(level, center, AntarchySettings.kingIceballRadius(), AntarchySettings.kingElementalTerrainCap());
level.sendParticles(ParticleTypes.SNOWFLAKE, center.x, center.y, center.z, 80, 4.0D, 2.0D, 4.0D, 0.08D);
}
private void coverInIce(ServerLevel level, Vec3 center, double radius, int cap) {
int changed = 0;
int minX = (int) Math.floor(center.x - radius);
int minY = (int) Math.floor(center.y - radius);
int minZ = (int) Math.floor(center.z - radius);
int maxX = (int) Math.floor(center.x + radius);
int maxY = (int) Math.floor(center.y + radius);
int maxZ = (int) Math.floor(center.z + radius);
for (BlockPos pos : BlockPos.betweenClosed(minX, minY, minZ, maxX, maxY, maxZ)) {
if (changed >= cap || pos.distToCenterSqr(center.x, center.y, center.z) > radius * radius) continue;
BlockState existing = level.getBlockState(pos);
if (existing.isAir() || !existing.getFluidState().isEmpty() || existing.hasBlockEntity()) continue;
if (existing.getDestroySpeed(level, pos) < 0.0F || existing.getBlock().getExplosionResistance() > 80.0F) continue;
BlockState frozen = pos.getY() >= center.y + 1.0D ? Blocks.SNOW_BLOCK.defaultBlockState() : Blocks.PACKED_ICE.defaultBlockState();
if (existing != frozen && level.setBlockAndUpdate(pos, frozen)) changed++;
}
}
protected net.minecraft.core.particles.ParticleOptions getTrailParticle() {
return this.isIceball() ? ParticleTypes.SNOWFLAKE : ParticleTypes.FLAME;
}
@Override
public void addAdditionalSaveData(CompoundTag tag) {
super.addAdditionalSaveData(tag);
tag.putInt("AttackType", this.entityData.get(ATTACK_TYPE));
}
@Override
public void readAdditionalSaveData(CompoundTag tag) {
super.readAdditionalSaveData(tag);
this.setAttackType(tag.getInt("AttackType"));
}
@Override
public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
controllers.add(new AnimationController<>(this, "elemental_projectile", 0, this::animation));
}
private PlayState animation(AnimationState<RoyalElementalProjectileEntity> state) {
return state.setAndContinue(RawAnimation.begin().thenLoop(this.isIceball() ? "animation.ice_ball" : "animation.fire_ball"));
}
@Override
public AnimatableInstanceCache getAnimatableInstanceCache() {
return this.geoCache;
}
}
@@ -41,10 +41,7 @@ public final class RoyalHead {
private final Slot slot; private final Slot slot;
private int targetId = -1; private int targetId = -1;
private int attackCooldown; private boolean shooting;
private int biteTicks;
private int biteHitTick = -1;
private int shootTicks;
private boolean beamActive; private boolean beamActive;
public RoyalHead(Slot slot) { public RoyalHead(Slot slot) {
@@ -68,22 +65,19 @@ public final class RoyalHead {
} }
public boolean busy() { public boolean busy() {
return this.biteTicks > 0 || this.shootTicks > 0 || this.beamActive; return this.shooting || this.beamActive;
} }
public boolean readyToAttack() { public boolean readyToAttack() {
return !this.busy() && this.attackCooldown <= 0; return !this.busy();
} }
public void startBite(int durationTicks, int hitTick, int cooldownTicks) { public void startShoot() {
this.biteTicks = durationTicks; this.shooting = true;
this.biteHitTick = hitTick;
this.attackCooldown = cooldownTicks;
} }
public void startShoot(int durationTicks, int cooldownTicks) { public void stopShoot() {
this.shootTicks = durationTicks; this.shooting = false;
this.attackCooldown = cooldownTicks;
} }
public void setBeamActive(boolean beamActive) { public void setBeamActive(boolean beamActive) {
@@ -95,36 +89,12 @@ public final class RoyalHead {
} }
public boolean shooting() { public boolean shooting() {
return this.shootTicks > 0; return this.shooting;
}
public boolean tickBiteHit() {
if (this.biteTicks <= 0) {
return false;
}
this.biteTicks--;
boolean hit = this.biteTicks == this.biteHitTick;
if (hit) {
this.biteHitTick = -1;
}
return hit;
}
public void tick() {
if (this.attackCooldown > 0) {
this.attackCooldown--;
}
if (this.shootTicks > 0) {
this.shootTicks--;
}
} }
public void reset() { public void reset() {
this.targetId = -1; this.targetId = -1;
this.attackCooldown = 0; this.shooting = false;
this.biteTicks = 0;
this.biteHitTick = -1;
this.shootTicks = 0;
this.beamActive = false; this.beamActive = false;
} }
} }
@@ -0,0 +1,98 @@
package com.craisinlord.antarchy.content.entity.royal;
import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.config.AntarchySettings;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import software.bernie.geckolib.animatable.GeoEntity;
import software.bernie.geckolib.animatable.instance.AnimatableInstanceCache;
import software.bernie.geckolib.animation.AnimatableManager;
import software.bernie.geckolib.animation.AnimationController;
import software.bernie.geckolib.animation.AnimationState;
import software.bernie.geckolib.animation.PlayState;
import software.bernie.geckolib.animation.RawAnimation;
import software.bernie.geckolib.util.GeckoLibUtil;
public class RoyalIceSpikeEntity extends Entity implements GeoEntity {
private static final RawAnimation EMERGE_ANIMATION = RawAnimation.begin().thenPlay("ice_spike");
private static final RawAnimation IMPACT_ANIMATION = RawAnimation.begin().thenPlay("ice_spike_impact");
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
private boolean impacted;
public RoyalIceSpikeEntity(EntityType<? extends RoyalIceSpikeEntity> type, Level level) {
super(type, level);
this.noPhysics = true;
this.noCulling = true;
}
public static RoyalIceSpikeEntity create(ServerLevel level, Vec3 position) {
RoyalIceSpikeEntity spike = new RoyalIceSpikeEntity(AntarchyObjects.ROYAL_ICE_SPIKE.get(), level);
spike.setPos(position.x, position.y, position.z);
return spike;
}
@Override
protected void defineSynchedData(net.minecraft.network.syncher.SynchedEntityData.Builder builder) {
}
@Override
public void tick() {
super.tick();
if (!this.level().isClientSide && this.tickCount == 1) {
this.triggerAnim("ice_spike", "emerge");
}
if (!this.level().isClientSide && this.tickCount == 6 && !this.impacted) {
this.impacted = true;
this.impact((ServerLevel) this.level());
this.triggerAnim("ice_spike", "impact");
}
if (!this.level().isClientSide && this.tickCount > 45) {
this.discard();
}
if (this.level().isClientSide && this.tickCount < 10) {
this.level().addParticle(ParticleTypes.SNOWFLAKE, this.getX(), this.getY() + this.tickCount * 0.4D, this.getZ(), 0.0D, 0.03D, 0.0D);
}
}
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())) {
living.hurt(source, (float) AntarchySettings.kingIceSpikeDamage());
living.setTicksFrozen(Math.min(living.getTicksRequiredToFreeze() + 60, living.getTicksFrozen() + 100));
living.setDeltaMovement(living.getDeltaMovement().add(0.0D, 0.35D, 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);
}
@Override
protected void readAdditionalSaveData(CompoundTag tag) {
this.impacted = tag.getBoolean("Impacted");
}
@Override
protected void addAdditionalSaveData(CompoundTag tag) {
tag.putBoolean("Impacted", this.impacted);
}
@Override
public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
controllers.add(new AnimationController<>(this, "ice_spike", 0, state -> PlayState.STOP)
.triggerableAnim("emerge", EMERGE_ANIMATION)
.triggerableAnim("impact", IMPACT_ANIMATION));
}
@Override
public AnimatableInstanceCache getAnimatableInstanceCache() {
return this.geoCache;
}
}
@@ -0,0 +1,12 @@
package com.craisinlord.antarchy.content.entity.royal.attack;
public enum RoyalAttackLane {
BODY,
LEFT_HEAD,
CENTER_HEAD,
RIGHT_HEAD,
HAZARD,
SUMMON,
DECREE,
MOVEMENT
}
@@ -0,0 +1,123 @@
package com.craisinlord.antarchy.content.entity.royal.attack;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import java.util.function.ToIntFunction;
import net.minecraft.util.RandomSource;
/** Shared server-side arbitration for Royal attack lifecycles. */
public final class RoyalAttackScheduler {
public interface Action {
default void onStart() {}
default void onActive(int elapsedTicks) {}
default void onComplete() {}
}
private static final class RunningAction {
private final Action action;
private final int windupTicks;
private final int activeTicks;
private final int recoveryTicks;
private int elapsedTicks;
private RunningAction(Action action, int windupTicks, int activeTicks, int recoveryTicks) {
this.action = action;
this.windupTicks = Math.max(0, windupTicks);
this.activeTicks = Math.max(1, activeTicks);
this.recoveryTicks = Math.max(0, recoveryTicks);
}
private int totalTicks() {
return this.windupTicks + this.activeTicks + this.recoveryTicks;
}
}
private final Map<String, Integer> cooldowns = new HashMap<>();
private final Map<RoyalAttackLane, RunningAction> active = new EnumMap<>(RoyalAttackLane.class);
private final Deque<String> recent = new ArrayDeque<>();
public void tick() {
cooldowns.replaceAll((key, value) -> Math.max(0, value - 1));
for (var iterator = active.entrySet().iterator(); iterator.hasNext();) {
RunningAction running = iterator.next().getValue();
running.elapsedTicks++;
int activeStart = running.windupTicks;
int activeEnd = activeStart + running.activeTicks;
if (running.elapsedTicks >= activeStart && running.elapsedTicks < activeEnd) {
running.action.onActive(running.elapsedTicks - activeStart);
}
if (running.elapsedTicks >= running.totalTicks()) {
iterator.remove();
running.action.onComplete();
}
}
}
public boolean ready(String id, RoyalAttackLane lane) {
return cooldowns.getOrDefault(id, 0) <= 0 && !active.containsKey(lane);
}
public boolean laneBusy(RoyalAttackLane lane) {
return active.containsKey(lane);
}
public boolean start(String id, RoyalAttackLane lane, int cooldownTicks, int activeTicks) {
return start(id, lane, cooldownTicks, 0, activeTicks, 0, new Action() {});
}
public boolean start(String id, RoyalAttackLane lane, int cooldownTicks, int windupTicks,
int activeTicks, int recoveryTicks, Action action) {
if (!ready(id, lane)) {
return false;
}
cooldowns.put(id, Math.max(1, cooldownTicks));
active.put(lane, new RunningAction(action, windupTicks, activeTicks, recoveryTicks));
action.onStart();
recent.remove(id);
recent.addFirst(id);
while (recent.size() > 8) {
recent.removeLast();
}
return true;
}
public int repetitionPenalty(String id) {
int penalty = 0;
for (String previous : recent) {
if (previous.equals(id)) {
penalty += 3;
}
}
return penalty;
}
public String chooseWeighted(String[] ids, ToIntFunction<String> weightFunction, RandomSource random) {
int total = 0;
int[] weights = new int[ids.length];
for (int i = 0; i < ids.length; i++) {
weights[i] = Math.max(0, weightFunction.applyAsInt(ids[i]) - repetitionPenalty(ids[i]));
total += weights[i];
}
if (total <= 0) {
return null;
}
int roll = random.nextInt(total);
for (int i = 0; i < ids.length; i++) {
roll -= weights[i];
if (roll < 0) {
return ids[i];
}
}
return null;
}
public int cooldown(String id) {
return cooldowns.getOrDefault(id, 0);
}
public void cancel(RoyalAttackLane lane) {
active.remove(lane);
}
}
@@ -0,0 +1,54 @@
package com.craisinlord.antarchy.content.entity.royal.attack;
import java.util.HashMap;
import java.util.Map;
/** Owns persistent per-tick effects started by a Royal attack. */
public final class RoyalEffectController {
private record Effect(Runnable tick, Runnable end, int duration, int age) {
private Effect advance() {
return new Effect(this.tick, this.end, this.duration, this.age + 1);
}
}
private final Map<String, Effect> active = new HashMap<>();
public void tick() {
for (var iterator = this.active.entrySet().iterator(); iterator.hasNext();) {
var entry = iterator.next();
Effect effect = entry.getValue();
effect.tick().run();
Effect next = effect.advance();
if (next.age() >= next.duration()) {
next.end().run();
iterator.remove();
} else {
entry.setValue(next);
}
}
}
public void start(String id, int duration, Runnable onStart, Runnable tick, Runnable end) {
this.stop(id);
onStart.run();
this.active.put(id, new Effect(tick, end, Math.max(1, duration), 0));
}
public boolean active(String id) {
return this.active.containsKey(id);
}
public void stop(String id) {
Effect effect = this.active.remove(id);
if (effect != null) {
effect.end().run();
}
}
public void clear() {
for (Effect effect : this.active.values()) {
effect.end().run();
}
this.active.clear();
}
}
@@ -26,10 +26,13 @@ import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
public final class RoyalBeamController { public final class RoyalBeamController {
private static final int INITIAL_TRACKING_DELAY_TICKS = 8;
private final Mob owner; private final Mob owner;
private Vec3 serverTarget; private Vec3 serverTarget;
private Vec3 beamEndPosition; private Vec3 beamEndPosition;
private int beamTicks; private int beamTicks;
private int beamAgeTicks;
private int trackingDelayTicks;
private int terrainMutationsThisTick; private int terrainMutationsThisTick;
private static final int ICE_MUTATIONS_PER_TICK = 3; private static final int ICE_MUTATIONS_PER_TICK = 3;
private static final float ICE_IMPACT_RADIUS = 2.0F; private static final float ICE_IMPACT_RADIUS = 2.0F;
@@ -55,12 +58,15 @@ public final class RoyalBeamController {
this.serverTarget = target; this.serverTarget = target;
this.beamEndPosition = target; this.beamEndPosition = target;
this.beamTicks = Math.max(1, durationTicks); this.beamTicks = Math.max(1, durationTicks);
this.beamAgeTicks = 0;
this.trackingDelayTicks = INITIAL_TRACKING_DELAY_TICKS;
} }
public void stop() { public void stop() {
this.serverTarget = null; this.serverTarget = null;
this.beamEndPosition = null; this.beamEndPosition = null;
this.beamTicks = 0; this.beamTicks = 0;
this.beamAgeTicks = 0;
} }
public void tick( public void tick(
@@ -76,8 +82,11 @@ public final class RoyalBeamController {
} }
this.beamTicks--; this.beamTicks--;
this.beamAgeTicks++;
this.terrainMutationsThisTick = 0; this.terrainMutationsThisTick = 0;
if (target != null && target.isAlive()) { if (this.trackingDelayTicks > 0) {
this.trackingDelayTicks--;
} else if (target != null && target.isAlive()) {
Vec3 targetEye = target.getEyePosition(); Vec3 targetEye = target.getEyePosition();
this.serverTarget = this.serverTarget == null this.serverTarget = this.serverTarget == null
? targetEye ? targetEye
@@ -93,7 +102,10 @@ public final class RoyalBeamController {
if (direction.lengthSqr() < 1.0E-7D) { if (direction.lengthSqr() < 1.0E-7D) {
direction = this.owner.getLookAngle(); direction = this.owner.getLookAngle();
} }
Vec3 clipEnd = shootFrom.add(direction.scale(settings.range())); double committedDistance = Math.min(settings.range(), shootFrom.distanceTo(this.serverTarget));
double travelProgress = Mth.clamp(this.beamAgeTicks / (double) settings.travelTicks(), 0.0D, 1.0D);
double currentRange = Math.max(settings.pathStep(), committedDistance * travelProgress);
Vec3 clipEnd = shootFrom.add(direction.scale(currentRange));
HitResult hit = this.owner.level().clip(new ClipContext( HitResult hit = this.owner.level().clip(new ClipContext(
shootFrom, shootFrom,
clipEnd, clipEnd,
@@ -5,6 +5,7 @@ public record RoyalBeamSettings(
double targetTracking, double targetTracking,
double pathStep, double pathStep,
int durationTicks, int durationTicks,
int travelTicks,
int cooldownTicks, int cooldownTicks,
float pathDamageRadius, float pathDamageRadius,
float impactDamageRadius, float impactDamageRadius,
@@ -27,6 +28,7 @@ public record RoyalBeamSettings(
0.1D, 0.1D,
7.5D, 7.5D,
30, 30,
12,
90, 90,
6.0F, 6.0F,
6.0F, 6.0F,
@@ -50,6 +52,7 @@ public record RoyalBeamSettings(
targetTracking = Math.clamp(targetTracking, 0.0D, 1.0D); targetTracking = Math.clamp(targetTracking, 0.0D, 1.0D);
pathStep = Math.max(1.0D, pathStep); pathStep = Math.max(1.0D, pathStep);
durationTicks = Math.max(1, durationTicks); durationTicks = Math.max(1, durationTicks);
travelTicks = Math.max(1, travelTicks);
cooldownTicks = Math.max(1, cooldownTicks); cooldownTicks = Math.max(1, cooldownTicks);
pathDamageRadius = Math.max(0.0F, pathDamageRadius); pathDamageRadius = Math.max(0.0F, pathDamageRadius);
impactDamageRadius = Math.max(0.0F, impactDamageRadius); impactDamageRadius = Math.max(0.0F, impactDamageRadius);
@@ -5,11 +5,9 @@ import net.minecraft.world.entity.LivingEntity;
public final class AdvanceDecree implements RoyalDecree { public final class AdvanceDecree implements RoyalDecree {
private static final double REQUIRED_RANGE = 14.0D; private static final double REQUIRED_RANGE = 14.0D;
public String translationKey() { return "decree.antarchy.advance"; } public String translationKey() { return "decree.antarchy.advance"; }
public void apply(ServerLevel level, KingEntity king, LivingEntity target) { public void apply(ServerLevel level, KingEntity king, LivingEntity target) { king.setDecreeRetreatPressure(true); }
king.setDecreeRetreatPressure(true); @Override public Evaluation evaluate(ServerLevel level, KingEntity king, LivingEntity target) {
if (target.distanceTo(king) > REQUIRED_RANGE) { return target.distanceTo(king) > REQUIRED_RANGE ? Evaluation.VIOLATED : Evaluation.COMPLIANT;
king.invokeJudgment(target);
}
} }
@Override @Override
public int contextWeight(KingEntity king, LivingEntity target) { public int contextWeight(KingEntity king, LivingEntity target) {
@@ -5,6 +5,9 @@ import net.minecraft.world.entity.LivingEntity;
public final class ComeNoCloserDecree implements RoyalDecree { public final class ComeNoCloserDecree implements RoyalDecree {
public String translationKey() { return "decree.antarchy.come_no_closer"; } public String translationKey() { return "decree.antarchy.come_no_closer"; }
public void apply(ServerLevel level, KingEntity king, LivingEntity target) { } public void apply(ServerLevel level, KingEntity king, LivingEntity target) { }
@Override public Evaluation evaluate(ServerLevel level, KingEntity king, LivingEntity target) {
return target.distanceTo(king) <= 16.0D ? Evaluation.VIOLATED : Evaluation.COMPLIANT;
}
@Override @Override
public int contextWeight(KingEntity king, LivingEntity target) { public int contextWeight(KingEntity king, LivingEntity target) {
return 4 + king.behaviorScore(KingEntity.Behavior.HUGGING); return 4 + king.behaviorScore(KingEntity.Behavior.HUGGING);
@@ -4,7 +4,10 @@ import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.LivingEntity;
public final class DoNotRunDecree implements RoyalDecree { public final class DoNotRunDecree implements RoyalDecree {
public String translationKey() { return "decree.antarchy.do_not_run"; } public String translationKey() { return "decree.antarchy.do_not_run"; }
public void apply(ServerLevel level, KingEntity king, LivingEntity target) { if (target.isSprinting()) king.invokeJudgment(target); } public void apply(ServerLevel level, KingEntity king, LivingEntity target) { }
@Override public Evaluation evaluate(ServerLevel level, KingEntity king, LivingEntity target) {
return target.isSprinting() ? Evaluation.VIOLATED : Evaluation.COMPLIANT;
}
@Override @Override
public int contextWeight(KingEntity king, LivingEntity target) { public int contextWeight(KingEntity king, LivingEntity target) {
return 4 + king.behaviorScore(KingEntity.Behavior.SPRINTING); return 4 + king.behaviorScore(KingEntity.Behavior.SPRINTING);
@@ -13,4 +13,8 @@ public final class FightMeCowardDecree implements RoyalDecree {
target.hasImpulse = true; target.hasImpulse = true;
} }
} }
@Override
public Evaluation evaluate(ServerLevel level, KingEntity king, LivingEntity target) {
return target.distanceTo(king) > 28.0D ? Evaluation.VIOLATED : Evaluation.COMPLIANT;
}
} }
@@ -5,10 +5,9 @@ import net.minecraft.world.entity.LivingEntity;
public final class KeepYourDistanceDecree implements RoyalDecree { public final class KeepYourDistanceDecree implements RoyalDecree {
private static final double INNER_RADIUS = 16.0D; private static final double INNER_RADIUS = 16.0D;
public String translationKey() { return "decree.antarchy.keep_your_distance"; } public String translationKey() { return "decree.antarchy.keep_your_distance"; }
public void apply(ServerLevel level, KingEntity king, LivingEntity target) { public void apply(ServerLevel level, KingEntity king, LivingEntity target) { }
if (target.distanceTo(king) <= INNER_RADIUS) { @Override public Evaluation evaluate(ServerLevel level, KingEntity king, LivingEntity target) {
king.invokeJudgment(target); return target.distanceTo(king) <= INNER_RADIUS ? Evaluation.VIOLATED : Evaluation.COMPLIANT;
}
} }
@Override @Override
public int contextWeight(KingEntity king, LivingEntity target) { public int contextWeight(KingEntity king, LivingEntity target) {
@@ -12,7 +12,7 @@ public final class KneelDecree implements RoyalDecree {
UUID id = target.getUUID(); UUID id = target.getUUID();
if (target.isCrouching()) { deadlines.remove(id); king.clearActiveDecree(target); return; } if (target.isCrouching()) { deadlines.remove(id); king.clearActiveDecree(target); return; }
int deadline = deadlines.computeIfAbsent(id, ignored -> target.tickCount + 100); int deadline = deadlines.computeIfAbsent(id, ignored -> target.tickCount + 100);
if (target.tickCount >= deadline) { king.invokeJudgment(target); deadlines.put(id, target.tickCount + 100); } if (target.tickCount >= deadline) { king.failActiveDecree(target); }
} }
@Override @Override
@@ -5,9 +5,19 @@ import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.LivingEntity;
public interface RoyalDecree { public interface RoyalDecree {
enum Evaluation {
COMPLIANT,
VIOLATED,
COMPLETE
}
String translationKey(); String translationKey();
void apply(ServerLevel level, KingEntity king, LivingEntity target); void apply(ServerLevel level, KingEntity king, LivingEntity target);
default Evaluation evaluate(ServerLevel level, KingEntity king, LivingEntity target) {
return Evaluation.COMPLIANT;
}
default int countdownTicks(LivingEntity target) { default int countdownTicks(LivingEntity target) {
return -1; return -1;
} }
@@ -6,8 +6,9 @@ public final class ShowNoMercyDecree implements RoyalDecree {
private static final long IDLE_DAMAGE_LIMIT_TICKS = 80L; private static final long IDLE_DAMAGE_LIMIT_TICKS = 80L;
public String translationKey() { return "decree.antarchy.show_no_mercy"; } public String translationKey() { return "decree.antarchy.show_no_mercy"; }
public void apply(ServerLevel level, KingEntity king, LivingEntity target) { public void apply(ServerLevel level, KingEntity king, LivingEntity target) {
if (king.ticksSincePlayerDamage(target) > IDLE_DAMAGE_LIMIT_TICKS) { }
king.invokeJudgment(target); @Override public Evaluation evaluate(ServerLevel level, KingEntity king, LivingEntity target) {
} return king.ticksSincePlayerDamage(target) > IDLE_DAMAGE_LIMIT_TICKS
? Evaluation.VIOLATED : Evaluation.COMPLIANT;
} }
} }
@@ -4,7 +4,10 @@ import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.LivingEntity;
public final class SkyIsMineDecree implements RoyalDecree { public final class SkyIsMineDecree implements RoyalDecree {
public String translationKey() { return "decree.antarchy.the_sky_is_mine"; } public String translationKey() { return "decree.antarchy.the_sky_is_mine"; }
public void apply(ServerLevel level, KingEntity king, LivingEntity target) { if (!target.onGround() && !target.isPassenger()) king.invokeJudgment(target); } public void apply(ServerLevel level, KingEntity king, LivingEntity target) { }
@Override public Evaluation evaluate(ServerLevel level, KingEntity king, LivingEntity target) {
return !target.onGround() && !target.isPassenger() ? Evaluation.VIOLATED : Evaluation.COMPLIANT;
}
@Override @Override
public int contextWeight(KingEntity king, LivingEntity target) { public int contextWeight(KingEntity king, LivingEntity target) {
return 4 + king.behaviorScore(KingEntity.Behavior.AIRBORNE); return 4 + king.behaviorScore(KingEntity.Behavior.AIRBORNE);
@@ -5,8 +5,8 @@ import net.minecraft.world.entity.LivingEntity;
public final class StandTallDecree implements RoyalDecree { public final class StandTallDecree implements RoyalDecree {
public String translationKey() { return "decree.antarchy.stand_tall"; } public String translationKey() { return "decree.antarchy.stand_tall"; }
public void apply(ServerLevel level, KingEntity king, LivingEntity target) { public void apply(ServerLevel level, KingEntity king, LivingEntity target) {
if (target.isCrouching()) { }
king.invokeJudgment(target); @Override public Evaluation evaluate(ServerLevel level, KingEntity king, LivingEntity target) {
} return target.isCrouching() ? Evaluation.VIOLATED : Evaluation.COMPLIANT;
} }
} }
@@ -12,9 +12,11 @@ public final class StandYourGroundDecree implements RoyalDecree {
public String translationKey() { return "decree.antarchy.stand_your_ground"; } public String translationKey() { return "decree.antarchy.stand_your_ground"; }
public void apply(ServerLevel level, KingEntity king, LivingEntity target) { public void apply(ServerLevel level, KingEntity king, LivingEntity target) {
Vec3 anchor = this.anchors.computeIfAbsent(target.getUUID(), ignored -> target.position()); Vec3 anchor = this.anchors.computeIfAbsent(target.getUUID(), ignored -> target.position());
if (target.position().distanceToSqr(anchor) > ALLOWED_DRIFT * ALLOWED_DRIFT) {
king.invokeJudgment(target);
} }
@Override public Evaluation evaluate(ServerLevel level, KingEntity king, LivingEntity target) {
Vec3 anchor = this.anchors.computeIfAbsent(target.getUUID(), ignored -> target.position());
return target.position().distanceToSqr(anchor) > ALLOWED_DRIFT * ALLOWED_DRIFT
? Evaluation.VIOLATED : Evaluation.COMPLIANT;
} }
@Override @Override
public void onEnded() { public void onEnded() {
@@ -8,6 +8,7 @@ import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.syncher.EntityDataAccessor; import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers; import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData; import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.network.protocol.game.ClientboundSetEntityMotionPacket;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundEvents; import net.minecraft.sounds.SoundEvents;
@@ -66,6 +67,8 @@ public class WindVortexEntity extends Entity {
private static final String TRAVEL_Z_KEY = "TravelZ"; private static final String TRAVEL_Z_KEY = "TravelZ";
private static final String TRAVELLING_KEY = "Travelling"; private static final String TRAVELLING_KEY = "Travelling";
private static final String HOMING_KEY = "Homing"; private static final String HOMING_KEY = "Homing";
private static final String ANCHORED_KEY = "Anchored";
private static final String BOUNCE_ON_IMPACT_KEY = "BounceOnImpact";
private static final double BASE_RADIUS = 0.35D; private static final double BASE_RADIUS = 0.35D;
private static final double DRIFT_FRICTION = 0.995D; private static final double DRIFT_FRICTION = 0.995D;
@@ -82,15 +85,20 @@ public class WindVortexEntity extends Entity {
} }
private int age; private int age;
private int durationTicks = 140; private int durationTicks = 240;
private double pullStrength = 0.32D; private double pullStrength = 0.32D;
private double launchStrength = 1.0D; private double launchStrength = 1.0D;
private float damageOverride = -1.0F; private float damageOverride = -1.0F;
private Vec3 travelVelocity = Vec3.ZERO; private Vec3 travelVelocity = Vec3.ZERO;
private boolean travelling = false; private boolean travelling = false;
private boolean homing = false; private boolean homing = false;
private boolean bounceOnImpact = false;
private int travelTicksRemaining; private int travelTicksRemaining;
private int fadeOutTicksRemaining; private int fadeOutTicksRemaining;
private boolean anchoredAtBlock;
private double flowTurnRate;
private double flowTurnTarget;
private int flowTurnChangeTicks;
private final Map<UUID, Double> carriedProgress = new HashMap<>(); private final Map<UUID, Double> carriedProgress = new HashMap<>();
@Nullable @Nullable
private UUID ownerUuid; private UUID ownerUuid;
@@ -148,6 +156,18 @@ public class WindVortexEntity extends Entity {
return; return;
} }
if (this.anchoredAtBlock) {
this.setDeltaMovement(Vec3.ZERO);
if ((this.tickCount & 1) == 0) {
this.applyVortexForces();
}
this.age++;
if (this.age >= this.durationTicks) {
this.fadeOut();
}
return;
}
boolean lensVortex = this.getMode() != VortexMode.UPWARD; boolean lensVortex = this.getMode() != VortexMode.UPWARD;
if (!lensVortex && (this.isInLava() || this.isInsideSolidBlock())) { if (!lensVortex && (this.isInLava() || this.isInsideSolidBlock())) {
this.fadeOut(); this.fadeOut();
@@ -164,14 +184,20 @@ public class WindVortexEntity extends Entity {
if (this.homing) { if (this.homing) {
this.steerTowardPrey(); this.steerTowardPrey();
} }
if (this.bounceOnImpact) {
this.updateFluidDrift();
}
Vec3 from = this.position(); Vec3 from = this.position();
Vec3 to = from.add(this.travelVelocity); Vec3 to = from.add(this.travelVelocity);
HitResult blockHit = this.level().clip( HitResult blockHit = this.level().clip(
new ClipContext(from, to, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, this)); new ClipContext(from, to, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, this));
if (blockHit.getType() != HitResult.Type.MISS) { if (blockHit.getType() != HitResult.Type.MISS) {
this.fadeOut(); if (this.bounceOnImpact) {
return; this.flowAroundBlock(blockHit);
} else {
this.anchorAtBlock(blockHit);
} }
} else {
this.setDeltaMovement(this.travelVelocity); this.setDeltaMovement(this.travelVelocity);
this.move(net.minecraft.world.entity.MoverType.SELF, this.travelVelocity); this.move(net.minecraft.world.entity.MoverType.SELF, this.travelVelocity);
if (--this.travelTicksRemaining <= 0) { if (--this.travelTicksRemaining <= 0) {
@@ -179,6 +205,7 @@ public class WindVortexEntity extends Entity {
this.noPhysics = false; this.noPhysics = false;
this.setDeltaMovement(this.travelVelocity); this.setDeltaMovement(this.travelVelocity);
} }
}
} else if (!lensVortex) { } else if (!lensVortex) {
Vec3 current = this.getDeltaMovement(); Vec3 current = this.getDeltaMovement();
double downwardSpeed = Math.min(MAX_FALL_SPEED, Math.max(MINIMUM_DOWNWARD_DRIFT, double downwardSpeed = Math.min(MAX_FALL_SPEED, Math.max(MINIMUM_DOWNWARD_DRIFT,
@@ -192,11 +219,15 @@ public class WindVortexEntity extends Entity {
HitResult blockHit = this.level().clip( HitResult blockHit = this.level().clip(
new ClipContext(from, to, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, this)); new ClipContext(from, to, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, this));
if (blockHit.getType() != HitResult.Type.MISS) { if (blockHit.getType() != HitResult.Type.MISS) {
this.fadeOut(); if (this.bounceOnImpact) {
return; this.flowAroundBlock(blockHit);
} else {
this.anchorAtBlock(blockHit);
} }
} else {
this.setDeltaMovement(drift); this.setDeltaMovement(drift);
this.move(net.minecraft.world.entity.MoverType.SELF, drift); this.move(net.minecraft.world.entity.MoverType.SELF, drift);
}
} else { } else {
Vec3 drift = this.getDeltaMovement().multiply(DRIFT_FRICTION, DRIFT_FRICTION, DRIFT_FRICTION); Vec3 drift = this.getDeltaMovement().multiply(DRIFT_FRICTION, DRIFT_FRICTION, DRIFT_FRICTION);
this.setDeltaMovement(drift); this.setDeltaMovement(drift);
@@ -256,6 +287,73 @@ public class WindVortexEntity extends Entity {
} }
} }
private void anchorAtBlock(HitResult hit) {
this.travelling = false;
this.noPhysics = false;
this.travelVelocity = Vec3.ZERO;
this.setDeltaMovement(Vec3.ZERO);
if (hit instanceof net.minecraft.world.phys.BlockHitResult blockHit) {
Vec3 normal = Vec3.atLowerCornerOf(blockHit.getDirection().getNormal());
this.setPos(blockHit.getLocation().add(normal.scale(0.05D)));
}
this.anchoredAtBlock = true;
}
private void updateFluidDrift() {
if (--this.flowTurnChangeTicks <= 0) {
this.flowTurnChangeTicks = 24 + this.random.nextInt(32);
this.flowTurnTarget = (this.random.nextDouble() * 2.0D - 1.0D) * 0.012D;
}
this.flowTurnRate = Mth.lerp(0.08D, this.flowTurnRate, this.flowTurnTarget);
Vec3 horizontal = new Vec3(this.travelVelocity.x, 0.0D, this.travelVelocity.z);
if (horizontal.lengthSqr() > 1.0E-6D) {
Vec3 turned = horizontal.yRot((float) this.flowTurnRate);
this.travelVelocity = new Vec3(turned.x, this.travelVelocity.y, turned.z);
}
}
private void flowAroundBlock(HitResult hit) {
if (!(hit instanceof net.minecraft.world.phys.BlockHitResult blockHit)) {
return;
}
Vec3 normal = Vec3.atLowerCornerOf(blockHit.getDirection().getNormal());
Vec3 incoming = this.travelVelocity.lengthSqr() > 1.0E-6D
? this.travelVelocity
: this.getDeltaMovement();
double speed = Mth.clamp(incoming.length(), 0.2D, 0.9D);
Vec3 flow = incoming;
if (normal.y > 0.5D) {
flow = new Vec3(incoming.x, 0.0D, incoming.z);
} else if (normal.y < -0.5D) {
flow = new Vec3(incoming.x, 0.0D, incoming.z).add(0.0D, -0.08D, 0.0D);
} else {
Vec3 tangent = incoming.subtract(normal.scale(incoming.dot(normal)));
if (tangent.lengthSqr() < 1.0E-6D) {
tangent = new Vec3(-normal.z, 0.0D, normal.x);
if (tangent.lengthSqr() < 1.0E-6D) {
tangent = new Vec3(0.0D, 0.0D, 1.0D);
}
}
flow = tangent.normalize().scale(speed);
}
if (flow.lengthSqr() < 1.0E-6D) {
this.travelVelocity = Vec3.ZERO;
this.setDeltaMovement(Vec3.ZERO);
this.travelling = false;
this.noPhysics = false;
return;
}
this.setPos(blockHit.getLocation().add(normal.scale(0.08D)));
this.travelVelocity = flow.normalize().scale(speed * 0.82D);
this.setDeltaMovement(this.travelVelocity);
this.noPhysics = true;
if (--this.travelTicksRemaining <= 0) {
this.travelling = false;
this.noPhysics = false;
}
}
@Override @Override
public void handleEntityEvent(byte id) { public void handleEntityEvent(byte id) {
if (id == 60) { if (id == 60) {
@@ -431,18 +529,13 @@ public class WindVortexEntity extends Entity {
updatedMovement = new Vec3(updatedMovement.x, Math.max(updatedMovement.y, minRise), updatedMovement.z); updatedMovement = new Vec3(updatedMovement.x, Math.max(updatedMovement.y, minRise), updatedMovement.z);
entity.setOnGround(false); entity.setOnGround(false);
} }
entity.setDeltaMovement(updatedMovement);
if (entity instanceof Player) { if (entity instanceof Player) {
Vec3 before = entity.position();
Vec3 playerCarry = wanted.scale(0.65D);
entity.move(net.minecraft.world.entity.MoverType.SELF, playerCarry);
if (entity.position().distanceToSqr(before) < 1.0E-6D && playerCarry.lengthSqr() > 1.0E-6D) {
entity.setPos(before.add(playerCarry));
}
entity.setOnGround(false); entity.setOnGround(false);
} }
entity.setDeltaMovement(updatedMovement);
entity.fallDistance = 0.0F; entity.fallDistance = 0.0F;
entity.hasImpulse = true; entity.hasImpulse = true;
this.syncPlayerMotion(entity);
} }
private void launch(Entity entity, Vec3 radialVector, double progress, Basis basis) { private void launch(Entity entity, Vec3 radialVector, double progress, Basis basis) {
@@ -452,6 +545,7 @@ public class WindVortexEntity extends Entity {
double scale = this.launchStrength * Mth.clamp(this.getVortexHeight() / 5.0D, 0.5D, 2.5D); double scale = this.launchStrength * Mth.clamp(this.getVortexHeight() / 5.0D, 0.5D, 2.5D);
entity.setDeltaMovement(tangent.scale(0.72D * scale).add(radial.scale(0.34D * scale)).add(basis.axis.scale((0.78D + progress * 0.28D) * scale))); entity.setDeltaMovement(tangent.scale(0.72D * scale).add(radial.scale(0.34D * scale)).add(basis.axis.scale((0.78D + progress * 0.28D) * scale)));
entity.hasImpulse = true; entity.hasImpulse = true;
this.syncPlayerMotion(entity);
} }
private void hurtCaughtEntity(Entity entity) { private void hurtCaughtEntity(Entity entity) {
@@ -487,12 +581,20 @@ public class WindVortexEntity extends Entity {
entity.fallDistance = 0.0F; entity.fallDistance = 0.0F;
entity.hasImpulse = true; entity.hasImpulse = true;
entity.hurtMarked = true; entity.hurtMarked = true;
this.syncPlayerMotion(entity);
if (this.isDamaging() && entity instanceof LivingEntity living if (this.isDamaging() && entity instanceof LivingEntity living
&& !living.getType().is(AntarchyTags.Entities.WIND_VORTEX_IMMUNE)) { && !living.getType().is(AntarchyTags.Entities.WIND_VORTEX_IMMUNE)) {
living.hurt(this.damageSource(), this.resolveDamage()); living.hurt(this.damageSource(), this.resolveDamage());
} }
} }
private void syncPlayerMotion(Entity entity) {
if (entity instanceof ServerPlayer player) {
player.hurtMarked = true;
player.connection.send(new ClientboundSetEntityMotionPacket(player));
}
}
private DamageSource damageSource() { private DamageSource damageSource() {
Entity owner = this.getOwnerEntity(); Entity owner = this.getOwnerEntity();
if (owner instanceof LivingEntity living) { if (owner instanceof LivingEntity living) {
@@ -582,6 +684,10 @@ public class WindVortexEntity extends Entity {
this.homing = homing; this.homing = homing;
} }
public void setBounceOnImpact(boolean bounceOnImpact) {
this.bounceOnImpact = bounceOnImpact;
}
public void setVortexStrengths(double pullStrength, double launchStrength) { public void setVortexStrengths(double pullStrength, double launchStrength) {
this.pullStrength = Math.max(0.0D, pullStrength); this.pullStrength = Math.max(0.0D, pullStrength);
this.launchStrength = Math.max(0.0D, launchStrength); this.launchStrength = Math.max(0.0D, launchStrength);
@@ -630,6 +736,8 @@ public class WindVortexEntity extends Entity {
this.setAxis(new Vec3(tag.getFloat(AXIS_X_KEY), tag.getFloat(AXIS_Y_KEY), tag.getFloat(AXIS_Z_KEY))); this.setAxis(new Vec3(tag.getFloat(AXIS_X_KEY), tag.getFloat(AXIS_Y_KEY), tag.getFloat(AXIS_Z_KEY)));
this.travelling = tag.getBoolean(TRAVELLING_KEY); this.travelling = tag.getBoolean(TRAVELLING_KEY);
this.homing = tag.getBoolean(HOMING_KEY); this.homing = tag.getBoolean(HOMING_KEY);
this.bounceOnImpact = tag.getBoolean(BOUNCE_ON_IMPACT_KEY);
this.anchoredAtBlock = tag.getBoolean(ANCHORED_KEY);
this.travelVelocity = new Vec3(tag.getDouble(TRAVEL_X_KEY), tag.getDouble(TRAVEL_Y_KEY), tag.getDouble(TRAVEL_Z_KEY)); this.travelVelocity = new Vec3(tag.getDouble(TRAVEL_X_KEY), tag.getDouble(TRAVEL_Y_KEY), tag.getDouble(TRAVEL_Z_KEY));
if (this.travelling) { if (this.travelling) {
this.noPhysics = true; this.noPhysics = true;
@@ -654,6 +762,8 @@ public class WindVortexEntity extends Entity {
tag.putFloat(AXIS_Z_KEY, (float) this.getAxis().z); tag.putFloat(AXIS_Z_KEY, (float) this.getAxis().z);
tag.putBoolean(TRAVELLING_KEY, this.travelling); tag.putBoolean(TRAVELLING_KEY, this.travelling);
tag.putBoolean(HOMING_KEY, this.homing); tag.putBoolean(HOMING_KEY, this.homing);
tag.putBoolean(BOUNCE_ON_IMPACT_KEY, this.bounceOnImpact);
tag.putBoolean(ANCHORED_KEY, this.anchoredAtBlock);
tag.putDouble(TRAVEL_X_KEY, this.travelVelocity.x); tag.putDouble(TRAVEL_X_KEY, this.travelVelocity.x);
tag.putDouble(TRAVEL_Y_KEY, this.travelVelocity.y); tag.putDouble(TRAVEL_Y_KEY, this.travelVelocity.y);
tag.putDouble(TRAVEL_Z_KEY, this.travelVelocity.z); tag.putDouble(TRAVEL_Z_KEY, this.travelVelocity.z);
@@ -1,6 +1,8 @@
package com.craisinlord.antarchy.content.gravity; package com.craisinlord.antarchy.content.gravity;
import net.minecraft.util.Mth; import net.minecraft.util.Mth;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.Mob;
import net.minecraft.world.level.PathNavigationRegion; import net.minecraft.world.level.PathNavigationRegion;
import net.minecraft.world.level.pathfinder.Node; import net.minecraft.world.level.pathfinder.Node;
@@ -9,6 +11,11 @@ import net.minecraft.world.level.pathfinder.PathfindingContext;
import net.minecraft.world.level.pathfinder.WalkNodeEvaluator; import net.minecraft.world.level.pathfinder.WalkNodeEvaluator;
public class GravityWalkNodeEvaluator extends WalkNodeEvaluator { public class GravityWalkNodeEvaluator extends WalkNodeEvaluator {
private static final long LOG_TIME_THRESHOLD_NANOS = 25_000_000L;
private long antarchy$prepareStartNanos;
private int antarchy$nodeProbeCount;
private static final int[] START_Y_SEARCH = {0, 1, -1, 2, -2}; private static final int[] START_Y_SEARCH = {0, 1, -1, 2, -2};
private static final int[][] INVERTED_CARDINAL_NEIGHBOR_OFFSETS = { private static final int[][] INVERTED_CARDINAL_NEIGHBOR_OFFSETS = {
{1, 0, 0}, {1, 0, 0},
@@ -26,10 +33,23 @@ public class GravityWalkNodeEvaluator extends WalkNodeEvaluator {
@Override @Override
public void prepare(PathNavigationRegion sourceIn, Mob mob) { public void prepare(PathNavigationRegion sourceIn, Mob mob) {
super.prepare(sourceIn, mob); super.prepare(sourceIn, mob);
this.antarchy$prepareStartNanos = System.nanoTime();
this.antarchy$nodeProbeCount = 0;
} }
@Override @Override
public void done() { public void done() {
if (this.mob != null && AntarchyGravityApi.isGravityInverted(this.mob)) {
long elapsedNanos = System.nanoTime() - this.antarchy$prepareStartNanos;
if (elapsedNanos > LOG_TIME_THRESHOLD_NANOS) {
com.craisinlord.antarchy.Antarchy.LOGGER.warn(
"[antarchy-gravity] slow inverted pathfind: type={} uuid={} pos=({}, {}, {}) nodeProbes={} elapsedMs={}",
this.mob.getType(), this.mob.getUUID(),
this.mob.getX(), this.mob.getY(), this.mob.getZ(),
this.antarchy$nodeProbeCount, elapsedNanos / 1_000_000.0
);
}
}
super.done(); super.done();
} }
@@ -43,10 +63,9 @@ public class GravityWalkNodeEvaluator extends WalkNodeEvaluator {
int z = Mth.floor(this.mob.getZ()); int z = Mth.floor(this.mob.getZ());
int y = Mth.floor(this.mob.getY() + this.mob.getBbHeight()) - 1; int y = Mth.floor(this.mob.getY() + this.mob.getBbHeight()) - 1;
Node node = this.antarchy$findStartNode(x, y, z); Node node = this.antarchy$findStartNode(x, y, z);
if (node == null) { // A floor-oriented start is a different coordinate model and must not
return super.getStart(); // be mixed into an inverted search. PathFinder treats null as a clean
} // no-path result.
return node; return node;
} }
@@ -66,7 +85,8 @@ public class GravityWalkNodeEvaluator extends WalkNodeEvaluator {
} }
private Node antarchy$createStartNode(int x, int y, int z) { private Node antarchy$createStartNode(int x, int y, int z) {
if (!this.antarchy$hasCeilingSupport(x, y, z)) { this.antarchy$nodeProbeCount++;
if (!this.antarchy$hasCeilingSupport(this.currentContext, x, y, z)) {
return null; return null;
} }
@@ -82,9 +102,16 @@ public class GravityWalkNodeEvaluator extends WalkNodeEvaluator {
return node; return node;
} }
private boolean antarchy$hasCeilingSupport(int x, int y, int z) { private boolean antarchy$hasCeilingSupport(PathfindingContext context, int x, int y, int z) {
PathType ceilingType = this.getPathType(this.currentContext, x, y + 1, z); for (int dx = 0; dx < this.entityWidth; dx++) {
return this.mob.getPathfindingMalus(ceilingType) < 0.0F; for (int dz = 0; dz < this.entityDepth; dz++) {
BlockPos ceilingPos = new BlockPos(x + dx, y + 1, z + dz);
if (!context.level().getBlockState(ceilingPos).isFaceSturdy(context.level(), ceilingPos, Direction.DOWN)) {
return false;
}
}
}
return true;
} }
@Override @Override
@@ -93,8 +120,7 @@ public class GravityWalkNodeEvaluator extends WalkNodeEvaluator {
return super.getPathTypeOfMob(context, x, y, z, mob); return super.getPathTypeOfMob(context, x, y, z, mob);
} }
PathType ceilingType = this.getPathType(context, x, y + 1, z); if (!this.antarchy$hasCeilingSupport(context, x, y, z)) {
if (mob.getPathfindingMalus(ceilingType) >= 0.0F) {
return PathType.OPEN; return PathType.OPEN;
} }
@@ -145,6 +171,18 @@ public class GravityWalkNodeEvaluator extends WalkNodeEvaluator {
} }
} }
// Relative "up" for an inverted mob is world-Y down. Keep both
// vertical directions available so a one-block ceiling step can be
// represented in the path instead of trapping the mob below it.
Node stepTowardInvertedUp = this.antarchy$getNeighbor(current.x, current.y - 1, current.z);
if (stepTowardInvertedUp != null) {
neighbors[count++] = stepTowardInvertedUp;
}
Node stepTowardInvertedDown = this.antarchy$getNeighbor(current.x, current.y + 1, current.z);
if (stepTowardInvertedDown != null) {
neighbors[count++] = stepTowardInvertedDown;
}
return count; return count;
} }
@@ -159,12 +197,16 @@ public class GravityWalkNodeEvaluator extends WalkNodeEvaluator {
} }
private Node antarchy$getNeighbor(int x, int y, int z) { private Node antarchy$getNeighbor(int x, int y, int z) {
this.antarchy$nodeProbeCount++;
PathType type = this.getPathTypeOfMob(this.currentContext, x, y, z, this.mob); PathType type = this.getPathTypeOfMob(this.currentContext, x, y, z, this.mob);
if (this.mob.getPathfindingMalus(type) < 0.0F) { if (type == PathType.OPEN || this.mob.getPathfindingMalus(type) < 0.0F) {
return null; return null;
} }
Node node = this.getNode(x, y, z); Node node = this.getNode(x, y, z);
if (node.closed) {
return null;
}
node.type = type; node.type = type;
node.costMalus = this.mob.getPathfindingMalus(type); node.costMalus = this.mob.getPathfindingMalus(type);
return node; return node;
@@ -137,6 +137,7 @@ public class EyeOfTheStormItem extends Item implements GeoItem {
vortex.setDamageOverride(AntarchySettings.eyeOfTheStormSurgeDamage()); vortex.setDamageOverride(AntarchySettings.eyeOfTheStormSurgeDamage());
vortex.setTravel(dir.scale(SURGE_PROJECTILE_SPEED)); vortex.setTravel(dir.scale(SURGE_PROJECTILE_SPEED));
vortex.setHoming(true); vortex.setHoming(true);
vortex.setBounceOnImpact(true);
level.addFreshEntity(vortex); level.addFreshEntity(vortex);
level.playSound(null, origin.x, origin.y, origin.z, level.playSound(null, origin.x, origin.y, origin.z,
@@ -79,12 +79,14 @@ public class KrakensGraspItem extends TridentItem {
public static void strikeLightning(LivingEntity target, ServerLevel level) { public static void strikeLightning(LivingEntity target, ServerLevel level) {
strikeLightningAt(level, target.getX(), target.getY(), target.getZ()); strikeLightningAt(level, target.getX(), target.getY(), target.getZ());
target.hurt(level.damageSources().lightningBolt(), (float) AntarchySettings.krakensGraspLightningDamage());
level.sendParticles(ParticleTypes.ELECTRIC_SPARK, target.getX(), target.getY(0.7D), target.getZ(), 10, 0.25D, 0.25D, 0.25D, 0.02D); level.sendParticles(ParticleTypes.ELECTRIC_SPARK, target.getX(), target.getY(0.7D), target.getZ(), 10, 0.25D, 0.25D, 0.25D, 0.02D);
} }
public static void strikeLightningAt(ServerLevel level, double x, double y, double z) { public static void strikeLightningAt(ServerLevel level, double x, double y, double z) {
LightningBolt lightningBolt = new LightningBolt(net.minecraft.world.entity.EntityType.LIGHTNING_BOLT, level); LightningBolt lightningBolt = new LightningBolt(net.minecraft.world.entity.EntityType.LIGHTNING_BOLT, level);
lightningBolt.moveTo(x, y, z); lightningBolt.moveTo(x, y, z);
lightningBolt.setVisualOnly(true);
level.addFreshEntity(lightningBolt); level.addFreshEntity(lightningBolt);
level.sendParticles(ParticleTypes.ELECTRIC_SPARK, x, y + 0.2D, z, 10, 0.25D, 0.25D, 0.25D, 0.02D); level.sendParticles(ParticleTypes.ELECTRIC_SPARK, x, y + 0.2D, z, 10, 0.25D, 0.25D, 0.25D, 0.02D);
} }
@@ -21,7 +21,9 @@ import net.minecraft.world.level.Level;
public final class ManticoreWingsItem extends ElytraItem { public final class ManticoreWingsItem extends ElytraItem {
private static final ResourceLocation REPAIR_MATERIAL_ID = ResourceLocation.fromNamespaceAndPath("antarchy", "manticore_wing"); private static final ResourceLocation REPAIR_MATERIAL_ID = ResourceLocation.fromNamespaceAndPath("antarchy", "manticore_wing");
private static final int DAMAGE_COOLDOWN_TICKS = 10; private static final int DAMAGE_COOLDOWN_TICKS = 10;
private static final float COLLISION_DAMAGE = 8.0F; private static final double MIN_COLLISION_SPEED = 0.2D;
private static final float COLLISION_DAMAGE_PER_BLOCK_PER_TICK = 8.0F;
private static final float MAX_COLLISION_DAMAGE = 20.0F;
private static final Map<UUID, Long> LAST_COLLISION_DAMAGE = new ConcurrentHashMap<>(); private static final Map<UUID, Long> LAST_COLLISION_DAMAGE = new ConcurrentHashMap<>();
public ManticoreWingsItem(Properties properties) { public ManticoreWingsItem(Properties properties) {
@@ -41,10 +43,16 @@ public final class ManticoreWingsItem extends ElytraItem {
return; return;
} }
long gameTime = level.getGameTime(); long gameTime = level.getGameTime();
double speed = player.getDeltaMovement().length();
if (speed < MIN_COLLISION_SPEED) {
return;
}
float collisionDamage = Math.min(MAX_COLLISION_DAMAGE,
Math.max(1.0F, (float) speed * COLLISION_DAMAGE_PER_BLOCK_PER_TICK));
for (LivingEntity target : level.getEntitiesOfClass(LivingEntity.class, player.getBoundingBox().inflate(0.45D), living -> for (LivingEntity target : level.getEntitiesOfClass(LivingEntity.class, player.getBoundingBox().inflate(0.45D), living ->
living != player && living.isAlive() && !player.isAlliedTo(living) && !living.isAlliedTo(player))) { living != player && living.isAlive() && !player.isAlliedTo(living) && !living.isAlliedTo(player))) {
long availableAt = LAST_COLLISION_DAMAGE.getOrDefault(target.getUUID(), 0L); long availableAt = LAST_COLLISION_DAMAGE.getOrDefault(target.getUUID(), 0L);
if (availableAt <= gameTime && target.hurt(level.damageSources().playerAttack(player), COLLISION_DAMAGE)) { if (availableAt <= gameTime && target.hurt(level.damageSources().playerAttack(player), collisionDamage)) {
LAST_COLLISION_DAMAGE.put(target.getUUID(), gameTime + DAMAGE_COOLDOWN_TICKS); LAST_COLLISION_DAMAGE.put(target.getUUID(), gameTime + DAMAGE_COOLDOWN_TICKS);
} }
} }
@@ -1,5 +1,6 @@
package com.craisinlord.antarchy.content.item; package com.craisinlord.antarchy.content.item;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.config.AntarchySettings; import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.AntarchyObjects; import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.content.item.royal.RoyalGearHelper; import com.craisinlord.antarchy.content.item.royal.RoyalGearHelper;
@@ -142,8 +143,18 @@ public class RoyalAssailantArmorItem extends ArmorItem {
} }
MobEffectInstance existing = player.getEffect(AntarchyObjects.INVERTED_EFFECT.get()); MobEffectInstance existing = player.getEffect(AntarchyObjects.INVERTED_EFFECT.get());
if (existing != null && existing.isInfiniteDuration()) { if (existing != null && existing.isInfiniteDuration()) {
Antarchy.LOGGER.info(
"[antarchy-gravity] royal chestplate toggling inversion off player={} uuid={} pos=({}, {}, {}) dim={} existingDuration={} gameTime={}",
player.getGameProfile().getName(), player.getUUID(), player.getX(), player.getY(), player.getZ(),
player.level().dimension().location(), existing.getDuration(), player.level().getGameTime()
);
player.removeEffect(AntarchyObjects.INVERTED_EFFECT.get()); player.removeEffect(AntarchyObjects.INVERTED_EFFECT.get());
} else { } else {
Antarchy.LOGGER.info(
"[antarchy-gravity] royal chestplate toggling inversion on player={} uuid={} pos=({}, {}, {}) dim={} existingDuration={} gameTime={}",
player.getGameProfile().getName(), player.getUUID(), player.getX(), player.getY(), player.getZ(),
player.level().dimension().location(), existing != null ? existing.getDuration() : -1, player.level().getGameTime()
);
player.addEffect(new MobEffectInstance(AntarchyObjects.INVERTED_EFFECT.get(), -1, 0, false, false, true)); player.addEffect(new MobEffectInstance(AntarchyObjects.INVERTED_EFFECT.get(), -1, 0, false, false, true));
} }
} }
@@ -65,6 +65,18 @@ public final class TimeDilationApi {
SYNCED_CLIENT_RATES.put(entityUuid, clampedRate); SYNCED_CLIENT_RATES.put(entityUuid, clampedRate);
} }
/** Clears both the authoritative entity state and any client-side UUID cache. */
public static void clearRate(Entity entity) {
if (entity instanceof TimeDilationEntityAccess access) {
boolean wasDilated = access.antarchy$getTimeDilationRate() < TimeDilationMath.NORMAL_RATE;
access.antarchy$setTimeDilationRate(TimeDilationMath.NORMAL_RATE);
if (wasDilated) {
syncEntityRate(entity, TimeDilationMath.NORMAL_RATE);
}
}
SYNCED_CLIENT_RATES.remove(entity.getUUID());
}
public static void clearSyncedClientRates() { public static void clearSyncedClientRates() {
SYNCED_CLIENT_RATES.clear(); SYNCED_CLIENT_RATES.clear();
} }
@@ -1,19 +1,16 @@
package com.craisinlord.antarchy.content.time; package com.craisinlord.antarchy.content.time;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.Level; import com.craisinlord.antarchy.content.effect.DilatedMobEffect;
import com.craisinlord.antarchy.content.effect.RoyalEffectEligibility;
import com.craisinlord.antarchy.content.effect.RoyalEffectHooks;
public final class TimeDilationManager { public final class TimeDilationManager {
private static final Set<ResourceKey<Level>> DILATED_LEVELS = new HashSet<>();
private TimeDilationManager() { private TimeDilationManager() {
} }
@@ -21,17 +18,12 @@ public final class TimeDilationManager {
for (ServerLevel level : server.getAllLevels()) { for (ServerLevel level : server.getAllLevels()) {
List<TimeDilationFieldEntity> fields = collectFields(level); List<TimeDilationFieldEntity> fields = collectFields(level);
syncFieldSnapshots(level, fields); syncFieldSnapshots(level, fields);
if (fields.isEmpty()) {
if (DILATED_LEVELS.remove(level.dimension())) {
resetRates(level);
}
continue;
}
DILATED_LEVELS.add(level.dimension());
updateEntities(level, fields); updateEntities(level, fields);
if (!fields.isEmpty()) {
TimeDilationParticles.spawnFieldBorders(level, fields); TimeDilationParticles.spawnFieldBorders(level, fields);
} }
} }
}
private static void syncFieldSnapshots(ServerLevel level, List<TimeDilationFieldEntity> fields) { private static void syncFieldSnapshots(ServerLevel level, List<TimeDilationFieldEntity> fields) {
List<TimeDilationFieldSnapshot> snapshots = fields.stream() List<TimeDilationFieldSnapshot> snapshots = fields.stream()
@@ -60,32 +52,30 @@ public final class TimeDilationManager {
if (!(entity instanceof TimeDilationEntityAccess access) || entity instanceof TimeDilationFieldEntity) { if (!(entity instanceof TimeDilationEntityAccess access) || entity instanceof TimeDilationFieldEntity) {
continue; continue;
} }
if (entity.getType().is(com.craisinlord.antarchy.content.AntarchyTags.Entities.TIME_DILATION_IMMUNE)) { if (entity.getType().is(com.craisinlord.antarchy.content.AntarchyTags.Entities.TIME_DILATION_IMMUNE)
if (access.antarchy$getTimeDilationRate() < TimeDilationMath.NORMAL_RATE) { || (entity instanceof net.minecraft.world.entity.LivingEntity living && !RoyalEffectEligibility.canApplyDilated(living))) {
access.antarchy$setTimeDilationRate(TimeDilationMath.NORMAL_RATE); if (RoyalEffectHooks.dilatedHolder() != null && entity instanceof net.minecraft.world.entity.LivingEntity living) {
TimeDilationApi.syncEntityRate(entity, TimeDilationMath.NORMAL_RATE); living.removeEffect(RoyalEffectHooks.dilatedHolder());
} }
TimeDilationApi.clearRate(entity);
continue; continue;
} }
double previousRate = access.antarchy$getTimeDilationRate(); double previousRate = access.antarchy$getTimeDilationRate();
double rate = TimeDilationFieldSampler.sample(fields, entity.position()); double fieldRate = TimeDilationFieldSampler.sample(fields, entity.position());
double effectRate = RoyalEffectHooks.dilatedHolder() != null
&& entity instanceof net.minecraft.world.entity.LivingEntity living
&& living.hasEffect(RoyalEffectHooks.dilatedHolder())
? DilatedMobEffect.RATE : TimeDilationMath.NORMAL_RATE;
double rate = Math.min(fieldRate, effectRate);
if (rate >= TimeDilationMath.NORMAL_RATE) {
TimeDilationApi.clearRate(entity);
} else {
access.antarchy$setTimeDilationRate(rate); access.antarchy$setTimeDilationRate(rate);
if (Math.abs(previousRate - rate) > 0.001D) { }
if (rate < TimeDilationMath.NORMAL_RATE && Math.abs(previousRate - rate) > 0.001D) {
TimeDilationApi.syncEntityRate(entity, rate); TimeDilationApi.syncEntityRate(entity, rate);
} }
} }
} }
private static void resetRates(ServerLevel level) {
for (Entity entity : level.getAllEntities()) {
if (!(entity instanceof TimeDilationEntityAccess access) || entity instanceof TimeDilationFieldEntity) {
continue;
}
if (access.antarchy$getTimeDilationRate() >= TimeDilationMath.NORMAL_RATE) {
continue;
}
access.antarchy$setTimeDilationRate(TimeDilationMath.NORMAL_RATE);
TimeDilationApi.syncEntityRate(entity, TimeDilationMath.NORMAL_RATE);
}
}
} }
@@ -20,6 +20,8 @@ import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.feature.Feature; import net.minecraft.world.level.levelgen.feature.Feature;
import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext; import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext;
import net.minecraft.world.level.levelgen.feature.configurations.NoneFeatureConfiguration; import net.minecraft.world.level.levelgen.feature.configurations.NoneFeatureConfiguration;
import java.util.HashMap;
import java.util.Map;
public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> { public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
private static final String SIZEABLE_FOLIAGE_MODID = "sizeable_foliage"; private static final String SIZEABLE_FOLIAGE_MODID = "sizeable_foliage";
@@ -38,6 +40,8 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
WorldGenLevel level = context.level(); WorldGenLevel level = context.level();
RandomSource random = context.random(); RandomSource random = context.random();
BlockPos origin = context.origin(); BlockPos origin = context.origin();
Map<Long, Integer> surfaceHeightCache = new HashMap<>();
Map<Long, Integer> oceanFloorHeightCache = new HashMap<>();
int attempts = switch (this.variant) { int attempts = switch (this.variant) {
case FOREST -> 185; case FOREST -> 185;
case MEADOW -> 95; case MEADOW -> 95;
@@ -68,7 +72,7 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
case FLOWER_FOREST_MILKWEED -> 0.0F; case FLOWER_FOREST_MILKWEED -> 0.0F;
}; };
if (random.nextFloat() < torchflowerChance) { if (random.nextFloat() < torchflowerChance) {
placedAny |= this.placeTorchflowerPatch(level, origin, random); placedAny |= this.placeTorchflowerPatch(level, origin, random, surfaceHeightCache);
} }
} }
@@ -79,7 +83,7 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
case FOREST -> 0.05F; case FOREST -> 0.05F;
case FLOWER_FOREST_MILKWEED -> 0.0F; case FLOWER_FOREST_MILKWEED -> 0.0F;
}) { }) {
placedAny |= this.placePitcherPatch(level, origin, random); placedAny |= this.placePitcherPatch(level, origin, random, surfaceHeightCache);
} }
if (random.nextFloat() < switch (this.variant) { if (random.nextFloat() < switch (this.variant) {
case MEADOW -> 0.16F; case MEADOW -> 0.16F;
@@ -88,7 +92,7 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
case FOREST -> 0.06F; case FOREST -> 0.06F;
case FLOWER_FOREST_MILKWEED -> 0.18F; case FLOWER_FOREST_MILKWEED -> 0.18F;
}) { }) {
placedAny |= this.placeMilkweedPatch(level, origin, random, AntarchyObjects.ORANGE_MILKWEED.get()); placedAny |= this.placeMilkweedPatch(level, origin, random, AntarchyObjects.ORANGE_MILKWEED.get(), surfaceHeightCache);
} }
if (random.nextFloat() < switch (this.variant) { if (random.nextFloat() < switch (this.variant) {
case MEADOW -> 0.16F; case MEADOW -> 0.16F;
@@ -97,20 +101,20 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
case FOREST -> 0.06F; case FOREST -> 0.06F;
case FLOWER_FOREST_MILKWEED -> 0.18F; case FLOWER_FOREST_MILKWEED -> 0.18F;
}) { }) {
placedAny |= this.placeMilkweedPatch(level, origin, random, AntarchyObjects.PINK_MILKWEED.get()); placedAny |= this.placeMilkweedPatch(level, origin, random, AntarchyObjects.PINK_MILKWEED.get(), surfaceHeightCache);
} }
if (this.variant == Variant.PEACH_FOREST && random.nextFloat() < 0.14F) { if (this.variant == Variant.PEACH_FOREST && random.nextFloat() < 0.14F) {
placedAny |= this.placeMilkweedPatch(level, origin, random, antarchyBlock(CAMELLIA_ID)); placedAny |= this.placeMilkweedPatch(level, origin, random, antarchyBlock(CAMELLIA_ID), surfaceHeightCache);
} }
if (this.variant == Variant.FOREST || this.variant == Variant.PEACH_FOREST) { if (this.variant == Variant.FOREST || this.variant == Variant.PEACH_FOREST) {
placedAny |= this.placeBigBushes(level, origin, random, radius); placedAny |= this.placeBigBushes(level, origin, random, radius, surfaceHeightCache);
} }
for (int i = 0; i < attempts; i++) { for (int i = 0; i < attempts; i++) {
int x = origin.getX() + random.nextInt(radius * 2 + 1) - radius; int x = origin.getX() + random.nextInt(radius * 2 + 1) - radius;
int z = origin.getZ() + random.nextInt(radius * 2 + 1) - radius; int z = origin.getZ() + random.nextInt(radius * 2 + 1) - radius;
int y = level.getHeight(Heightmap.Types.WORLD_SURFACE_WG, x, z); int y = cachedHeight(level, Heightmap.Types.WORLD_SURFACE_WG, x, z, surfaceHeightCache);
BlockPos plantPos = new BlockPos(x, y, z); BlockPos plantPos = new BlockPos(x, y, z);
BlockPos groundPos = plantPos.below(); BlockPos groundPos = plantPos.below();
@@ -125,7 +129,7 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
} }
} }
placedAny |= this.placeSeagrass(level, origin, random, radius); placedAny |= this.placeSeagrass(level, origin, random, radius, oceanFloorHeightCache);
return placedAny; return placedAny;
} }
@@ -227,7 +231,7 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
return block; return block;
} }
private boolean placeBigBushes(WorldGenLevel level, BlockPos origin, RandomSource random, int radius) { private boolean placeBigBushes(WorldGenLevel level, BlockPos origin, RandomSource random, int radius, Map<Long, Integer> surfaceHeightCache) {
Block bushBlock = BuiltInRegistries.BLOCK.get(ResourceLocation.fromNamespaceAndPath(SIZEABLE_FOLIAGE_MODID, "big_bush")); Block bushBlock = BuiltInRegistries.BLOCK.get(ResourceLocation.fromNamespaceAndPath(SIZEABLE_FOLIAGE_MODID, "big_bush"));
if (!(bushBlock instanceof BigBushBlock)) { if (!(bushBlock instanceof BigBushBlock)) {
return false; return false;
@@ -241,7 +245,7 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
for (int i = 0; i < bushAttempts; i++) { for (int i = 0; i < bushAttempts; i++) {
int x = origin.getX() + random.nextInt(radius * 2 + 1) - radius; int x = origin.getX() + random.nextInt(radius * 2 + 1) - radius;
int z = origin.getZ() + random.nextInt(radius * 2 + 1) - radius; int z = origin.getZ() + random.nextInt(radius * 2 + 1) - radius;
int y = level.getHeight(Heightmap.Types.WORLD_SURFACE_WG, x, z); int y = cachedHeight(level, Heightmap.Types.WORLD_SURFACE_WG, x, z, surfaceHeightCache);
BlockPos plantPos = new BlockPos(x, y, z); BlockPos plantPos = new BlockPos(x, y, z);
if (!isValidPlantSpot(level, plantPos, plantPos.below())) { if (!isValidPlantSpot(level, plantPos, plantPos.below())) {
@@ -333,8 +337,8 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
return true; return true;
} }
private boolean placeTorchflowerPatch(WorldGenLevel level, BlockPos origin, RandomSource random) { private boolean placeTorchflowerPatch(WorldGenLevel level, BlockPos origin, RandomSource random, Map<Long, Integer> surfaceHeightCache) {
BlockPos patchCenter = sampleSurface(level, origin, random, this.variant == Variant.FOREST ? 8 : 12); BlockPos patchCenter = sampleSurface(level, origin, random, this.variant == Variant.FOREST ? 8 : 12, surfaceHeightCache);
if (patchCenter == null) { if (patchCenter == null) {
return false; return false;
} }
@@ -346,7 +350,7 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
boolean bushEligible = this.variant == Variant.FOREST || this.variant == Variant.MEADOW; boolean bushEligible = this.variant == Variant.FOREST || this.variant == Variant.MEADOW;
boolean placed = false; boolean placed = false;
for (int i = 0; i < plants; i++) { for (int i = 0; i < plants; i++) {
BlockPos plantPos = samplePatchPos(level, patchCenter, radius, random); BlockPos plantPos = samplePatchPos(level, patchCenter, radius, random, surfaceHeightCache);
if (plantPos == null) { if (plantPos == null) {
continue; continue;
} }
@@ -369,8 +373,8 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
return placed; return placed;
} }
private boolean placePitcherPatch(WorldGenLevel level, BlockPos origin, RandomSource random) { private boolean placePitcherPatch(WorldGenLevel level, BlockPos origin, RandomSource random, Map<Long, Integer> surfaceHeightCache) {
BlockPos patchCenter = sampleSurface(level, origin, random, this.variant == Variant.FOREST ? 8 : 13); BlockPos patchCenter = sampleSurface(level, origin, random, this.variant == Variant.FOREST ? 8 : 13, surfaceHeightCache);
if (patchCenter == null) { if (patchCenter == null) {
return false; return false;
} }
@@ -379,7 +383,7 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
int plants = this.variant == Variant.BUTTERFLY_FIELDS ? 10 + random.nextInt(12) : this.variant == Variant.FLOWER_FOREST_MILKWEED ? 2 + random.nextInt(3) : 5 + random.nextInt(11); int plants = this.variant == Variant.BUTTERFLY_FIELDS ? 10 + random.nextInt(12) : this.variant == Variant.FLOWER_FOREST_MILKWEED ? 2 + random.nextInt(3) : 5 + random.nextInt(11);
boolean placed = false; boolean placed = false;
for (int i = 0; i < plants; i++) { for (int i = 0; i < plants; i++) {
BlockPos plantPos = samplePatchPos(level, patchCenter, radius, random); BlockPos plantPos = samplePatchPos(level, patchCenter, radius, random, surfaceHeightCache);
if (plantPos == null) { if (plantPos == null) {
continue; continue;
} }
@@ -396,8 +400,8 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
return placed; return placed;
} }
private boolean placeMilkweedPatch(WorldGenLevel level, BlockPos origin, RandomSource random, Block flower) { private boolean placeMilkweedPatch(WorldGenLevel level, BlockPos origin, RandomSource random, Block flower, Map<Long, Integer> surfaceHeightCache) {
BlockPos patchCenter = sampleSurface(level, origin, random, this.variant == Variant.FOREST ? 8 : 13); BlockPos patchCenter = sampleSurface(level, origin, random, this.variant == Variant.FOREST ? 8 : 13, surfaceHeightCache);
if (patchCenter == null) { if (patchCenter == null) {
return false; return false;
} }
@@ -406,7 +410,7 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
int plants = this.variant == Variant.BUTTERFLY_FIELDS ? 14 + random.nextInt(16) : this.variant == Variant.FLOWER_FOREST_MILKWEED ? 2 + random.nextInt(4) : 4 + random.nextInt(9); int plants = this.variant == Variant.BUTTERFLY_FIELDS ? 14 + random.nextInt(16) : this.variant == Variant.FLOWER_FOREST_MILKWEED ? 2 + random.nextInt(4) : 4 + random.nextInt(9);
boolean placed = false; boolean placed = false;
for (int i = 0; i < plants; i++) { for (int i = 0; i < plants; i++) {
BlockPos plantPos = samplePatchPos(level, patchCenter, radius, random); BlockPos plantPos = samplePatchPos(level, patchCenter, radius, random, surfaceHeightCache);
if (plantPos == null) { if (plantPos == null) {
continue; continue;
} }
@@ -423,7 +427,7 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
return placed; return placed;
} }
private boolean placeSeagrass(WorldGenLevel level, BlockPos origin, RandomSource random, int radius) { private boolean placeSeagrass(WorldGenLevel level, BlockPos origin, RandomSource random, int radius, Map<Long, Integer> oceanFloorHeightCache) {
int attempts = switch (this.variant) { int attempts = switch (this.variant) {
case FOREST -> 3; case FOREST -> 3;
case MEADOW -> 5; case MEADOW -> 5;
@@ -436,7 +440,7 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
for (int i = 0; i < attempts; i++) { for (int i = 0; i < attempts; i++) {
int x = origin.getX() + random.nextInt(radius * 2 + 1) - radius; int x = origin.getX() + random.nextInt(radius * 2 + 1) - radius;
int z = origin.getZ() + random.nextInt(radius * 2 + 1) - radius; int z = origin.getZ() + random.nextInt(radius * 2 + 1) - radius;
int y = level.getHeight(Heightmap.Types.OCEAN_FLOOR_WG, x, z); int y = cachedHeight(level, Heightmap.Types.OCEAN_FLOOR_WG, x, z, oceanFloorHeightCache);
BlockPos seagrassPos = new BlockPos(x, y, z); BlockPos seagrassPos = new BlockPos(x, y, z);
BlockPos floorPos = seagrassPos.below(); BlockPos floorPos = seagrassPos.below();
@@ -455,11 +459,11 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
return placed; return placed;
} }
private static BlockPos sampleSurface(WorldGenLevel level, BlockPos origin, RandomSource random, int radius) { private static BlockPos sampleSurface(WorldGenLevel level, BlockPos origin, RandomSource random, int radius, Map<Long, Integer> surfaceHeightCache) {
for (int attempt = 0; attempt < 8; attempt++) { for (int attempt = 0; attempt < 8; attempt++) {
int x = origin.getX() + random.nextInt(radius * 2 + 1) - radius; int x = origin.getX() + random.nextInt(radius * 2 + 1) - radius;
int z = origin.getZ() + random.nextInt(radius * 2 + 1) - radius; int z = origin.getZ() + random.nextInt(radius * 2 + 1) - radius;
int y = level.getHeight(Heightmap.Types.WORLD_SURFACE_WG, x, z); int y = cachedHeight(level, Heightmap.Types.WORLD_SURFACE_WG, x, z, surfaceHeightCache);
BlockPos plantPos = new BlockPos(x, y, z); BlockPos plantPos = new BlockPos(x, y, z);
if (isValidPlantSpot(level, plantPos, plantPos.below())) { if (isValidPlantSpot(level, plantPos, plantPos.below())) {
return plantPos; return plantPos;
@@ -468,14 +472,25 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
return null; return null;
} }
private static BlockPos samplePatchPos(WorldGenLevel level, BlockPos center, int radius, RandomSource random) { private static BlockPos samplePatchPos(WorldGenLevel level, BlockPos center, int radius, RandomSource random, Map<Long, Integer> surfaceHeightCache) {
int x = center.getX() + random.nextInt(radius * 2 + 1) - radius; int x = center.getX() + random.nextInt(radius * 2 + 1) - radius;
int z = center.getZ() + random.nextInt(radius * 2 + 1) - radius; int z = center.getZ() + random.nextInt(radius * 2 + 1) - radius;
int y = level.getHeight(Heightmap.Types.WORLD_SURFACE_WG, x, z); int y = cachedHeight(level, Heightmap.Types.WORLD_SURFACE_WG, x, z, surfaceHeightCache);
BlockPos plantPos = new BlockPos(x, y, z); BlockPos plantPos = new BlockPos(x, y, z);
return isOpenPlantSpot(level, plantPos) ? plantPos : null; return isOpenPlantSpot(level, plantPos) ? plantPos : null;
} }
private static int cachedHeight(WorldGenLevel level, Heightmap.Types type, int x, int z, Map<Long, Integer> cache) {
long key = ((long) x << 32) ^ (z & 0xFFFFFFFFL);
Integer cached = cache.get(key);
if (cached != null) {
return cached;
}
int height = level.getHeight(type, x, z);
cache.put(key, height);
return height;
}
private static void normalizePeachForestGround(WorldGenLevel level, BlockPos groundPos, Variant variant) { private static void normalizePeachForestGround(WorldGenLevel level, BlockPos groundPos, Variant variant) {
if (variant != Variant.PEACH_FOREST) { if (variant != Variant.PEACH_FOREST) {
return; return;
@@ -7,7 +7,10 @@ import com.craisinlord.antarchy.content.block.OuranwoodLeavesBlock;
import com.craisinlord.antarchy.content.entity.flying_squirrel.FlyingSquirrelEntity; import com.craisinlord.antarchy.content.entity.flying_squirrel.FlyingSquirrelEntity;
import com.mojang.serialization.Codec; import com.mojang.serialization.Codec;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction; import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
@@ -1113,29 +1116,76 @@ public class OuranwoodTreeFeature extends Feature<OuranwoodTreeConfiguration> {
int minY = Math.max(level.getMinBuildHeight(), origin.getY() - 2); int minY = Math.max(level.getMinBuildHeight(), origin.getY() - 2);
int maxY = Math.min(level.getMaxBuildHeight() - 1, origin.getY() + height + canopyDepth + 8); int maxY = Math.min(level.getMaxBuildHeight() - 1, origin.getY() + height + canopyDepth + 8);
List<BlockPos> leafPositions = new ArrayList<>(); List<BlockPos> leafPositions = new ArrayList<>();
Map<BlockPos, Integer> distances = new HashMap<>();
ArrayDeque<BlockPos> queue = new ArrayDeque<>();
BlockPos.MutableBlockPos cursor = new BlockPos.MutableBlockPos(); BlockPos.MutableBlockPos cursor = new BlockPos.MutableBlockPos();
for (int x = origin.getX() - horizontalRadius; x <= origin.getX() + horizontalRadius; x++) { for (int x = origin.getX() - horizontalRadius; x <= origin.getX() + horizontalRadius; x++) {
for (int z = origin.getZ() - horizontalRadius; z <= origin.getZ() + horizontalRadius; z++) { for (int z = origin.getZ() - horizontalRadius; z <= origin.getZ() + horizontalRadius; z++) {
for (int y = minY; y <= maxY; y++) { for (int y = minY; y <= maxY; y++) {
cursor.set(x, y, z); cursor.set(x, y, z);
if (level.getBlockState(cursor).getBlock() instanceof LeavesBlock) { BlockState state = level.getBlockState(cursor);
leafPositions.add(cursor.immutable()); if (state.getBlock() instanceof LeavesBlock && this.getLeafDistanceProperty(state) != null) {
BlockPos pos = cursor.immutable();
leafPositions.add(pos);
distances.put(pos, this.getLeafDistanceLimit(state));
} }
} }
} }
} }
for (int pass = 1; pass <= 16; pass++) {
boolean anyChanged = false;
for (BlockPos pos : leafPositions) { for (BlockPos pos : leafPositions) {
BlockState state = level.getBlockState(pos); BlockState state = level.getBlockState(pos);
IntegerProperty distanceProperty = this.getLeafDistanceProperty(state); int limit = this.getLeafDistanceLimit(state);
if (!(state.getBlock() instanceof LeavesBlock) || distanceProperty == null) { int bestDistance = limit;
for (Direction direction : Direction.values()) {
BlockPos neighborPos = pos.relative(direction);
BlockState neighbor = level.getBlockState(neighborPos);
if (neighbor.is(BlockTags.LOGS)) {
bestDistance = 1;
break;
}
if (!distances.containsKey(neighborPos)) {
IntegerProperty neighborProperty = this.getLeafDistanceProperty(neighbor);
if (neighborProperty != null) {
bestDistance = Math.min(bestDistance, neighbor.getValue(neighborProperty) + 1);
}
}
}
if (bestDistance < distances.get(pos)) {
distances.put(pos, bestDistance);
queue.add(pos);
}
}
while (!queue.isEmpty()) {
BlockPos pos = queue.removeFirst();
int distance = distances.get(pos);
for (Direction direction : Direction.values()) {
BlockPos neighborPos = pos.relative(direction);
Integer neighborDistance = distances.get(neighborPos);
if (neighborDistance == null) {
continue; continue;
} }
int computedDistance = this.computeLeafDistance(level, pos, state); BlockState neighborState = level.getBlockState(neighborPos);
int candidate = Math.min(this.getLeafDistanceLimit(neighborState), distance + 1);
if (candidate < neighborDistance) {
distances.put(neighborPos, candidate);
queue.addLast(neighborPos);
}
}
}
for (BlockPos pos : leafPositions) {
BlockState state = level.getBlockState(pos);
IntegerProperty distanceProperty = this.getLeafDistanceProperty(state);
if (distanceProperty == null) {
continue;
}
int computedDistance = distances.get(pos);
if (computedDistance == state.getValue(distanceProperty)) { if (computedDistance == state.getValue(distanceProperty)) {
continue; continue;
} }
@@ -1147,9 +1197,6 @@ public class OuranwoodTreeFeature extends Feature<OuranwoodTreeConfiguration> {
updatedState = state.setValue(distanceProperty, computedDistance); updatedState = state.setValue(distanceProperty, computedDistance);
} }
setBlock(level, pos, updatedState); setBlock(level, pos, updatedState);
anyChanged = true;
}
if (!anyChanged) break;
} }
} }
@@ -0,0 +1,63 @@
package com.craisinlord.antarchy.content.worldgen.thoraxis;
import com.craisinlord.antarchy.Antarchy;
import com.mojang.serialization.Codec;
import net.minecraft.core.BlockPos;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.feature.Feature;
import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext;
import net.minecraft.world.level.levelgen.feature.configurations.NoneFeatureConfiguration;
/**
* Fills the low, open main-side Thoraxis basin with source ichor during chunk generation.
* The feature works one chunk at a time so it does not depend on runtime fluid spreading.
*/
public final class ThoraxisIchorLakeFeature extends Feature<NoneFeatureConfiguration> {
private static final int LAKE_FLOOR_Y = 1;
private static final int LAKE_SURFACE_Y = 8;
private static final ResourceLocation ICHOR_BLOCK_ID =
ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "ichor");
public ThoraxisIchorLakeFeature(Codec<NoneFeatureConfiguration> codec) {
super(codec);
}
@Override
public boolean place(FeaturePlaceContext<NoneFeatureConfiguration> context) {
WorldGenLevel level = context.level();
BlockPos origin = context.origin();
BlockState ichorState = BuiltInRegistries.BLOCK.get(ICHOR_BLOCK_ID).defaultBlockState();
if (ichorState.isAir() || ichorState.getFluidState().isEmpty()) {
return false;
}
int chunkMinX = origin.getX() >> 4 << 4;
int chunkMinZ = origin.getZ() >> 4 << 4;
boolean placedAny = false;
BlockPos.MutableBlockPos mutable = new BlockPos.MutableBlockPos();
for (int x = chunkMinX; x < chunkMinX + 16; x++) {
for (int z = chunkMinZ; z < chunkMinZ + 16; z++) {
for (int y = LAKE_FLOOR_Y; y <= LAKE_SURFACE_Y; y++) {
mutable.set(x, y, z);
BlockState current = level.getBlockState(mutable);
if (!isOpenForLake(current)) {
continue;
}
level.setBlock(mutable, ichorState, 2);
placedAny = true;
}
}
}
return placedAny;
}
private static boolean isOpenForLake(BlockState state) {
return state.isAir() || state.canBeReplaced() || !state.blocksMotion();
}
}
@@ -35,6 +35,8 @@ public final class ThoraxisUndersideManager {
private static final int DISCOVERY_INTERVAL_TICKS = 5; private static final int DISCOVERY_INTERVAL_TICKS = 5;
private static final int SIMULATION_DISTANCE_MARGIN_CHUNKS = 1; private static final int SIMULATION_DISTANCE_MARGIN_CHUNKS = 1;
private static final int MAX_FLIPS_PER_TICK = 8; private static final int MAX_FLIPS_PER_TICK = 8;
private static final double SLOW_TICK_WARN_MS = 25.0D;
private static final long DIAGNOSTIC_INTERVAL_TICKS = 100L;
private static final int ENTER_UNDERSIDE_Y = GRAVITY_FLIP_Y - 4; 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 EXIT_UNDERSIDE_Y = GRAVITY_FLIP_Y + 4;
private static final int FLIP_COOLDOWN_TICKS = 40; private static final int FLIP_COOLDOWN_TICKS = 40;
@@ -55,12 +57,20 @@ public final class ThoraxisUndersideManager {
return; return;
} }
long startNanos = System.nanoTime();
long now = level.getGameTime(); long now = level.getGameTime();
int discoveredLiving = 0;
int discoveredItems = 0;
if (now % DISCOVERY_INTERVAL_TICKS == 0L) { if (now % DISCOVERY_INTERVAL_TICKS == 0L) {
tracking.discover(level); DiscoveryStats stats = tracking.discover(level);
discoveredLiving = stats.livingEntities();
discoveredItems = stats.itemEntities();
} }
int flipsRemaining = MAX_FLIPS_PER_TICK; int flipsRemaining = MAX_FLIPS_PER_TICK;
int refreshedLiving = 0;
int flippedItems = 0;
int restoredItems = 0;
for (Entity entity : tracking.entities) { for (Entity entity : tracking.entities) {
if (!entity.isAlive() || entity.isSpectator()) { if (!entity.isAlive() || entity.isSpectator()) {
continue; continue;
@@ -71,6 +81,7 @@ public final class ThoraxisUndersideManager {
int threshold = hasEffect ? EXIT_UNDERSIDE_Y : ENTER_UNDERSIDE_Y; int threshold = hasEffect ? EXIT_UNDERSIDE_Y : ENTER_UNDERSIDE_Y;
if (living.getY() < threshold) { if (living.getY() < threshold) {
refreshInvertedEffect(living); refreshInvertedEffect(living);
refreshedLiving++;
} }
continue; continue;
} }
@@ -94,6 +105,7 @@ public final class ThoraxisUndersideManager {
flipsRemaining--; flipsRemaining--;
tracking.lastFlipTick.put(uuid, now); tracking.lastFlipTick.put(uuid, now);
AntarchyGravityApi.setForcedGravityDirection(entity, AntarchyGravityDirection.UP, TRANSITION); AntarchyGravityApi.setForcedGravityDirection(entity, AntarchyGravityDirection.UP, TRANSITION);
flippedItems++;
} }
continue; continue;
} }
@@ -108,6 +120,7 @@ public final class ThoraxisUndersideManager {
if (isForcedUp) { if (isForcedUp) {
tracking.lastFlipTick.put(uuid, now); tracking.lastFlipTick.put(uuid, now);
AntarchyGravityApi.setGravityDirection(entity, AntarchyGravityDirection.DOWN, false, TRANSITION); AntarchyGravityApi.setGravityDirection(entity, AntarchyGravityDirection.DOWN, false, TRANSITION);
restoredItems++;
} }
} }
} }
@@ -115,26 +128,44 @@ public final class ThoraxisUndersideManager {
tracking.entities.removeIf(entity -> !entity.isAlive() || entity.isRemoved()); tracking.entities.removeIf(entity -> !entity.isAlive() || entity.isRemoved());
tracking.forcedItems.removeIf(entity -> !entity.isAlive() || entity.isRemoved()); tracking.forcedItems.removeIf(entity -> !entity.isAlive() || entity.isRemoved());
tracking.lastFlipTick.entrySet().removeIf(entry -> now - entry.getValue() > FLIP_COOLDOWN_TICKS * 4L); tracking.lastFlipTick.entrySet().removeIf(entry -> now - entry.getValue() > FLIP_COOLDOWN_TICKS * 4L);
double elapsedMs = (System.nanoTime() - startNanos) / 1_000_000.0D;
if (elapsedMs >= SLOW_TICK_WARN_MS && now - tracking.lastDiagnosticTick >= DIAGNOSTIC_INTERVAL_TICKS) {
tracking.lastDiagnosticTick = now;
Antarchy.LOGGER.warn(
"[antarchy-thoraxis] slow underside tick dim={} gameTime={} elapsedMs={} trackedEntities={} forcedItems={} discoveredLiving={} discoveredItems={} refreshedLiving={} flippedItems={} restoredItems={} players={}",
level.dimension().location(), now, elapsedMs, tracking.entities.size(), tracking.forcedItems.size(),
discoveredLiving, discoveredItems, refreshedLiving, flippedItems, restoredItems, level.players().size()
);
}
} }
private static final class TrackingState { private static final class TrackingState {
private final Set<Entity> entities = Collections.newSetFromMap(new IdentityHashMap<>()); private final Set<Entity> entities = Collections.newSetFromMap(new IdentityHashMap<>());
private final Set<Entity> forcedItems = Collections.newSetFromMap(new IdentityHashMap<>()); private final Set<Entity> forcedItems = Collections.newSetFromMap(new IdentityHashMap<>());
private final Map<UUID, Long> lastFlipTick = new java.util.HashMap<>(); private final Map<UUID, Long> lastFlipTick = new java.util.HashMap<>();
private long lastDiagnosticTick = Long.MIN_VALUE / 2L;
private void discover(ServerLevel level) { private DiscoveryStats discover(ServerLevel level) {
int radius = (level.getServer().getPlayerList().getSimulationDistance() + SIMULATION_DISTANCE_MARGIN_CHUNKS) * 16; int radius = (level.getServer().getPlayerList().getSimulationDistance() + SIMULATION_DISTANCE_MARGIN_CHUNKS) * 16;
int discoveredLiving = 0;
int discoveredItems = 0;
for (ServerPlayer player : level.players()) { for (ServerPlayer player : level.players()) {
AABB area = new AABB( AABB area = new AABB(
player.getX() - radius, level.getMinBuildHeight(), player.getZ() - radius, player.getX() - radius, level.getMinBuildHeight(), player.getZ() - radius,
player.getX() + radius, level.getMaxBuildHeight(), player.getZ() + radius player.getX() + radius, level.getMaxBuildHeight(), player.getZ() + radius
); );
entities.add(player); entities.add(player);
entities.addAll(level.getEntitiesOfClass(LivingEntity.class, area, java.util.List<LivingEntity> livingEntities = level.getEntitiesOfClass(LivingEntity.class, area,
entity -> !entity.isSpectator() && entity.getY() < EXIT_UNDERSIDE_Y)); entity -> !entity.isSpectator() && entity.getY() < EXIT_UNDERSIDE_Y);
entities.addAll(level.getEntitiesOfClass(ItemEntity.class, area, java.util.List<ItemEntity> itemEntities = level.getEntitiesOfClass(ItemEntity.class, area,
entity -> !entity.isSpectator() && entity.getY() < EXIT_UNDERSIDE_Y)); entity -> !entity.isSpectator() && entity.getY() < EXIT_UNDERSIDE_Y);
discoveredLiving += livingEntities.size();
discoveredItems += itemEntities.size();
entities.addAll(livingEntities);
entities.addAll(itemEntities);
} }
return new DiscoveryStats(discoveredLiving, discoveredItems);
} }
private void clear() { private void clear() {
@@ -149,6 +180,9 @@ public final class ThoraxisUndersideManager {
return last == null || now - last >= FLIP_COOLDOWN_TICKS; return last == null || now - last >= FLIP_COOLDOWN_TICKS;
} }
private record DiscoveryStats(int livingEntities, int itemEntities) {
}
public static void applyUndersideInversion(LivingEntity living) { public static void applyUndersideInversion(LivingEntity living) {
refreshInvertedEffect(living); refreshInvertedEffect(living);
} }
@@ -184,4 +218,8 @@ public final class ThoraxisUndersideManager {
public static boolean isThoraxis(Level level) { public static boolean isThoraxis(Level level) {
return level.dimension().location().equals(THORAXIS_DIMENSION); return level.dimension().location().equals(THORAXIS_DIMENSION);
} }
public static boolean isAboveUndersideExit(Entity entity) {
return isThoraxis(entity.level()) && entity.getY() >= EXIT_UNDERSIDE_Y;
}
} }
@@ -8,10 +8,14 @@ import net.minecraft.world.level.levelgen.feature.stateproviders.BlockStateProvi
public record TruffaloTreeConfiguration( public record TruffaloTreeConfiguration(
BlockStateProvider trunkProvider, BlockStateProvider trunkProvider,
IntProvider height IntProvider height,
int tuftRadius,
int tuftHeight
) implements FeatureConfiguration { ) implements FeatureConfiguration {
public static final Codec<TruffaloTreeConfiguration> CODEC = RecordCodecBuilder.create(instance -> instance.group( public static final Codec<TruffaloTreeConfiguration> CODEC = RecordCodecBuilder.create(instance -> instance.group(
BlockStateProvider.CODEC.fieldOf("trunk_provider").forGetter(TruffaloTreeConfiguration::trunkProvider), BlockStateProvider.CODEC.fieldOf("trunk_provider").forGetter(TruffaloTreeConfiguration::trunkProvider),
IntProvider.codec(4, 9).fieldOf("height").forGetter(TruffaloTreeConfiguration::height) IntProvider.codec(4, 18).fieldOf("height").forGetter(TruffaloTreeConfiguration::height),
Codec.INT.optionalFieldOf("tuft_radius", 1).forGetter(TruffaloTreeConfiguration::tuftRadius),
Codec.INT.optionalFieldOf("tuft_height", 3).forGetter(TruffaloTreeConfiguration::tuftHeight)
).apply(instance, TruffaloTreeConfiguration::new)); ).apply(instance, TruffaloTreeConfiguration::new));
} }
@@ -58,9 +58,9 @@ public class TruffaloTreeFeature extends Feature<TruffaloTreeConfiguration> {
return false; return false;
} }
} }
for (int dy = 0; dy <= 2; dy++) { for (int dy = 0; dy < config.tuftHeight(); dy++) {
for (int dx = -1; dx <= 1; dx++) { for (int dx = -config.tuftRadius(); dx <= config.tuftRadius(); dx++) {
for (int dz = -1; dz <= 1; dz++) { for (int dz = -config.tuftRadius(); dz <= config.tuftRadius(); dz++) {
if (!canReplace(level, capBase.offset(dx, dy, dz))) { if (!canReplace(level, capBase.offset(dx, dy, dz))) {
return false; return false;
} }
@@ -74,9 +74,9 @@ public class TruffaloTreeFeature extends Feature<TruffaloTreeConfiguration> {
Block tuft = BuiltInRegistries.BLOCK.get(ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, color.getName() + "_truffalo_tuft")); Block tuft = BuiltInRegistries.BLOCK.get(ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, color.getName() + "_truffalo_tuft"));
BlockState tuftState = tuft.defaultBlockState(); BlockState tuftState = tuft.defaultBlockState();
for (int dy = 0; dy <= 2; dy++) { for (int dy = 0; dy < config.tuftHeight(); dy++) {
for (int dx = -1; dx <= 1; dx++) { for (int dx = -config.tuftRadius(); dx <= config.tuftRadius(); dx++) {
for (int dz = -1; dz <= 1; dz++) { for (int dz = -config.tuftRadius(); dz <= config.tuftRadius(); dz++) {
setBlock(level, capBase.offset(dx, dy, dz), tuftState); setBlock(level, capBase.offset(dx, dy, dz), tuftState);
} }
} }
@@ -0,0 +1,52 @@
package com.craisinlord.antarchy.mixins;
import com.craisinlord.antarchy.content.effect.CommandedEntityAccess;
import java.util.UUID;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.entity.Mob;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
@Mixin(Mob.class)
public abstract class CommandedMobMixin implements CommandedEntityAccess {
@Unique
private UUID antarchy$commanderUuid;
@Unique
private boolean antarchy$commandedTargetOwned;
@Override
public UUID antarchy$getCommanderUuid() {
return antarchy$commanderUuid;
}
@Override
public void antarchy$setCommanderUuid(UUID uuid) {
antarchy$commanderUuid = uuid;
}
@Override
public boolean antarchy$isCommandedTargetOwned() {
return antarchy$commandedTargetOwned;
}
@Override
public void antarchy$setCommandedTargetOwned(boolean owned) {
antarchy$commandedTargetOwned = owned;
}
@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);
}
}
@org.spongepowered.asm.mixin.injection.Inject(method = "readAdditionalSaveData", at = @org.spongepowered.asm.mixin.injection.At("TAIL"))
private void antarchy$loadCommandedData(CompoundTag tag, org.spongepowered.asm.mixin.injection.callback.CallbackInfo ci) {
if (tag.hasUUID("AntarchyCommander")) {
antarchy$commanderUuid = tag.getUUID("AntarchyCommander");
antarchy$commandedTargetOwned = tag.getBoolean("AntarchyCommandedTargetOwned");
}
}
}
@@ -0,0 +1,90 @@
package com.craisinlord.antarchy.mixins;
import com.craisinlord.antarchy.content.effect.CommandedEntityAccess;
import com.craisinlord.antarchy.content.effect.RoyalEffectEligibility;
import java.util.UUID;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.AreaEffectCloud;
import net.minecraft.world.entity.projectile.Projectile;
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.CallbackInfoReturnable;
@Mixin(LivingEntity.class)
public abstract class LivingEntityRoyalEffectsMixin {
@Unique
private UUID antarchy$previousCommander;
@Inject(method = "canBeAffected", at = @At("HEAD"), cancellable = true)
private void antarchy$checkRoyalEffectBlacklist(MobEffectInstance instance, CallbackInfoReturnable<Boolean> cir) {
if (!RoyalEffectEligibility.canApply((LivingEntity) (Object) this, instance)) {
cir.setReturnValue(false);
}
}
@Inject(method = "addEffect(Lnet/minecraft/world/effect/MobEffectInstance;Lnet/minecraft/world/entity/Entity;)Z", at = @At("HEAD"), cancellable = true)
private void antarchy$prepareCommandedSource(MobEffectInstance instance, Entity source, CallbackInfoReturnable<Boolean> cir) {
if (!RoyalEffectEligibility.isCommanded(instance)) {
return;
}
LivingEntity entity = (LivingEntity) (Object) this;
if (!RoyalEffectEligibility.canApplyCommanded(entity) || !(entity instanceof Mob mob)) {
cir.setReturnValue(false);
return;
}
PlayerSource playerSource = antarchy$resolvePlayer(source);
if (playerSource == null) {
cir.setReturnValue(false);
return;
}
CommandedEntityAccess access = (CommandedEntityAccess) mob;
UUID current = access.antarchy$getCommanderUuid();
if (current != null && !current.equals(playerSource.player().getUUID())) {
cir.setReturnValue(false);
return;
}
antarchy$previousCommander = current;
access.antarchy$setCommanderUuid(playerSource.player().getUUID());
}
@Inject(method = "addEffect(Lnet/minecraft/world/effect/MobEffectInstance;Lnet/minecraft/world/entity/Entity;)Z", at = @At("RETURN"))
private void antarchy$finishCommandedSource(MobEffectInstance instance, Entity source, CallbackInfoReturnable<Boolean> cir) {
if (!RoyalEffectEligibility.isCommanded(instance) || cir.getReturnValue()) {
return;
}
LivingEntity entity = (LivingEntity) (Object) this;
if (entity instanceof CommandedEntityAccess access) {
access.antarchy$setCommanderUuid(antarchy$previousCommander);
}
}
@Inject(method = "onEffectRemoved", at = @At("HEAD"))
private void antarchy$clearCommandedState(MobEffectInstance instance, org.spongepowered.asm.mixin.injection.callback.CallbackInfo ci) {
if (RoyalEffectEligibility.isCommanded(instance) && (Object) this instanceof Mob mob) {
com.craisinlord.antarchy.content.effect.CommandedBehavior.clear(mob);
}
}
@Unique
private PlayerSource antarchy$resolvePlayer(Entity source) {
if (source instanceof net.minecraft.world.entity.player.Player player) {
return new PlayerSource(player);
}
if (source instanceof Projectile projectile && projectile.getOwner() instanceof net.minecraft.world.entity.player.Player player) {
return new PlayerSource(player);
}
if (source instanceof AreaEffectCloud cloud && cloud.getOwner() instanceof net.minecraft.world.entity.player.Player player) {
return new PlayerSource(player);
}
return null;
}
@Unique
private record PlayerSource(net.minecraft.world.entity.player.Player player) {
}
}
@@ -0,0 +1,29 @@
package com.craisinlord.antarchy.mixins;
import com.craisinlord.antarchy.content.effect.CommandedBehavior;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.Mob;
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(Mob.class)
public abstract class MobCommandedTargetMixin {
@Inject(method = "setTarget", at = @At("HEAD"), cancellable = true)
private void antarchy$protectCommandedAllies(LivingEntity target, CallbackInfo ci) {
Mob mob = (Mob) (Object) this;
if (target != null && CommandedBehavior.isProtectedAlly(mob, target)) {
ci.cancel();
}
}
@Inject(method = "doHurtTarget", at = @At("HEAD"), cancellable = true)
private void antarchy$preventCommandedFriendlyFire(net.minecraft.world.entity.Entity target,
org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable<Boolean> cir) {
Mob mob = (Mob) (Object) this;
if (CommandedBehavior.isProtectedAlly(mob, target)) {
cir.setReturnValue(false);
}
}
}
@@ -5,10 +5,16 @@ import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityDirection; import com.craisinlord.antarchy.content.gravity.AntarchyGravityDirection;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityRotationUtil; import com.craisinlord.antarchy.content.gravity.AntarchyGravityRotationUtil;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityTransition; import com.craisinlord.antarchy.content.gravity.AntarchyGravityTransition;
import com.craisinlord.antarchy.content.entity.BedBugEntity;
import com.craisinlord.antarchy.content.entity.portal.DimensionalTearEntity;
import com.craisinlord.antarchy.content.worldgen.thoraxis.ThoraxisUndersideManager;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.level.material.FluidState; import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.AABB;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
@@ -71,6 +77,51 @@ public abstract class EntityGravityMixin implements AntarchyGravityAccess {
this.antarchy$gravityForced = tag.getBoolean(ANTARCHY_GRAVITY_FORCED_TAG); this.antarchy$gravityForced = tag.getBoolean(ANTARCHY_GRAVITY_FORCED_TAG);
this.antarchy$gravityTransitionDuration = Math.max(0, tag.getInt(ANTARCHY_GRAVITY_TRANSITION_DURATION_TAG)); this.antarchy$gravityTransitionDuration = Math.max(0, tag.getInt(ANTARCHY_GRAVITY_TRANSITION_DURATION_TAG));
this.antarchy$gravityTransitionRemaining = Math.max(0, tag.getInt(ANTARCHY_GRAVITY_TRANSITION_REMAINING_TAG)); this.antarchy$gravityTransitionRemaining = Math.max(0, tag.getInt(ANTARCHY_GRAVITY_TRANSITION_REMAINING_TAG));
Entity entity = this.antarchy$entity();
if (this.antarchy$gravityDirection.isInverted() && this.antarchy$gravityForced
&& entity instanceof Mob mob) {
MobEffectInstance invertedEffect = mob.getEffect(com.craisinlord.antarchy.content.AntarchyObjects.INVERTED_EFFECT.get());
boolean aboveUndersideExit = ThoraxisUndersideManager.isAboveUndersideExit(mob);
int nearbyDimensionalTears = this.antarchy$countNearbyDimensionalTears(mob);
if (invertedEffect != null
&& invertedEffect.isAmbient()
&& !invertedEffect.isVisible()
&& aboveUndersideExit) {
this.antarchy$clearLoadedInversion(mob);
com.craisinlord.antarchy.Antarchy.LOGGER.warn(
"[antarchy-gravity] cleared stale underside inversion from loaded mob type={} uuid={} pos=({}, {}, {}) dim={} effectDuration={} nearbyDimensionalTears={}",
mob.getType(), mob.getUUID(), mob.getX(), mob.getY(), mob.getZ(),
mob.level() != null ? mob.level().dimension().location() : "?",
invertedEffect.getDuration(), nearbyDimensionalTears
);
return;
}
if (invertedEffect != null
&& mob instanceof BedBugEntity
&& aboveUndersideExit
&& ThoraxisUndersideManager.isThoraxis(mob.level())) {
this.antarchy$clearLoadedInversion(mob);
com.craisinlord.antarchy.Antarchy.LOGGER.warn(
"[antarchy-gravity] cleared loaded inverted bed bug above underside exit type={} uuid={} pos=({}, {}, {}) dim={} effectDuration={} effectAmbient={} effectVisible={} nearbyDimensionalTears={}",
mob.getType(), mob.getUUID(), mob.getX(), mob.getY(), mob.getZ(),
mob.level() != null ? mob.level().dimension().location() : "?",
invertedEffect.getDuration(), invertedEffect.isAmbient(), invertedEffect.isVisible(), nearbyDimensionalTears
);
return;
}
com.craisinlord.antarchy.Antarchy.LOGGER.info(
"[antarchy-gravity] loaded inverted mob type={} uuid={} pos=({}, {}, {}) dim={} hasInvertedEffect={} effectDuration={} effectAmbient={} effectVisible={} aboveUndersideExit={} nearbyDimensionalTears={}",
mob.getType(), mob.getUUID(), mob.getX(), mob.getY(), mob.getZ(),
mob.level() != null ? mob.level().dimension().location() : "?",
invertedEffect != null,
invertedEffect != null ? invertedEffect.getDuration() : -1,
invertedEffect != null && invertedEffect.isAmbient(),
invertedEffect != null && invertedEffect.isVisible(),
aboveUndersideExit,
nearbyDimensionalTears
);
}
} }
@Inject(method = "baseTick", at = @At("TAIL")) @Inject(method = "baseTick", at = @At("TAIL"))
@@ -134,6 +185,11 @@ public abstract class EntityGravityMixin implements AntarchyGravityAccess {
} }
this.antarchy$gravityForced = forced; this.antarchy$gravityForced = forced;
if (changed && this.antarchy$entity() instanceof Mob mob) {
// Any existing path is expressed in the previous gravity frame.
// Keep goals from following stale nodes after a flip.
mob.getNavigation().stop();
}
if (changed && !this.antarchy$entity().level().isClientSide) { if (changed && !this.antarchy$entity().level().isClientSide) {
AntarchyGravityApi.notifyGravityStateChanged(this.antarchy$entity()); AntarchyGravityApi.notifyGravityStateChanged(this.antarchy$entity());
} }
@@ -172,6 +228,29 @@ public abstract class EntityGravityMixin implements AntarchyGravityAccess {
this.setBoundingBox(this.makeBoundingBox()); this.setBoundingBox(this.makeBoundingBox());
} }
@Unique
private void antarchy$clearLoadedInversion(Mob mob) {
mob.removeEffect(com.craisinlord.antarchy.content.AntarchyObjects.INVERTED_EFFECT.get());
this.antarchy$gravityDirection = AntarchyGravityDirection.DOWN;
this.antarchy$prevGravityDirection = AntarchyGravityDirection.DOWN;
this.antarchy$gravityForced = false;
this.antarchy$gravityTransitionDuration = 0;
this.antarchy$gravityTransitionRemaining = 0;
this.antarchy$refreshGravityBounds();
}
@Unique
private int antarchy$countNearbyDimensionalTears(Mob mob) {
if (!(mob.level() instanceof ServerLevel serverLevel)) {
return -1;
}
return serverLevel.getEntitiesOfClass(
DimensionalTearEntity.class,
mob.getBoundingBox().inflate(48.0D),
Entity::isAlive
).size();
}
@Unique @Unique
private void antarchy$adjustPositionForGravityChange(AntarchyGravityDirection oldDirection, AntarchyGravityDirection newDirection) { private void antarchy$adjustPositionForGravityChange(AntarchyGravityDirection oldDirection, AntarchyGravityDirection newDirection) {
if (oldDirection == newDirection) { if (oldDirection == newDirection) {
@@ -1,26 +1,89 @@
package com.craisinlord.antarchy.mixins.gravity; package com.craisinlord.antarchy.mixins.gravity;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi;
import com.craisinlord.antarchy.content.gravity.GravityWalkNodeEvaluator; import com.craisinlord.antarchy.content.gravity.GravityWalkNodeEvaluator;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.entity.EntityDimensions;
import net.minecraft.world.entity.Pose;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.ai.navigation.GroundPathNavigation; import net.minecraft.world.entity.ai.navigation.GroundPathNavigation;
import net.minecraft.world.level.pathfinder.NodeEvaluator; import net.minecraft.world.level.pathfinder.NodeEvaluator;
import net.minecraft.world.level.pathfinder.PathFinder; import net.minecraft.world.level.pathfinder.PathFinder;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(GroundPathNavigation.class) @Mixin(GroundPathNavigation.class)
/*
* Makes ground pathing read gravity-aware floor checks.
*/
public abstract class GroundPathNavigationMixin { public abstract class GroundPathNavigationMixin {
@Inject(method = "createPathFinder", at = @At("HEAD"), cancellable = true) @Inject(method = "createPathFinder", at = @At("HEAD"), cancellable = true)
private void antarchy$useGravityEvaluator(int maxVisitedNodes, CallbackInfoReturnable<PathFinder> cir) { private void antarchy$useGravityEvaluator(int maxVisitedNodes, CallbackInfoReturnable<PathFinder> cir) {
var accessor = (PathNavigationAccessor) (Object) this; var accessor = (PathNavigationAccessor) (Object) this;
var mob = accessor.antarchy$getMob();
NodeEvaluator evaluator = new GravityWalkNodeEvaluator(); NodeEvaluator evaluator = new GravityWalkNodeEvaluator();
// GroundPathNavigation enables door passing on its vanilla evaluator.
// Preserve that setting when swapping in the gravity-aware evaluator.
evaluator.setCanPassDoors(true);
accessor.antarchy$setNodeEvaluator(evaluator); accessor.antarchy$setNodeEvaluator(evaluator);
cir.setReturnValue(new PathFinder(evaluator, maxVisitedNodes)); cir.setReturnValue(new PathFinder(evaluator, maxVisitedNodes));
} }
/**
* GroundPathNavigation normalizes entity destinations toward the floor
* before delegating to PathNavigation. For inverted mobs, replace that
* final destination with a bounded, collision-free ceiling anchor.
*/
@org.spongepowered.asm.mixin.injection.ModifyArg(
method = "createPath(Lnet/minecraft/core/BlockPos;I)Lnet/minecraft/world/level/pathfinder/Path;",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/entity/ai/navigation/PathNavigation;createPath(Lnet/minecraft/core/BlockPos;I)Lnet/minecraft/world/level/pathfinder/Path;"
),
index = 0
)
private BlockPos antarchy$projectInvertedDestination(BlockPos normalizedDestination) {
Mob mob = ((PathNavigationAccessor) (Object) this).antarchy$getMob();
if (!AntarchyGravityApi.isGravityInverted(mob)) {
return normalizedDestination;
}
EntityDimensions dimensions = mob.getDimensions(mob.getPose());
int centerX = normalizedDestination.getX();
int centerZ = normalizedDestination.getZ();
int baseY = normalizedDestination.getY();
int minY = Math.max(mob.level().getMinBuildHeight(), baseY - 32);
int maxY = Math.min(mob.level().getMaxBuildHeight() - 2, baseY + 32);
for (int candidateY = baseY; candidateY <= maxY; candidateY++) {
BlockPos support = new BlockPos(centerX, candidateY + 1, centerZ);
if (!mob.level().getBlockState(support).isFaceSturdy(mob.level(), support, Direction.DOWN)) {
continue;
}
double topY = candidateY + 1.0D - 1.0E-3D;
double halfWidth = dimensions.width() / 2.0D;
AABB body = new AABB(
centerX + 0.5D - halfWidth,
topY - dimensions.height(),
centerZ + 0.5D - halfWidth,
centerX + 0.5D + halfWidth,
topY,
centerZ + 0.5D + halfWidth
).deflate(1.0E-4D);
if (mob.level().noCollision(mob, body)) {
return new BlockPos(centerX, candidateY, centerZ);
}
}
return normalizedDestination;
}
@Inject(method = "getTempMobPos", at = @At("HEAD"), cancellable = true)
private void antarchy$useInvertedMobAnchor(CallbackInfoReturnable<Vec3> cir) {
Mob mob = ((PathNavigationAccessor) (Object) this).antarchy$getMob();
if (AntarchyGravityApi.isGravityInverted(mob)) {
cir.setReturnValue(new Vec3(mob.getX(), mob.getY(), mob.getZ()));
}
}
} }
@@ -0,0 +1,101 @@
package com.craisinlord.antarchy.mixins.gravity;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.PathfinderMob;
import net.minecraft.world.entity.ai.goal.MeleeAttackGoal;
import net.minecraft.world.entity.player.Player;
import org.spongepowered.asm.mixin.Final;
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;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(MeleeAttackGoal.class)
public abstract class MeleeAttackGoalGravityMixin {
@Shadow
@Final
protected PathfinderMob mob;
@Shadow
@Final
private double speedModifier;
@Shadow
@Final
private boolean followingTargetEvenIfNotSeen;
@Shadow
private int ticksUntilNextAttack;
@Shadow
protected abstract void checkAndPerformAttack(LivingEntity enemy);
@Inject(method = "canUse", at = @At("HEAD"), cancellable = true)
private void antarchy$useInvertedMelee(CallbackInfoReturnable<Boolean> cir) {
if (!AntarchyGravityApi.isGravityInverted(this.mob)) {
return;
}
LivingEntity target = this.mob.getTarget();
cir.setReturnValue(this.antarchy$isValidInvertedMeleeTarget(target));
}
@Inject(method = "canContinueToUse", at = @At("HEAD"), cancellable = true)
private void antarchy$continueInvertedMelee(CallbackInfoReturnable<Boolean> cir) {
if (!AntarchyGravityApi.isGravityInverted(this.mob)) {
return;
}
LivingEntity target = this.mob.getTarget();
cir.setReturnValue(this.antarchy$isValidInvertedMeleeTarget(target));
}
@Inject(method = "start", at = @At("HEAD"), cancellable = true)
private void antarchy$startInvertedMelee(CallbackInfo ci) {
if (!AntarchyGravityApi.isGravityInverted(this.mob)) {
return;
}
LivingEntity target = this.mob.getTarget();
this.mob.getNavigation().stop();
this.mob.setAggressive(true);
this.ticksUntilNextAttack = 0;
ci.cancel();
}
@Inject(method = "tick", at = @At("HEAD"), cancellable = true)
private void antarchy$tickInvertedMelee(CallbackInfo ci) {
if (!AntarchyGravityApi.isGravityInverted(this.mob)) {
return;
}
LivingEntity target = this.mob.getTarget();
if (!this.antarchy$isValidInvertedMeleeTarget(target)) {
this.mob.getNavigation().stop();
ci.cancel();
return;
}
this.mob.getLookControl().setLookAt(target, 30.0F, 30.0F);
// Do not call PathNavigation.moveTo here: that starts the unsafe vanilla
// path acquisition path. MoveControl receives a world-space target and
// the gravity movement mixin converts it into the inverted frame.
this.mob.getMoveControl().setWantedPosition(target.getX(), target.getY(), target.getZ(), this.speedModifier);
this.ticksUntilNextAttack = Math.max(0, this.ticksUntilNextAttack - 1);
this.checkAndPerformAttack(target);
ci.cancel();
}
private boolean antarchy$isValidInvertedMeleeTarget(LivingEntity target) {
if (target == null || !target.isAlive()) {
return false;
}
if (this.followingTargetEvenIfNotSeen && !this.mob.isWithinRestriction(target.blockPosition())) {
return false;
}
return !(target instanceof Player player) || (!player.isSpectator() && !player.isCreative());
}
}
@@ -0,0 +1,48 @@
package com.craisinlord.antarchy.mixins.gravity;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityRotationUtil;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.ai.control.MoveControl;
import net.minecraft.world.phys.Vec3;
import org.spongepowered.asm.mixin.Final;
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;
@Mixin(MoveControl.class)
public abstract class MoveControlGravityMixin {
@Shadow
@Final
protected Mob mob;
@Shadow
protected double wantedX;
@Shadow
protected double wantedY;
@Shadow
protected double wantedZ;
@Shadow
protected double speedModifier;
@Inject(method = "setWantedPosition", at = @At("TAIL"))
private void antarchy$convertInvertedWantedPosition(double x, double y, double z, double speed, CallbackInfo ci) {
if (!AntarchyGravityApi.isGravityInverted(this.mob)) {
return;
}
Vec3 worldOffset = new Vec3(x - this.mob.getX(), y - this.mob.getY(), z - this.mob.getZ());
Vec3 localOffset = AntarchyGravityRotationUtil.vecWorldToPlayer(
worldOffset,
AntarchyGravityApi.getGravityDirection(this.mob));
this.wantedX = this.mob.getX() + localOffset.x;
this.wantedY = this.mob.getY() + localOffset.y;
this.wantedZ = this.mob.getZ() + localOffset.z;
this.speedModifier = speed;
}
}
@@ -0,0 +1,161 @@
package com.craisinlord.antarchy.mixins.gravity;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.PathfinderMob;
import net.minecraft.world.entity.Pose;
import net.minecraft.world.entity.ai.goal.RandomStrollGoal;
import net.minecraft.world.entity.EntityDimensions;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.AABB;
import org.spongepowered.asm.mixin.Final;
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;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(RandomStrollGoal.class)
public abstract class RandomStrollGoalGravityMixin {
@Unique
private static final int ANTARCHY$INVERTED_STROLL_ATTEMPTS = 12;
@Unique
private static final int ANTARCHY$INVERTED_STROLL_HORIZONTAL_RANGE = 7;
@Unique
private static final int ANTARCHY$INVERTED_STROLL_VERTICAL_RANGE = 2;
@Shadow
@Final
protected PathfinderMob mob;
@Shadow
protected double wantedX;
@Shadow
protected double wantedY;
@Shadow
protected double wantedZ;
@Shadow
@Final
protected double speedModifier;
@Shadow
protected int interval;
@Shadow
protected boolean forceTrigger;
@Shadow
@Final
private boolean checkNoActionTime;
@Unique
private int antarchy$invertedStrollTicks;
@Inject(method = "canUse", at = @At("HEAD"), cancellable = true)
private void antarchy$useInvertedRandomStroll(CallbackInfoReturnable<Boolean> cir) {
if (!AntarchyGravityApi.isGravityInverted(this.mob)) {
return;
}
if (this.mob.hasControllingPassenger()) {
cir.setReturnValue(false);
return;
}
if (!this.forceTrigger) {
if (this.checkNoActionTime && this.mob.getNoActionTime() >= 100) {
cir.setReturnValue(false);
return;
}
int chance = Math.max(1, this.interval);
if (this.mob.getRandom().nextInt(chance) != 0) {
cir.setReturnValue(false);
return;
}
}
cir.setReturnValue(this.antarchy$findInvertedStrollTarget());
}
@Inject(method = "start", at = @At("HEAD"), cancellable = true)
private void antarchy$startInvertedRandomStroll(CallbackInfo ci) {
if (!AntarchyGravityApi.isGravityInverted(this.mob)) {
return;
}
this.mob.getNavigation().stop();
this.mob.getMoveControl().setWantedPosition(this.wantedX, this.wantedY, this.wantedZ, this.speedModifier);
this.antarchy$invertedStrollTicks = 0;
this.forceTrigger = false;
ci.cancel();
}
@Inject(method = "canContinueToUse", at = @At("HEAD"), cancellable = true)
private void antarchy$continueInvertedRandomStroll(CallbackInfoReturnable<Boolean> cir) {
if (!AntarchyGravityApi.isGravityInverted(this.mob)) {
return;
}
double dx = this.wantedX - this.mob.getX();
double dy = this.wantedY - this.mob.getY();
double dz = this.wantedZ - this.mob.getZ();
boolean arrived = dx * dx + dy * dy + dz * dz <= 1.0D;
boolean timedOut = this.antarchy$invertedStrollTicks++ >= 200;
if (!arrived && !timedOut && !this.mob.hasControllingPassenger()) {
// RandomStrollGoal inherits Goal.tick(), so continuation is the
// declared per-tick hook available for refreshing direct movement.
this.mob.getMoveControl().setWantedPosition(this.wantedX, this.wantedY, this.wantedZ, this.speedModifier);
}
cir.setReturnValue(!this.mob.hasControllingPassenger() && !arrived && !timedOut);
}
@Unique
private boolean antarchy$findInvertedStrollTarget() {
int baseX = Mth.floor(this.mob.getX());
int baseY = Mth.floor(this.mob.getY() + this.mob.getBbHeight()) - 1;
int baseZ = Mth.floor(this.mob.getZ());
for (int attempt = 0; attempt < ANTARCHY$INVERTED_STROLL_ATTEMPTS; attempt++) {
int x = baseX + this.mob.getRandom().nextInt(ANTARCHY$INVERTED_STROLL_HORIZONTAL_RANGE * 2 + 1) - ANTARCHY$INVERTED_STROLL_HORIZONTAL_RANGE;
int y = baseY + this.mob.getRandom().nextInt(ANTARCHY$INVERTED_STROLL_VERTICAL_RANGE * 2 + 1) - ANTARCHY$INVERTED_STROLL_VERTICAL_RANGE;
int z = baseZ + this.mob.getRandom().nextInt(ANTARCHY$INVERTED_STROLL_HORIZONTAL_RANGE * 2 + 1) - ANTARCHY$INVERTED_STROLL_HORIZONTAL_RANGE;
if (this.antarchy$isValidInvertedStrollTarget(x, y, z)) {
this.wantedX = x + 0.5D;
this.wantedY = y + 1.0D - 1.0E-3D;
this.wantedZ = z + 0.5D;
return true;
}
}
return false;
}
@Unique
private boolean antarchy$isValidInvertedStrollTarget(int x, int y, int z) {
Level level = this.mob.level();
BlockPos ceilingPos = new BlockPos(x, y + 1, z);
if (!level.getBlockState(ceilingPos).isFaceSturdy(level, ceilingPos, Direction.DOWN)) {
return false;
}
EntityDimensions dimensions = this.mob.getDimensions(Pose.STANDING);
double halfWidth = dimensions.width() / 2.0D;
double topY = y + 1.0D - 1.0E-3D;
AABB bodyBox = new AABB(
x + 0.5D - halfWidth,
topY - dimensions.height(),
z + 0.5D - halfWidth,
x + 0.5D + halfWidth,
topY,
z + 0.5D + halfWidth
).deflate(1.0E-4D);
return level.noCollision(this.mob, bodyBox);
}
}
@@ -0,0 +1,17 @@
package com.craisinlord.antarchy.mixins.time;
import com.craisinlord.antarchy.content.time.TimeDilationApi;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.LivingEntity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(LivingEntity.class)
public abstract class LivingEntityTimeDilationCleanupMixin {
@Inject(method = "die", at = @At("HEAD"))
private void antarchy$clearTimeDilationOnDeath(DamageSource damageSource, CallbackInfo ci) {
TimeDilationApi.clearRate((LivingEntity) (Object) this);
}
}
@@ -28,6 +28,9 @@
"GlowVinesUnderLeavesMixin", "GlowVinesUnderLeavesMixin",
"GrindstoneMenuMixin", "GrindstoneMenuMixin",
"LivingEntityJumpingAccessor", "LivingEntityJumpingAccessor",
"CommandedMobMixin",
"LivingEntityRoyalEffectsMixin",
"MobCommandedTargetMixin",
"RoyalNoRespiteMixin", "RoyalNoRespiteMixin",
"MantisClawWallClimbMixin", "MantisClawWallClimbMixin",
"MilkBucketSizeResetMixin", "MilkBucketSizeResetMixin",
@@ -64,12 +67,15 @@
"gravity.FireworkRocketGravityMixin", "gravity.FireworkRocketGravityMixin",
"gravity.GroundPathNavigationMixin", "gravity.GroundPathNavigationMixin",
"gravity.LivingEntityMixin", "gravity.LivingEntityMixin",
"gravity.MeleeAttackGoalGravityMixin",
"gravity.MoveControlGravityMixin",
"gravity.MobHurtMixin", "gravity.MobHurtMixin",
"gravity.PathNavigationAccessor", "gravity.PathNavigationAccessor",
"gravity.PlayerAttackMixin", "gravity.PlayerAttackMixin",
"gravity.PlayerGravityMixin", "gravity.PlayerGravityMixin",
"gravity.PlayerPoseFitMixin", "gravity.PlayerPoseFitMixin",
"gravity.PlayerSneakMixin", "gravity.PlayerSneakMixin",
"gravity.RandomStrollGoalGravityMixin",
"gravity.ProjectileGravityMixin", "gravity.ProjectileGravityMixin",
"gravity.ServerChunkCacheAccessor", "gravity.ServerChunkCacheAccessor",
"gravity.ServerEntityAccessor", "gravity.ServerEntityAccessor",
@@ -86,6 +92,7 @@
"time.CreeperTimeDilationMixin", "time.CreeperTimeDilationMixin",
"time.EndCrystalTimeDilationMixin", "time.EndCrystalTimeDilationMixin",
"time.LivingEntityTimeDilationMixin", "time.LivingEntityTimeDilationMixin",
"time.LivingEntityTimeDilationCleanupMixin",
"time.LivingEntityAttackCooldownTimeDilationMixin", "time.LivingEntityAttackCooldownTimeDilationMixin",
"time.LookControlTimeDilationMixin", "time.LookControlTimeDilationMixin",
"time.MeleeAttackGoalTimeDilationMixin", "time.MeleeAttackGoalTimeDilationMixin",
@@ -0,0 +1,381 @@
{
"format_version": "1.8.0",
"animations": {
"animation": {
"loop": true,
"animation_length": 20.125,
"bones": {
"black_hole": {
"position": {
"0.0": {
"vector": [0, -100, 0]
},
"19.125": {
"vector": [0, 505, 0]
}
},
"scale": {
"0.0": {
"vector": [1, 1, 1]
},
"19.125": {
"vector": [10, 10, 10]
},
"19.25": {
"vector": [0, 0, 0]
}
}
},
"blocks": {
"position": {
"0.0": {
"post": {
"vector": [0, 0, 0]
},
"lerp_mode": "catmullrom"
},
"0.875": {
"post": {
"vector": [-141, 71, 0]
},
"lerp_mode": "catmullrom"
},
"2.375": {
"post": {
"vector": [-141, 104, 0]
},
"lerp_mode": "catmullrom"
}
},
"scale": {
"0.0": {
"vector": [1, 1, 1]
},
"2.4583": {
"vector": [0, 0, 0]
}
}
},
"blocks2": {
"position": {
"0.0": {
"post": {
"vector": [0, 0, 0]
},
"lerp_mode": "catmullrom"
},
"0.875": {
"post": {
"vector": [116, 72, 0]
},
"lerp_mode": "catmullrom"
},
"2.375": {
"post": {
"vector": [116, 124, 0]
},
"lerp_mode": "catmullrom"
}
},
"scale": {
"0.0": {
"vector": [1, 1, 1]
},
"2.4583": {
"vector": [0, 0, 0]
}
}
},
"blocks3": {
"position": {
"0.0": {
"post": {
"vector": [0, 0, 0]
},
"lerp_mode": "catmullrom"
},
"1.4167": {
"post": {
"vector": [-69, 96, -164]
},
"lerp_mode": "catmullrom"
}
},
"scale": {
"0.0": {
"vector": [1, 1, 1]
},
"2.4583": {
"vector": [0, 0, 0]
}
}
},
"blocks4": {
"position": {
"0.0": {
"post": {
"vector": [0, 0, 0]
},
"lerp_mode": "catmullrom"
},
"0.9583": {
"post": {
"vector": [35, 96, 135]
},
"lerp_mode": "catmullrom"
}
},
"scale": {
"0.0": {
"vector": [1, 1, 1]
},
"2.4583": {
"vector": [0, 0, 0]
}
}
},
"blocks5": {
"position": {
"0.0": {
"post": {
"vector": [0, 0, 0]
},
"lerp_mode": "catmullrom"
},
"2.375": {
"post": {
"vector": [113, 118, -188]
},
"lerp_mode": "catmullrom"
}
},
"scale": {
"0.0": {
"vector": [1, 1, 1]
},
"2.4583": {
"vector": [0, 0, 0]
}
}
},
"blocks6": {
"position": {
"0.0": {
"post": {
"vector": [0, 0, 0]
},
"lerp_mode": "catmullrom"
},
"0.7083": {
"post": {
"vector": [-114, 64, 112]
},
"lerp_mode": "catmullrom"
},
"1.2083": {
"post": {
"vector": [-146, 81, 162]
},
"lerp_mode": "catmullrom"
},
"1.9583": {
"post": {
"vector": [-146, 94, 162]
},
"lerp_mode": "catmullrom"
}
},
"scale": {
"0.0": {
"vector": [1, 1, 1]
},
"2.4583": {
"vector": [0, 0, 0]
}
}
},
"blocks7": {
"position": {
"0.0": {
"post": {
"vector": [0, 0, 0]
},
"lerp_mode": "catmullrom"
},
"1.9583": {
"post": {
"vector": [187, 123, 137]
},
"lerp_mode": "catmullrom"
}
},
"scale": {
"0.0": {
"vector": [1, 1, 1]
},
"2.4583": {
"vector": [0, 0, 0]
}
}
},
"blocks8": {
"position": {
"0.0": {
"post": {
"vector": [0, 0, 0]
},
"lerp_mode": "catmullrom"
},
"2.375": {
"post": {
"vector": [236, 124, 0]
},
"lerp_mode": "catmullrom"
}
},
"scale": {
"0.0": {
"vector": [1, 1, 1]
},
"2.4583": {
"vector": [0, 0, 0]
}
}
},
"blocks9": {
"position": {
"0.0": {
"post": {
"vector": [0, 0, 0]
},
"lerp_mode": "catmullrom"
},
"1.9583": {
"post": {
"vector": [-239, 106, 0]
},
"lerp_mode": "catmullrom"
}
},
"scale": {
"0.0": {
"vector": [1, 1, 1]
},
"2.4583": {
"vector": [0, 0, 0]
}
}
},
"blocks10": {
"position": {
"0.0": {
"post": {
"vector": [0, 0, 0]
},
"lerp_mode": "catmullrom"
},
"0.375": {
"post": {
"vector": [-10, 65, 0]
},
"lerp_mode": "catmullrom"
},
"2.375": {
"post": {
"vector": [-10, 119, 0]
},
"lerp_mode": "catmullrom"
}
},
"scale": {
"0.0": {
"vector": [1, 1, 1]
},
"2.4583": {
"vector": [0, 0, 0]
}
}
},
"blocks11": {
"position": {
"0.0": {
"post": {
"vector": [0, 0, 0]
},
"lerp_mode": "catmullrom"
},
"2.375": {
"post": {
"vector": [-223, 106, -145]
},
"lerp_mode": "catmullrom"
}
},
"scale": {
"0.0": {
"vector": [1, 1, 1]
},
"2.4583": {
"vector": [0, 0, 0]
}
}
},
"beam": {
"scale": {
"0.0": {
"post": {
"vector": [0.2, 0.5, 0.5]
},
"lerp_mode": "catmullrom"
},
"0.4167": {
"post": {
"vector": [-0.2, 3.6, 0.1]
},
"lerp_mode": "catmullrom"
},
"1.5833": {
"post": {
"vector": [-0.1, 3.6, 0.1]
},
"lerp_mode": "catmullrom"
},
"13.0833": {
"post": {
"vector": [0.4, 122.1, -0.4]
},
"lerp_mode": "catmullrom"
}
}
},
"group": {
"position": {
"0.0": {
"vector": [0, 0, 0]
},
"18.9167": {
"vector": [0, 0, 0]
},
"19.125": {
"vector": [0, 445, 0]
}
},
"scale": {
"0.0": {
"vector": [0, 0, 0]
},
"18.9167": {
"vector": [0, 0, 0]
},
"19.5833": {
"vector": [83.7, 1, 0]
},
"20.125": {
"vector": [0, 0, 0]
}
}
}
}
}
}
}
@@ -0,0 +1,33 @@
{
"format_version": "1.8.0",
"animations": {
"idle": {
"loop": true,
"animation_length": 0.25,
"bones": {
"fireball": {
"scale": {
"0.0": {
"post": {
"vector": [1, 1, 1]
},
"lerp_mode": "catmullrom"
},
"0.125": {
"post": {
"vector": [1, 1, 1.2]
},
"lerp_mode": "catmullrom"
},
"0.25": {
"post": {
"vector": [1, 1, 1]
},
"lerp_mode": "catmullrom"
}
}
}
}
}
}
}
@@ -0,0 +1,56 @@
{
"format_version": "1.8.0",
"animations": {
"animation": {
"loop": true,
"animation_length": 0.5,
"bones": {
"ice_ball": {
"rotation": {
"0.0": {
"vector": [0, 0, 0]
},
"0.25": {
"vector": [180, 0, 52.5]
},
"0.5": {
"vector": [360, 0, 0]
}
},
"scale": {
"0.0": {
"post": {
"vector": [1, 1, 1]
},
"lerp_mode": "catmullrom"
},
"0.125": {
"post": {
"vector": [1, 1.1, 1]
},
"lerp_mode": "catmullrom"
},
"0.25": {
"post": {
"vector": [1, 1, 1]
},
"lerp_mode": "catmullrom"
},
"0.375": {
"post": {
"vector": [1, 1.1, 1]
},
"lerp_mode": "catmullrom"
},
"0.5": {
"post": {
"vector": [1, 1, 1]
},
"lerp_mode": "catmullrom"
}
}
}
}
}
}
}
@@ -0,0 +1,363 @@
{
"format_version": "1.8.0",
"animations": {
"animation": {
"animation_length": 5.0833,
"bones": {
"ice_spike": {
"position": {
"0.0": {
"vector": [0, -80, 0]
},
"0.0417": {
"vector": [-6, -66.67, 0]
},
"0.0833": {
"vector": [0, -53.34, 0]
},
"0.125": {
"vector": [-5, -40.01, 0]
},
"0.1667": {
"vector": [-0.33, -26.67, 0]
},
"0.25": {
"vector": [0, 0, 0]
},
"0.8333": {
"vector": [0, 0, 0]
},
"1.0": {
"vector": [0, -47, 0]
}
},
"scale": {
"0.0": {
"vector": [1, 1, 1]
},
"0.2083": {
"vector": [1, 1, 1]
},
"0.25": {
"vector": [0, 0, 0]
},
"0.3333": {
"vector": [1, 1, 1]
},
"0.8333": {
"vector": [1, 1, 1]
},
"1.0": {
"vector": [1, 0, 1]
}
}
},
"ice_spike_impact": {
"scale": {
"0.0": {
"vector": [0, 0, 0]
},
"0.0417": [0, 0, 0],
"0.2083": {
"vector": [0, 0, 0]
},
"0.25": {
"vector": [1, 1, 1]
},
"0.2917": {
"vector": [1, 1.3, 1]
},
"0.3333": {
"vector": [0, 0, 0]
}
}
},
"sparkle": {
"scale": {
"0.0": {
"vector": [0, 0, 0]
},
"0.2917": {
"vector": [0, 0, 0]
},
"0.4583": {
"vector": [1.2, 2.5, 1]
},
"0.625": {
"vector": [0, 1, 1]
}
}
},
"ice_spike_impact2": {
"scale": {
"1.0": {
"vector": [0, 0, 0]
},
"1.0417": [0, 0, 0],
"1.2083": {
"vector": [0, 0, 0]
},
"1.25": {
"vector": [1, 1, 1]
},
"1.2917": {
"vector": [1, 1.3, 1]
},
"1.3333": {
"vector": [0, 0, 0]
}
}
},
"ice_spike2": {
"position": {
"1.0": {
"vector": [0, -80, 0]
},
"1.0417": {
"vector": [-6, -66.67, 0]
},
"1.0833": {
"vector": [0, -53.34, 0]
},
"1.125": {
"vector": [-5, -40.01, 0]
},
"1.1667": {
"vector": [-0.33, -26.67, 0]
},
"1.25": {
"vector": [0, 0, 0]
},
"1.8333": {
"vector": [0, 0, 0]
},
"2.0": {
"vector": [0, -47, 0]
}
},
"scale": {
"1.0": {
"vector": [1, 1, 1]
},
"1.2083": {
"vector": [1, 1, 1]
},
"1.25": {
"vector": [0, 0, 0]
},
"1.3333": {
"vector": [1, 1, 1]
},
"1.8333": {
"vector": [1, 1, 1]
},
"2.0": {
"vector": [1, 0, 1]
}
}
},
"sparkle2": {
"scale": {
"1.0": {
"vector": [0, 0, 0]
},
"1.2917": {
"vector": [0, 0, 0]
},
"1.4583": {
"vector": [1.2, 2.5, 1]
},
"1.625": {
"vector": [0, 1, 1]
}
}
},
"ice_spike_impact3": {
"scale": {
"2.0": {
"vector": [0, 0, 0]
},
"2.0417": [0, 0, 0],
"2.2083": {
"vector": [0, 0, 0]
},
"2.25": {
"vector": [1, 1, 1]
},
"2.2917": {
"vector": [1, 1.3, 1]
},
"2.3333": {
"vector": [0, 0, 0]
}
}
},
"ice_spike3": {
"position": {
"2.0": {
"vector": [0, -80, 0]
},
"2.0417": {
"vector": [-6, -66.67, 0]
},
"2.0833": {
"vector": [0, -53.34, 0]
},
"2.125": {
"vector": [-5, -40.01, 0]
},
"2.1667": {
"vector": [-0.33, -26.67, 0]
},
"2.25": {
"vector": [0, 0, 0]
},
"2.8333": {
"vector": [0, 0, 0]
},
"3.0": {
"vector": [0, -47, 0]
}
},
"scale": {
"2.0": {
"vector": [1, 1, 1]
},
"2.2083": {
"vector": [1, 1, 1]
},
"2.25": {
"vector": [0, 0, 0]
},
"2.3333": {
"vector": [1, 1, 1]
},
"2.8333": {
"vector": [1, 1, 1]
},
"3.0": {
"vector": [1, 0, 1]
}
}
},
"sparkle3": {
"scale": {
"2.0": {
"vector": [0, 0, 0]
},
"2.2917": {
"vector": [0, 0, 0]
},
"2.4583": {
"vector": [1.2, 2.5, 1]
},
"2.625": {
"vector": [0, 1, 1]
}
}
},
"ice_spike_impact4": {
"scale": {
"3.0": {
"vector": [0, 0, 0]
},
"3.0417": [0, 0, 0],
"3.2083": {
"vector": [0, 0, 0]
},
"3.25": {
"vector": [1, 1, 1]
},
"3.2917": {
"vector": [1, 1.3, 1]
},
"3.3333": {
"vector": [0, 0, 0]
}
}
},
"ice_spike4": {
"position": {
"3.0": {
"vector": [0, -80, 0]
},
"3.0417": {
"vector": [-6, -66.67, 0]
},
"3.0833": {
"vector": [0, -53.34, 0]
},
"3.125": {
"vector": [-5, -40.01, 0]
},
"3.1667": {
"vector": [-0.33, -26.67, 0]
},
"3.25": {
"vector": [0, 0, 0]
},
"3.8333": {
"vector": [0, 0, 0]
},
"4.0": {
"vector": [0, -47, 0]
}
},
"scale": {
"3.0": {
"vector": [1, 1, 1]
},
"3.2083": {
"vector": [1, 1, 1]
},
"3.25": {
"vector": [0, 0, 0]
},
"3.3333": {
"vector": [1, 1, 1]
},
"3.8333": {
"vector": [1, 1, 1]
},
"4.0": {
"vector": [1, 0, 1]
}
}
},
"sparkle4": {
"scale": {
"3.0": {
"vector": [0, 0, 0]
},
"3.2917": {
"vector": [0, 0, 0]
},
"3.4583": {
"vector": [1.2, 2.5, 1]
},
"3.625": {
"vector": [0, 1, 1]
}
}
}
},
"particle_effects": {
"0.0": {
"effect": "particles"
},
"1.0": {
"effect": "particles",
"locator": "spike_2"
},
"2.0": {
"effect": "particles",
"locator": "spike_3"
},
"3.0": {
"effect": "particles",
"locator": "spike_4"
}
}
}
}
}
@@ -0,0 +1,7 @@
{
"variants": {
"": {
"model": "minecraft:block/air"
}
}
}
@@ -0,0 +1,7 @@
{
"variants": {
"": {
"model": "minecraft:block/air"
}
}
}
@@ -0,0 +1,7 @@
{
"variants": {
"": {
"model": "minecraft:block/air"
}
}
}
@@ -1,31 +1,41 @@
{ {
"variants": { "variants": {
"thickness=base,vertical_direction=down": { "thickness=base,vertical_direction=down": {
"model": "antarchy:block/typhonite_spike_down_base" "model": "antarchy:block/typhonite_spike_down_base",
"x": 180,
"uvlock": true
}, },
"thickness=base,vertical_direction=up": { "thickness=base,vertical_direction=up": {
"model": "antarchy:block/typhonite_spike_up_base" "model": "antarchy:block/typhonite_spike_up_base"
}, },
"thickness=frustum,vertical_direction=down": { "thickness=frustum,vertical_direction=down": {
"model": "antarchy:block/typhonite_spike_down_frustum" "model": "antarchy:block/typhonite_spike_down_frustum",
"x": 180,
"uvlock": true
}, },
"thickness=frustum,vertical_direction=up": { "thickness=frustum,vertical_direction=up": {
"model": "antarchy:block/typhonite_spike_up_frustum" "model": "antarchy:block/typhonite_spike_up_frustum"
}, },
"thickness=middle,vertical_direction=down": { "thickness=middle,vertical_direction=down": {
"model": "antarchy:block/typhonite_spike_down_middle" "model": "antarchy:block/typhonite_spike_down_middle",
"x": 180,
"uvlock": true
}, },
"thickness=middle,vertical_direction=up": { "thickness=middle,vertical_direction=up": {
"model": "antarchy:block/typhonite_spike_up_middle" "model": "antarchy:block/typhonite_spike_up_middle"
}, },
"thickness=tip,vertical_direction=down": { "thickness=tip,vertical_direction=down": {
"model": "antarchy:block/typhonite_spike_down_tip" "model": "antarchy:block/typhonite_spike_down_tip",
"x": 180,
"uvlock": true
}, },
"thickness=tip,vertical_direction=up": { "thickness=tip,vertical_direction=up": {
"model": "antarchy:block/typhonite_spike_up_tip" "model": "antarchy:block/typhonite_spike_up_tip"
}, },
"thickness=tip_merge,vertical_direction=down": { "thickness=tip_merge,vertical_direction=down": {
"model": "antarchy:block/typhonite_spike_down_tip_merge" "model": "antarchy:block/typhonite_spike_down_tip_merge",
"x": 180,
"uvlock": true
}, },
"thickness=tip_merge,vertical_direction=up": { "thickness=tip_merge,vertical_direction=up": {
"model": "antarchy:block/typhonite_spike_up_tip_merge" "model": "antarchy:block/typhonite_spike_up_tip_merge"
Loaded 100 of 169 files, more files were not shown because too many files have changed in this diff. Show more