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
+4885 -1048

No files matched your search

@@ -1368,6 +1368,8 @@ public final class AntarchySettings {
public static void setKrakensGraspAttackSpeed(double value) { krakensGraspAttackSpeed = value; }
public static void setKrakensGraspThrownDamage(double value) { krakensGraspThrownDamage = 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 setKrakensGraspInnateLoyaltyLevel(int value) { krakensGraspInnateLoyaltyLevel = 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;
public class ReverieEntity extends PathfinderMob implements FlyingAnimal, GeoEntity {
private static final int DAMAGE_REACTION_DURATION_TICKS = 40;
private static final EntityDataAccessor<Integer> MOOD =
SynchedEntityData.defineId(ReverieEntity.class, EntityDataSerializers.INT);
private static final EntityDataAccessor<Integer> ANIMATION_STATE =
@@ -309,7 +310,7 @@ public class ReverieEntity extends PathfinderMob implements FlyingAnimal, GeoEnt
this.releaseFocus();
this.clearNoticePlayer();
this.ambientTarget = null;
this.damageReactionTicks = 0;
this.damageReactionTicks = DAMAGE_REACTION_DURATION_TICKS;
this.inversionReactionTicks = 0;
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.Attributes;
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.PanicGoal;
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.sounds.SoundEvent;
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.ServerLevelAccessor;
import org.jetbrains.annotations.Nullable;
@@ -76,9 +80,11 @@ public class StinkBugEntity extends Animal implements GeoEntity {
protected void registerGoals() {
this.goalSelector.addGoal(0, new FloatGoal(this));
this.goalSelector.addGoal(1, new PanicGoal(this, 1.4D));
this.goalSelector.addGoal(2, new WaterAvoidingRandomStrollGoal(this, 0.9D));
this.goalSelector.addGoal(3, new LookAtPlayerGoal(this, Player.class, 6.0F));
this.goalSelector.addGoal(4, new RandomLookAroundGoal(this));
this.goalSelector.addGoal(2, new TemptGoal(this, 1.0D, Ingredient.of(Items.ROTTEN_FLESH), false));
this.goalSelector.addGoal(3, new BreedGoal(this, 0.9D));
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
@@ -108,7 +114,7 @@ public class StinkBugEntity extends Animal implements GeoEntity {
@Override
public boolean isFood(ItemStack stack) {
return false;
return stack.is(Items.ROTTEN_FLESH);
}
@Override
@@ -1026,7 +1026,8 @@ public class KrakenEntity extends Monster implements GeoEntity, MultipartEntityO
for (int i = 0; i < strikeBolts; i++) {
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 {
this.lightningStrikeCooldown = this.isPhaseTwo() ? 30 : 60;
@@ -91,12 +91,14 @@ public class KrakensGraspItem extends TridentItem {
public static void strikeLightning(LivingEntity target, ServerLevel level) {
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);
}
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.moveTo(x, y, z);
lightningBolt.setVisualOnly(true);
level.addFreshEntity(lightningBolt);
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": [
{
"description": {
@@ -1,5 +1,5 @@
{
"format_version": "1.21.110",
"format_version": "1.12.0",
"minecraft:geometry": [
{
"description": {
@@ -1,5 +1,5 @@
{
"format_version": "1.21.110",
"format_version": "1.12.0",
"minecraft:geometry": [
{
"description": {
@@ -1,5 +1,5 @@
{
"format_version": "1.21.110",
"format_version": "1.12.0",
"minecraft:geometry": [
{
"description": {
@@ -1,5 +1,5 @@
{
"textures": {
"particle": "antarchy:block/nadir/nadir_hanging"
"particle": "antarchy:block/nadir/nadir_planks"
}
}
@@ -1,5 +1,5 @@
{
"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 bigBerthaNightmareDamageBonusPercent = 30.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 dimensionalTearInvertedDurationTicks = 2400;
private static int dimensionalTearEmergenceMinIntervalTicks = 2400;
private static int dimensionalTearEmergenceMaxIntervalTicks = 7200;
private static int dimensionalTearEmergenceMinIntervalTicks = 400;
private static int dimensionalTearEmergenceMaxIntervalTicks = 1200;
private static float dimensionalTearLucidEventChance = 0.65F;
private static int ultimateToolEnchantability = 25;
private static int ultimateArmorEnchantability = 10;
@@ -250,6 +250,23 @@ public final class AntarchySettings {
private static double queenHealth = 6000.0D;
private static double kingAttackDamage = 50.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 royalBossFollowRange = 128.0D;
private static double royalBossMovementSpeed = 0.22D;
@@ -391,7 +408,7 @@ public final class AntarchySettings {
private static double toreterrorDamageRange = 25.0D;
private static double brutalflyDamageRange = 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 lucidAttackDamage = 4.0D;
private static double lucidMovementSpeed = 0.22D;
@@ -402,7 +419,7 @@ public final class AntarchySettings {
private static double vortexMovementSpeed = 0.22D;
private static double vortexFlyingSpeed = 0.35D;
private static int vortexMaxActiveVortexes = 3;
private static int windVortexDurationTicks = 140;
private static int windVortexDurationTicks = 240;
private static double windVortexPullStrength = 0.32D;
private static double windVortexLaunchStrength = 1.0D;
private static boolean eyeOfTheStormEnabled = true;
@@ -534,6 +551,8 @@ public final class AntarchySettings {
private static double queenBeamDamage = 45.0D;
private static double queenBeamRange = 100.0D;
private static int queenBeamDurationTicks = 30;
private static int queenBeamWindupTicks = 20;
private static int queenBeamTravelTicks = 12;
private static int queenBeamCooldownTicks = 90;
private static double queenBeamTracking = 0.1D;
private static double queenBeamTerrainRadius = 5.0D;
@@ -541,6 +560,8 @@ public final class AntarchySettings {
private static double kingBeamDamage = 50.0D;
private static double kingBeamRange = 100.0D;
private static int kingBeamDurationTicks = 30;
private static int kingBeamWindupTicks = 20;
private static int kingBeamTravelTicks = 12;
private static int kingBeamCooldownTicks = 90;
private static double kingBeamTracking = 0.1D;
private static double kingBeamTerrainRadius = 5.0D;
@@ -803,6 +824,8 @@ public final class AntarchySettings {
return bigBerthaNightmareDamageBonusPercent;
}
public static double krakensGraspLightningDamage() { return krakensGraspLightningDamage; }
public static double potentNyxiteInvertedDurationSeconds() {
return potentNyxiteInvertedDurationSeconds;
}
@@ -2013,6 +2036,40 @@ public final class AntarchySettings {
public static double queenAttackDamage() { return queenAttackDamage; }
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 void setRoyalBossArmor(double v) { royalBossArmor = v; }
@@ -2832,6 +2889,10 @@ public final class AntarchySettings {
public static void setQueenBeamRange(double v) { queenBeamRange = v; }
public static int queenBeamDurationTicks() { return queenBeamDurationTicks; }
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 void setQueenBeamCooldownTicks(int v) { queenBeamCooldownTicks = v; }
public static double queenBeamTracking() { return queenBeamTracking; }
@@ -2846,6 +2907,10 @@ public final class AntarchySettings {
public static void setKingBeamRange(double v) { kingBeamRange = v; }
public static int kingBeamDurationTicks() { return kingBeamDurationTicks; }
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 void setKingBeamCooldownTicks(int v) { kingBeamCooldownTicks = v; }
public static double kingBeamTracking() { return kingBeamTracking; }
@@ -157,6 +157,10 @@ public final class AntarchyObjects {
@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;
@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;
@SuppressWarnings("unchecked")
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;
}
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) {
KING = supplier;
}
@@ -202,6 +202,7 @@ public final class AntarchySoundEvents {
public static Supplier<SoundEvent> KING_ROAR = (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> QUEEN_FLY_LOOP = (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_SHOOT = (Supplier<SoundEvent>) UNBOUND;
@@ -406,6 +407,7 @@ public final class AntarchySoundEvents {
Supplier<SoundEvent> kingRoar,
Supplier<SoundEvent> kingWingFlap,
Supplier<SoundEvent> kingFlyLoop,
Supplier<SoundEvent> queenFlyLoop,
Supplier<SoundEvent> princeIdle,
Supplier<SoundEvent> princeBite,
Supplier<SoundEvent> princeShoot,
@@ -606,6 +608,7 @@ public final class AntarchySoundEvents {
KING_ROAR = kingRoar;
KING_WING_FLAP = kingWingFlap;
KING_FLY_LOOP = kingFlyLoop;
QUEEN_FLY_LOOP = queenFlyLoop;
PRINCE_IDLE = princeIdle;
PRINCE_BITE = princeBite;
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_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<?>> 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() {
}
@@ -12,17 +12,23 @@ import net.minecraft.util.RandomSource;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
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 MAX_LINK_RANGE = 500;
private static final int PARTNER_ORIGIN_ATTEMPTS = 12;
private static final int PARTNER_SEARCH_ATTEMPTS = 48;
private static final int LOCAL_SEARCH_ATTEMPTS = 18;
private static final int SEARCH_ATTEMPTS_PER_TICK = 2;
private static final int PARTNER_ORIGIN_ATTEMPTS = 8;
private static final int PARTNER_SEARCH_ATTEMPTS = 32;
private static final int LOCAL_SEARCH_ATTEMPTS = 12;
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 localAttempts;
@@ -64,6 +70,11 @@ public class DimensionalTearMarkerBlockEntity extends BlockEntity {
continue;
}
if (partnerOrigin == null && partnerOriginAttempts == 0
&& countNearbyTears(level, origin) >= NATURAL_TEAR_LOCAL_CAP) {
return false;
}
if (partnerOrigin == null || partnerSearchAttempts >= PARTNER_SEARCH_ATTEMPTS) {
if (partnerOriginAttempts >= PARTNER_ORIGIN_ATTEMPTS) {
return false;
@@ -75,7 +86,8 @@ public class DimensionalTearMarkerBlockEntity extends BlockEntity {
partnerSearchAttempts++;
BlockPos candidate = randomCandidate(partnerOrigin, random);
if (!isUsablePocket(level, candidate)) {
if (Math.abs(candidate.getY() - firstPocket.getY()) < MIN_VERTICAL_SEPARATION
|| !isUsablePocket(level, candidate)) {
continue;
}
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 dx = Mth.floor(Math.cos(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);
}
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) {
int minY = level.getMinBuildHeight() + 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.item.ItemEntity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.util.Mth;
public final class ClientTimeDilationTicker {
@@ -32,6 +33,11 @@ public final class ClientTimeDilationTicker {
com.craisinlord.antarchy.content.time.TimeDilationApi.clearSyncedClientRates();
}
// 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) {
@@ -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;
}
private static final float BEAM_OUTER_RADIUS = 0.55F;
private static final float BEAM_INNER_RADIUS = 0.26F;
private static final float BEAM_OUTER_RADIUS = 1.10F;
private static final float BEAM_INNER_RADIUS = 0.52F;
private static final float BEAM_TILE_BLOCKS = 3.0F;
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;
public class WindVortexRenderer extends EntityRenderer<WindVortexEntity> {
private static final int RINGS = 10;
private static final int SEGMENTS_PER_RING = 12;
private static final int ARC_STEPS = 6;
private static final int RINGS = 11;
private static final int SQUARES_PER_SIDE = 3;
private static final float BASE_RADIUS = 0.35F;
public WindVortexRenderer(EntityRendererProvider.Context context) {
@@ -44,48 +43,72 @@ public class WindVortexRenderer extends EntityRenderer<WindVortexEntity> {
float radius = Mth.lerp(shapeProgress, BASE_RADIUS, topRadius);
float ringSpin = spinDirection * age * (0.13F + progress * 0.045F) + ring * 0.47F;
float alpha = alphaMultiplier;
float thickness = 0.025F + radius * 0.012F;
float arcLength = 0.34F + progress * 0.08F;
float halfSide = radius * 0.92F;
float tileSize = Math.max(0.035F, radius * 0.12F);
int red = push ? 70 : 64;
int green = push ? 220 : 255;
int blue = push ? 255 : 56;
for (int segment = 0; segment < SEGMENTS_PER_RING; segment++) {
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);
}
drawSquareRing(consumer, pose, basis, halfSide, axisDistance, tileSize, ringSpin,
alpha, red, green, blue);
}
super.render(entity, entityYaw, partialTick, poseStack, bufferSource, packedLight);
}
private static void drawArc(VertexConsumer consumer, Matrix4f pose, WindVortexEntity.Basis basis, float radius,
float axisDistance, float thickness, float start, float end, float alpha, int red, int green, int blue) {
float previousAngle = start;
for (int step = 1; step <= ARC_STEPS; step++) {
float angle = Mth.lerp(step / (float) ARC_STEPS, start, end);
addLineQuad(consumer, pose, basis, radius, axisDistance, thickness, previousAngle, angle, alpha, red, green, blue);
previousAngle = angle;
private static void drawSquareRing(VertexConsumer consumer, Matrix4f pose, WindVortexEntity.Basis basis,
float halfSide, float axisDistance, float tileSize, float rotation, float alpha,
int red, int green, int blue) {
for (int side = 0; side < 4; side++) {
for (int tile = 0; tile < SQUARES_PER_SIDE; tile++) {
float start = -halfSide + (tile + 0.18F) * (2.0F * halfSide / SQUARES_PER_SIDE);
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,
float axisDistance, float thickness, float startAngle, float endAngle, float alpha, int red, int green, int blue) {
org.joml.Vector3f start = point(basis, radius, axisDistance, startAngle);
org.joml.Vector3f end = point(basis, radius, axisDistance, endAngle);
org.joml.Vector3f axisOffset = new org.joml.Vector3f((float) basis.axis.x, (float) basis.axis.y, (float) basis.axis.z).mul(thickness);
float middleAngle = (startAngle + endAngle) * 0.5F;
org.joml.Vector3f radialOffset = new org.joml.Vector3f(
(float) (basis.sideA.x * Mth.cos(middleAngle) + basis.sideB.x * Mth.sin(middleAngle)),
(float) (basis.sideA.y * Mth.cos(middleAngle) + basis.sideB.y * Mth.sin(middleAngle)),
(float) (basis.sideA.z * Mth.cos(middleAngle) + basis.sideB.z * Mth.sin(middleAngle))
).mul(thickness);
private static void addSquareTile(VertexConsumer consumer, Matrix4f pose, WindVortexEntity.Basis basis,
float halfSide, float axisDistance, float tileSize, float sideAngle, float start, float end,
float alpha, int red, int green, int blue) {
org.joml.Vector3f center = squarePoint(basis, halfSide, axisDistance, sideAngle, (start + end) * 0.5F);
org.joml.Vector3f tangent = new org.joml.Vector3f(
(float) (-basis.sideA.x * Mth.sin(sideAngle) + basis.sideB.x * Mth.cos(sideAngle)),
(float) (-basis.sideA.y * Mth.sin(sideAngle) + basis.sideB.y * Mth.cos(sideAngle)),
(float) (-basis.sideA.z * Mth.sin(sideAngle) + basis.sideB.z * Mth.cos(sideAngle)));
org.joml.Vector3f axisOffset = new org.joml.Vector3f(
(float) basis.axis.x, (float) basis.axis.y, (float) basis.axis.z).mul(tileSize * 0.5F);
tangent.mul(tileSize * 0.5F);
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);
addQuad(consumer, pose, start, end, radialOffset, red, green, blue, a);
private static org.joml.Vector3f squarePoint(WindVortexEntity.Basis basis, float halfSide,
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,
@@ -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, 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) {
double radialA = Mth.cos(angle) * radius;
double radialB = Mth.sin(angle) * radius;
double x = basis.axis.x * axisDistance + basis.sideA.x * radialA + basis.sideB.x * radialB;
double y = basis.axis.y * axisDistance + basis.sideA.y * radialA + basis.sideB.y * radialB;
double z = basis.axis.z * axisDistance + basis.sideA.z * radialA + basis.sideB.z * radialB;
return new org.joml.Vector3f((float) x, (float) y, (float) z);
// The lightning render type can cull one winding from some camera
// angles. Emit the opposite winding as well so every ribbon is
// visible from both sides.
addVertex(consumer, pose, start.x + offset.x, start.y + offset.y, start.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);
}
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(
"dread", "long_dread", "inversion", "long_inversion", "stinky", "long_stinky",
"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();
@@ -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;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityDirection;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityTransition;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.effect.MobEffect;
import net.minecraft.world.effect.MobEffectCategory;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
public final class InvertedMobEffect extends MobEffect {
private static final AntarchyGravityTransition EFFECT_TRANSITION = new AntarchyGravityTransition(12);
@@ -19,6 +22,19 @@ public final class InvertedMobEffect extends MobEffect {
public boolean applyEffectTick(LivingEntity entity, int amplifier) {
if (!entity.level().isClientSide()) {
if (AntarchyGravityApi.getGravityDirection(entity) != AntarchyGravityDirection.UP || !AntarchyGravityApi.isGravityForced(entity)) {
MobEffectInstance effect = entity.getEffect(com.craisinlord.antarchy.content.AntarchyObjects.INVERTED_EFFECT.get());
if (entity instanceof Player || com.craisinlord.antarchy.content.worldgen.thoraxis.ThoraxisUndersideManager.isThoraxis(entity.level())) {
Antarchy.LOGGER.info(
"[antarchy-gravity] applying inverted gravity from effect targetType={} uuid={} pos=({}, {}, {}) dim={} duration={} ambient={} visible={} forcedBefore={} gameTime={}",
entity.getType(), entity.getUUID(), entity.getX(), entity.getY(), entity.getZ(),
entity.level().dimension().location(),
effect != null ? effect.getDuration() : -1,
effect != null && effect.isAmbient(),
effect != null && effect.isVisible(),
AntarchyGravityApi.isGravityForced(entity),
entity.level().getGameTime()
);
}
AntarchyGravityApi.setForcedGravityDirection(entity, AntarchyGravityDirection.UP, EFFECT_TRANSITION);
}
} else {
@@ -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
private ItemEntity cachedRottenFlesh;
private int nextFoodSearchTick;
private int ignoredRottenFleshId = -1;
private int ignoredRottenFleshUntilTick;
private int guardNestCooldown;
public BedBugEntity(EntityType<? extends BedBugEntity> entityType, Level level) {
@@ -450,6 +452,24 @@ public class BedBugEntity extends Animal implements GeoEntity {
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
private ItemEntity findNearestRottenFlesh() {
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),
itemEntity -> itemEntity.isAlive()
&& !itemEntity.getItem().isEmpty()
&& itemEntity.getItem().is(Items.ROTTEN_FLESH))
&& itemEntity.getItem().is(Items.ROTTEN_FLESH)
&& !this.isTemporarilyIgnoredFood(itemEntity))
.stream()
.min(Comparator.comparingDouble(this::distanceToSqr))
.orElse(null);
@@ -581,6 +602,7 @@ public class BedBugEntity extends Animal implements GeoEntity {
private ItemEntity targetFood;
private double lastDistanceSqr;
private int stuckTicks;
private int pathRefreshCooldown;
private SeekRottenFleshGoal() {
this.setFlags(EnumSet.of(Flag.MOVE, Flag.LOOK));
@@ -614,6 +636,7 @@ public class BedBugEntity extends Animal implements GeoEntity {
BedBugEntity.this.setTarget(null);
this.lastDistanceSqr = Double.MAX_VALUE;
this.stuckTicks = 0;
this.pathRefreshCooldown = 0;
}
@Override
@@ -622,6 +645,7 @@ public class BedBugEntity extends Animal implements GeoEntity {
this.targetFood = null;
this.lastDistanceSqr = Double.MAX_VALUE;
this.stuckTicks = 0;
this.pathRefreshCooldown = 0;
}
@Override
@@ -631,7 +655,14 @@ public class BedBugEntity extends Animal implements GeoEntity {
}
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);
if (distanceSqr < this.lastDistanceSqr - 0.2D) {
@@ -639,7 +670,7 @@ public class BedBugEntity extends Animal implements GeoEntity {
this.stuckTicks = 0;
} else if (BedBugEntity.this.getNavigation().isDone() && distanceSqr > 4.0D) {
if (++this.stuckTicks >= 20) {
BedBugEntity.this.invalidateFoodCache();
BedBugEntity.this.temporarilyIgnoreRottenFlesh(this.targetFood);
this.targetFood = null;
return;
}
@@ -29,12 +29,14 @@ import org.jetbrains.annotations.Nullable;
import java.util.List;
public class DiamondMinecartEntity extends Minecart {
private static final long INPUT_TIMEOUT_TICKS = 5L;
private static final EntityDataAccessor<Float> SYNCED_SPEED =
SynchedEntityData.defineId(DiamondMinecartEntity.class, EntityDataSerializers.FLOAT);
private static final EntityDataAccessor<Integer> SYNCED_FACING =
SynchedEntityData.defineId(DiamondMinecartEntity.class, EntityDataSerializers.INT);
private byte inputFlags = 0;
private long lastInputGameTime = Long.MIN_VALUE;
private float currentSpeed = 0.0F;
private Direction facingDir = Direction.SOUTH;
private BlockPos lastBlockPos = null;
@@ -109,19 +111,38 @@ public class DiamondMinecartEntity extends Minecart {
@Override
protected double getMaxSpeed() {
return 0.4D;
return AntarchySettings.diamondMinecartEnabled()
? AntarchySettings.diamondMinecartMaxSpeed()
: 0.4D;
}
public void onInputReceived(byte flags) {
this.inputFlags = flags;
this.lastInputGameTime = this.level().getGameTime();
}
@Override
public void tick() {
super.tick();
if (!this.level().isClientSide) {
this.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();
boolean hasRider = rider != null;
@@ -176,6 +197,17 @@ public class DiamondMinecartEntity extends Minecart {
if (AntarchySettings.diamondMinecartMobDamageEnabled() && this.currentSpeed > 0.15F && hasRider) {
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) {
@@ -1,8 +1,11 @@
package com.craisinlord.antarchy.content.entity;
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.gravity.AntarchyGravityApi;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityDirection;
import com.craisinlord.antarchy.content.worldgen.thoraxis.ThoraxisUndersideManager;
import java.util.List;
import java.util.UUID;
import net.minecraft.core.BlockPos;
@@ -89,6 +92,11 @@ public class ManticoreEntity extends Monster implements GeoEntity {
this.xpReward = 12;
}
@Override
public boolean fireImmune() {
return true;
}
public static AttributeSupplier.Builder createAttributes() {
return Mob.createMobAttributes()
.add(Attributes.MAX_HEALTH, AntarchySettings.manticoreHealth())
@@ -206,6 +214,8 @@ public class ManticoreEntity extends Monster implements GeoEntity {
return;
}
this.tickQueenSummonInversionRule();
boolean flying = !this.onGround();
if (flying != this.wasFlyingLastTick) {
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);
}
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) {
this.swing(InteractionHand.MAIN_HAND);
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;
}
@Override
public boolean canBeAffected(MobEffectInstance effectInstance) {
if (this.isQueenSummoned() && effectInstance.is(AntarchyObjects.INVERTED_EFFECT.get())) {
return this.canUseQueenSummonInversion();
}
return super.canBeAffected(effectInstance);
}
@Override
public boolean isInWall() {
return false;
@@ -75,6 +75,7 @@ import software.bernie.geckolib.animation.RawAnimation;
import software.bernie.geckolib.util.GeckoLibUtil;
public class ReverieEntity extends PathfinderMob implements FlyingAnimal, GeoEntity {
private static final int DAMAGE_REACTION_DURATION_TICKS = 40;
private static final EntityDataAccessor<Integer> MOOD =
SynchedEntityData.defineId(ReverieEntity.class, EntityDataSerializers.INT);
private static final EntityDataAccessor<Integer> ANIMATION_STATE =
@@ -318,7 +319,7 @@ public class ReverieEntity extends PathfinderMob implements FlyingAnimal, GeoEnt
this.releaseFocus();
this.clearNoticePlayer();
this.ambientTarget = null;
this.damageReactionTicks = 0;
this.damageReactionTicks = DAMAGE_REACTION_DURATION_TICKS;
this.inversionReactionTicks = 0;
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.level.Level;
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.instance.AnimatableInstanceCache;
import software.bernie.geckolib.animation.AnimatableManager;
@@ -51,6 +54,10 @@ import org.jetbrains.annotations.Nullable;
import java.util.UUID;
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 EntityDataAccessor<Integer> TEXTURE_VARIANT =
SynchedEntityData.defineId(ScorpionEntity.class, EntityDataSerializers.INT);
@@ -81,6 +88,11 @@ public class ScorpionEntity extends Monster implements GeoEntity {
this.xpReward = 5;
}
@Override
public boolean fireImmune() {
return true;
}
public static AttributeSupplier.Builder createAttributes() {
return Mob.createMobAttributes()
.add(Attributes.MAX_HEALTH, AntarchySettings.scorpionHealth())
@@ -98,6 +110,12 @@ public class ScorpionEntity extends Monster implements GeoEntity {
|| spawnReason == MobSpawnType.COMMAND) {
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)) {
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);
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.attackDamageApplied = true;
this.doHurtTarget(this.attackTarget);
@@ -328,6 +347,10 @@ public class ScorpionEntity extends Monster implements GeoEntity {
if (ScorpionEntity.this.attackTarget != null) {
ScorpionEntity.this.getLookControl().setLookAt(ScorpionEntity.this.attackTarget, 30.0F, 30.0F);
}
if (ScorpionEntity.this.isAttackLocked()) {
ScorpionEntity.this.getNavigation().stop();
return;
}
super.tick();
}
@@ -341,8 +364,10 @@ public class ScorpionEntity extends Monster implements GeoEntity {
if (ScorpionEntity.this.isAttackLocked()) {
return;
}
if (this.canPerformAttack(enemy) && this.isTimeToAttack() && ScorpionEntity.this.attackCooldownTicks <= 0) {
this.resetAttackCooldown();
// Inverted melee tick handling replaces vanilla MeleeAttackGoal.tick,
// 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);
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.Attributes;
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.PanicGoal;
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.sounds.SoundEvent;
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.ServerLevelAccessor;
import net.minecraft.world.Difficulty;
@@ -86,9 +90,11 @@ public class StinkBugEntity extends Animal implements GeoEntity {
protected void registerGoals() {
this.goalSelector.addGoal(0, new FloatGoal(this));
this.goalSelector.addGoal(1, new PanicGoal(this, 1.4D));
this.goalSelector.addGoal(2, new WaterAvoidingRandomStrollGoal(this, 0.9D));
this.goalSelector.addGoal(3, new LookAtPlayerGoal(this, Player.class, 6.0F));
this.goalSelector.addGoal(4, new RandomLookAroundGoal(this));
this.goalSelector.addGoal(2, new TemptGoal(this, 1.0D, Ingredient.of(Items.ROTTEN_FLESH), false));
this.goalSelector.addGoal(3, new BreedGoal(this, 0.9D));
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
@@ -118,7 +124,7 @@ public class StinkBugEntity extends Animal implements GeoEntity {
@Override
public boolean isFood(ItemStack stack) {
return false;
return stack.is(Items.ROTTEN_FLESH);
}
@Override
@@ -313,7 +313,10 @@ public final class AntTeleportHelper {
return new int[]{100, 200};
}
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;
}
@@ -328,7 +331,7 @@ public final class AntTeleportHelper {
}
int x = preferredPos.getX() + xOff;
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));
if (safePos != null && safePos.y >= minY && safePos.y <= maxY) {
return safePos;
@@ -1035,7 +1035,8 @@ public class KrakenEntity extends Monster implements GeoEntity, MultipartEntityO
for (int i = 0; i < strikeBolts; i++) {
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 {
this.lightningStrikeCooldown = this.isPhaseTwo() ? 30 : 60;
@@ -1,5 +1,6 @@
package com.craisinlord.antarchy.content.entity.portal;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.content.AntarchySoundEvents;
@@ -176,6 +177,13 @@ public class DimensionalTearEntity extends Entity implements GeoEntity {
DimensionalTearEntity linked = this.findLinkedTear();
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();
return;
}
@@ -264,7 +272,7 @@ public class DimensionalTearEntity extends Entity implements GeoEntity {
manticore.setTarget(target);
}
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);
}
}
@@ -348,6 +356,7 @@ public class DimensionalTearEntity extends Entity implements GeoEntity {
private void teleportEntity(Entity entity, DimensionalTearEntity destination) {
Vec3 motion = entity.getDeltaMovement();
Vec3 startPos = entity.position();
Vec3 exitPos = destination.exitPosition();
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.hasImpulse = true;
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);
}
@@ -516,6 +533,16 @@ public class DimensionalTearEntity extends Entity implements GeoEntity {
this.queenManticoreCount = tag.getInt("QueenManticoreCount");
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
@@ -1,6 +1,7 @@
package com.craisinlord.antarchy.content.entity.royal;
import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.content.AntarchySoundEvents;
import net.minecraft.sounds.SoundEvent;
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.RoyalBeamTerrainMode;
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.ComeNoCloserDecree;
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.StandTallDecree;
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.List;
import java.util.HashMap;
@@ -79,9 +80,10 @@ public class KingEntity extends RoyalBossEntity {
private int decreeCooldownTicks;
private int activeDecreeTicks;
private int stompCooldownTicks;
private int stompWindupTicks;
private int wingGustCooldownTicks;
private int wingGustWindupTicks;
private int fireballCooldownTicks = 80;
private int iceballCooldownTicks = 120;
private int iceSpikeCooldownTicks = 160;
private boolean decreeRetreatPressure;
private final Map<UUID, Long> judgmentCooldowns = new HashMap<>();
private final Map<UUID, Long> lastPlayerDamageTime = new HashMap<>();
@@ -91,9 +93,9 @@ public class KingEntity extends RoyalBossEntity {
private RoyalDecree activeDecree;
private static final List<RoyalDecree> DECREES = List.of(
new SkyIsMineDecree(), new DoNotRunDecree(), new KneelDecree(), new CloseQuartersDecree(),
new ComeNoCloserDecree(), new FightMeCowardDecree(), new NoRespiteDecree(), new NoRetreatDecree(),
new KeepYourDistanceDecree(), new StandTallDecree(), new StandYourGroundDecree(),
new HandsOffTheCrownDecree(), new AdvanceDecree(), new ShowNoMercyDecree());
new ComeNoCloserDecree(), new FightMeCowardDecree(), new NoRespiteDecree(),
new StandTallDecree(), new StandYourGroundDecree(),
new HandsOffTheCrownDecree(), new ShowNoMercyDecree());
public KingEntity(EntityType<? extends KingEntity> entityType, Level level) {
super(entityType, level);
@@ -133,6 +135,11 @@ public class KingEntity extends RoyalBossEntity {
return AntarchySoundEvents.KING_DEATH.get();
}
@Override
protected SoundEvent royalFlyLoopSound() {
return AntarchySoundEvents.KING_FLY_LOOP.get();
}
@Override
protected SoundEvent royalBiteSound() {
return AntarchySoundEvents.KING_BITE.get();
@@ -160,7 +167,7 @@ public class KingEntity extends RoyalBossEntity {
@Override
protected RoyalBeamSettings royalBeamSettings() {
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.kingBeamTerrainRadius(), 4.0F, AntarchySettings.kingBeamTerrainCap(),
1.0F, 0.08F, 15.0F, true, true);
@@ -194,13 +201,13 @@ public class KingEntity extends RoyalBossEntity {
return;
}
var particle = switch (head.slot()) {
case LEFT -> ParticleTypes.FLAME;
case LEFT -> null;
case CENTER -> ParticleTypes.ELECTRIC_SPARK;
case RIGHT -> ParticleTypes.SNOWFLAKE;
};
Vec3 direction = end.subtract(start);
double distance = direction.length();
if (distance > 0.001D) {
if (particle != null && distance > 0.001D) {
direction = direction.scale(1.0D / distance);
for (double walked = 0.0D; walked <= distance; walked += 2.5D) {
Vec3 point = start.add(direction.scale(walked));
@@ -311,7 +318,7 @@ public class KingEntity extends RoyalBossEntity {
}
if (this.patrolCooldownTicks-- <= 0) {
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 radius = 12.0D + this.random.nextDouble() * 18.0D;
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) {
if (this.stompCooldownTicks > 0) this.stompCooldownTicks--;
if (this.wingGustCooldownTicks > 0) this.wingGustCooldownTicks--;
boolean skyIsMine = this.activeDecree instanceof SkyIsMineDecree;
boolean forceFar = this.decreeRetreatPressure
|| this.activeDecree instanceof ComeNoCloserDecree
|| 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);
if (this.stompCooldownTicks <= 0 && (distance <= STOMP_RADIUS || skyIsMine)) {
this.stompCooldownTicks = skyIsMine ? STOMP_COOLDOWN_TICKS / 2 : STOMP_COOLDOWN_TICKS;
this.stompWindupTicks = STOMP_WINDUP_TICKS;
this.triggerAnim("body_action", "stomp");
this.playSound(AntarchySoundEvents.KING_ROAR.get(), 3.0F, 0.7F);
if (this.attackScheduler.ready("stomp", RoyalAttackLane.BODY)
&& (distance <= STOMP_RADIUS || skyIsMine)
&& this.beginRoyalAttack("stomp", RoyalAttackLane.BODY,
skyIsMine ? STOMP_COOLDOWN_TICKS / 2 : STOMP_COOLDOWN_TICKS,
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) {
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)) {
this.wingGustCooldownTicks = WING_GUST_COOLDOWN_TICKS;
this.wingGustWindupTicks = WING_GUST_WINDUP_TICKS;
this.triggerAnim("body_action", "wing_gust");
this.playSound(AntarchySoundEvents.KING_WING_FLAP.get(), 3.0F, 0.85F);
} else if (this.attackScheduler.ready("wing_gust", RoyalAttackLane.BODY)
&& distance <= WING_GUST_RADIUS && (forceFar || this.random.nextInt(3) == 0)
&& this.beginRoyalAttack("wing_gust", RoyalAttackLane.BODY, WING_GUST_COOLDOWN_TICKS,
WING_GUST_WINDUP_TICKS, 1, 16, new com.craisinlord.antarchy.content.entity.royal.attack.RoyalAttackScheduler.Action() {
@Override public void onStart() {
KingEntity.this.triggerAnim("body_action", "wing_gust");
KingEntity.this.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;
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) {
@@ -406,7 +512,7 @@ public class KingEntity extends RoyalBossEntity {
RoyalBlockDestruction.destroySphere(level, this,
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);
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) {
@@ -434,11 +540,15 @@ public class KingEntity extends RoyalBossEntity {
if (this.decreeCooldownTicks-- > 0) {
return;
}
if (!this.beginRoyalAttack("decree", RoyalAttackLane.DECREE,
AntarchySettings.royalDecreeCooldownTicks(), 300)) {
return;
}
this.activeDecreeTicks = 300;
this.activeDecree = this.pickDecree(target);
this.decreeRetreatPressure = false;
this.playSound(AntarchySoundEvents.KING_DECREE_CAST.get(), 3.0F, 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_DECREE_CAST.get(), 0.9F + this.random.nextFloat() * 0.15F);
this.playRoyalSound(AntarchySoundEvents.KING_ROAR.get(), 0.8F + this.random.nextFloat() * 0.12F);
if (target instanceof ServerPlayer player) {
player.connection.send(new ClientboundSetTitlesAnimationPacket(5, 300, 10));
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);
}
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) {
this.endDecree(target);
}
@@ -495,6 +610,15 @@ public class KingEntity extends RoyalBossEntity {
this.endDecree(target);
}
/** Resolves a decree violation once, using the existing bounded judgment while the new judgment system is pending. */
public void failActiveDecree(@Nullable LivingEntity target) {
if (this.activeDecree == null || target == null || !target.isAlive()) {
return;
}
this.invokeJudgment(target);
this.endDecree(target);
}
public boolean blocksHealing() {
return this.activeDecree instanceof NoRespiteDecree;
}
@@ -520,11 +644,11 @@ public class KingEntity extends RoyalBossEntity {
boolean projectile = source.getDirectEntity() instanceof Projectile;
this.bumpBehavior(projectile ? Behavior.RANGED : Behavior.HUGGING, 2);
if (this.activeDecree instanceof CloseQuartersDecree && projectile) {
this.invokeJudgment(attacker);
this.failActiveDecree(attacker);
} else if (this.activeDecree instanceof ComeNoCloserDecree && !projectile) {
this.invokeJudgment(attacker);
this.failActiveDecree(attacker);
} else if (this.activeDecree instanceof HandsOffTheCrownDecree) {
this.invokeJudgment(attacker);
this.failActiveDecree(attacker);
}
}
return super.hurt(source, amount);
@@ -30,6 +30,8 @@ import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
import com.craisinlord.antarchy.content.entity.royal.beam.RoyalBeamSettings;
import com.craisinlord.antarchy.content.entity.royal.beam.RoyalBeamTerrainMode;
import com.craisinlord.antarchy.content.entity.royal.attack.RoyalAttackLane;
import com.craisinlord.antarchy.content.entity.royal.attack.RoyalEffectController;
import com.craisinlord.antarchy.content.entity.portal.DimensionalTearEntity;
import com.craisinlord.antarchy.content.time.TimeDilationApi;
import net.minecraft.world.damagesource.DamageSource;
@@ -61,13 +63,12 @@ public class QueenEntity extends RoyalBossEntity {
private int gravityStompCooldownTicks;
private int timeFieldCooldownTicks;
private int momentumLockCooldownTicks;
private int momentumLockTicks;
private int crushingGravityCooldownTicks;
private int crushingGravityTicks;
private int accelerationCooldownTicks;
private int accelerationTicks;
private int blackHoleCooldownTicks;
private int idleWanderCooldownTicks;
private int finalTimeControlTicks;
private final RoyalEffectController royalEffects = new RoyalEffectController();
private final Map<UUID, Vec3> frozenVelocities = new HashMap<>();
public QueenEntity(EntityType<? extends QueenEntity> entityType, Level level) {
@@ -85,7 +86,12 @@ public class QueenEntity extends RoyalBossEntity {
@Override
protected boolean isFlyingBoss() {
return false;
return true;
}
@Override
protected SoundEvent royalFlyLoopSound() {
return AntarchySoundEvents.QUEEN_FLY_LOOP.get();
}
@Override
@@ -128,7 +134,7 @@ public class QueenEntity extends RoyalBossEntity {
@Override
protected RoyalBeamSettings royalBeamSettings() {
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.queenBeamTerrainRadius(), 4.0F, AntarchySettings.queenBeamTerrainCap(),
1.0F, 0.08F, 15.0F, true, true);
@@ -156,9 +162,8 @@ public class QueenEntity extends RoyalBossEntity {
}
this.tickManticoreSummon();
this.tickQueenAbilities();
this.tickRoyalAcceleration();
this.tickMomentumLock();
this.tickCrushingGravity();
this.tickFinalTimeControl();
this.royalEffects.tick();
}
private void tickIdleWander() {
@@ -174,7 +179,7 @@ public class QueenEntity extends RoyalBossEntity {
}
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() {
@@ -205,7 +210,7 @@ public class QueenEntity extends RoyalBossEntity {
: FAILED_SUMMON_RETRY_TICKS;
if (spawned > 0) {
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(),
36, 3.0D, 2.0D, 3.0D, 0.15D);
}
@@ -249,40 +254,97 @@ public class QueenEntity extends RoyalBossEntity {
}
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;
String[] candidates = phase == Phase.ONE
? new String[] {"gravity_stomp", "time_field", "black_hole"}
: new String[] {"gravity_stomp", "time_field", "black_hole", "momentum_lock", "crushing_gravity", "acceleration"};
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");
}
}
@Override public void onActive(int elapsedTicks) {
QueenEntity.this.performSelectedQueenAttack(selected, phase, cooldown, level, target);
}
})) return;
}
if (this.gravityStompCooldownTicks <= 0 && (allowOverlap || !majorBusy)) {
this.gravityStompCooldownTicks = this.cooldown(GRAVITY_STOMP_COOLDOWN);
this.performGravityStomp(level, target);
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;
TimeDilationApi.createField(level, target.position(), 12.0D, fieldRate, 140 + phase.ordinal() * 40);
}
case "black_hole" -> { this.blackHoleCooldownTicks = cooldown; this.castBlackHole(level, target); }
case "momentum_lock" -> { this.momentumLockCooldownTicks = cooldown; this.startMomentumLock(level); }
case "crushing_gravity" -> {
this.crushingGravityCooldownTicks = cooldown;
this.royalEffects.start("crushing_gravity", CRUSHING_GRAVITY_DURATION,
() -> {}, this::tickCrushingGravity, () -> {});
this.playRoyalSound(AntarchySoundEvents.QUEEN_ROAR.get(), 0.6F);
}
case "acceleration" -> { this.accelerationCooldownTicks = cooldown; this.startRoyalAcceleration(); }
}
if (this.timeFieldCooldownTicks <= 0 && (allowOverlap || !majorBusy)) {
this.timeFieldCooldownTicks = this.cooldown(TIME_FIELD_COOLDOWN);
double fieldRate = phase == Phase.THREE ? 0.1D : 0.4D;
TimeDilationApi.createField(level, target.position(), 12.0D, fieldRate, 140 + phase.ordinal() * 40);
this.triggerAnim("body_action", "wing_gust");
}
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.blackHoleCooldownTicks <= 0 && (allowOverlap || !majorBusy)) {
this.blackHoleCooldownTicks = this.cooldown(BLACK_HOLE_COOLDOWN);
this.castBlackHole(level, target);
}
if (this.momentumLockTicks <= 0 && this.momentumLockCooldownTicks <= 0 && (allowOverlap || !majorBusy)) {
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))) {
if (!this.royalEffects.active("acceleration") && this.accelerationCooldownTicks <= 0) {
this.startRoyalAcceleration();
}
if (this.finalTimeControlTicks-- <= 0) {
this.finalTimeControlTicks = AntarchySettings.queenFinalTimeFieldCooldownTicks();
LivingEntity target = this.getTarget();
if (target != null && target.isAlive()) {
TimeDilationApi.createField(level, target.position(), AntarchySettings.queenFinalTimeFieldRadius(),
AntarchySettings.queenFinalTimeFieldRate(), AntarchySettings.queenFinalTimeFieldDurationTicks());
}
}
}
private void performGravityStomp(ServerLevel level, LivingEntity target) {
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);
for (LivingEntity living : level.getEntitiesOfClass(LivingEntity.class,
this.getBoundingBox().inflate(18.0D), entity -> entity instanceof Player && entity.isAlive())) {
@@ -303,30 +365,36 @@ public class QueenEntity extends RoyalBossEntity {
RoyalBlackHoleEntity hole = RoyalBlackHoleEntity.create(level, anchor, this.getUUID());
level.addFreshEntity(hole);
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);
}
private void startMomentumLock(ServerLevel level) {
this.momentumLockCooldownTicks = MOMENTUM_LOCK_COOLDOWN;
this.momentumLockTicks = MOMENTUM_LOCK_DURATION;
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,
this.getBoundingBox().inflate(MOMENTUM_LOCK_RADIUS))) {
if (entity == this || isFieldImmune(entity) || entity instanceof Player player && player.isCreative()) {
continue;
this.royalEffects.start("momentum_lock", MOMENTUM_LOCK_DURATION, () -> {
this.frozenVelocities.clear();
for (Entity entity : level.getEntitiesOfClass(Entity.class,
this.getBoundingBox().inflate(MOMENTUM_LOCK_RADIUS))) {
if (entity == this || isFieldImmune(entity) || entity instanceof Player player && player.isCreative()) {
continue;
}
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() {
if (this.momentumLockTicks <= 0) {
return;
}
this.momentumLockTicks--;
private void tickMomentumLockEffect() {
if (!(this.level() instanceof ServerLevel level)) {
return;
}
@@ -335,29 +403,20 @@ public class QueenEntity extends RoyalBossEntity {
if (entity == null || !entity.isAlive()) {
continue;
}
if (this.momentumLockTicks <= 0) {
entity.setDeltaMovement(entry.getValue());
entity.hasImpulse = true;
} else {
entity.setDeltaMovement(Vec3.ZERO);
entity.fallDistance = 0.0F;
entity.hasImpulse = true;
if (this.tickCount % 6 == 0 && level instanceof ServerLevel serverLevel) {
serverLevel.sendParticles(ParticleTypes.END_ROD, entity.getX(), entity.getY() + entity.getBbHeight() * 0.5D, entity.getZ(),
2, 0.2D, 0.2D, 0.2D, 0.0D);
}
entity.setDeltaMovement(Vec3.ZERO);
entity.fallDistance = 0.0F;
entity.hasImpulse = true;
if (this.tickCount % 6 == 0) {
level.sendParticles(ParticleTypes.END_ROD, entity.getX(), entity.getY() + entity.getBbHeight() * 0.5D, entity.getZ(),
2, 0.2D, 0.2D, 0.2D, 0.0D);
}
}
if (this.momentumLockTicks <= 0) {
this.frozenVelocities.clear();
}
}
private void tickCrushingGravity() {
if (this.crushingGravityTicks <= 0) {
if (!this.royalEffects.active("crushing_gravity")) {
return;
}
this.crushingGravityTicks--;
if (!(this.level() instanceof ServerLevel level)) {
return;
}
@@ -378,11 +437,16 @@ public class QueenEntity extends RoyalBossEntity {
private void startRoyalAcceleration() {
this.accelerationCooldownTicks = ACCELERATION_COOLDOWN;
this.accelerationTicks = ACCELERATION_DURATION;
this.setRoyalAccelerated(true);
this.playSound(AntarchySoundEvents.QUEEN_ROAR.get(), 3.5F, 1.4F);
applyAccelModifier(this.getAttribute(Attributes.MOVEMENT_SPEED), ACCEL_SPEED_ID);
applyAccelModifier(this.getAttribute(Attributes.FLYING_SPEED), ACCEL_FLY_ID);
this.royalEffects.start("acceleration", ACCELERATION_DURATION, () -> {
this.setRoyalAccelerated(true);
this.playRoyalSound(AntarchySoundEvents.QUEEN_ROAR.get(), 1.4F);
applyAccelModifier(this.getAttribute(Attributes.MOVEMENT_SPEED), ACCEL_SPEED_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) {
@@ -398,16 +462,10 @@ public class QueenEntity extends RoyalBossEntity {
}
}
private void tickRoyalAcceleration() {
if (this.accelerationTicks <= 0) {
if (this.isRoyalAccelerated()) {
this.setRoyalAccelerated(false);
removeAccelModifier(this.getAttribute(Attributes.MOVEMENT_SPEED), ACCEL_SPEED_ID);
removeAccelModifier(this.getAttribute(Attributes.FLYING_SPEED), ACCEL_FLY_ID);
}
private void tickRoyalAccelerationEffect() {
if (!this.isRoyalAccelerated()) {
return;
}
this.accelerationTicks--;
if (this.level() instanceof ServerLevel level && this.tickCount % 2 == 0) {
level.sendParticles(ACCEL_DUST,
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(),
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
@@ -427,6 +480,8 @@ public class QueenEntity extends RoyalBossEntity {
if (!this.level().isClientSide && this.level() instanceof ServerLevel level) {
DimensionalTearEntity.discardQueenOwnedTears(level, this.getUUID());
}
this.royalEffects.clear();
this.frozenVelocities.clear();
removeAccelModifier(this.getAttribute(Attributes.MOVEMENT_SPEED), ACCEL_SPEED_ID);
removeAccelModifier(this.getAttribute(Attributes.FLYING_SPEED), ACCEL_FLY_ID);
super.die(damageSource);
@@ -468,8 +523,6 @@ public class QueenEntity extends RoyalBossEntity {
tag.putInt(SUMMON_COOLDOWN_KEY, this.manticoreSummonCooldownTicks);
tag.putInt("GravityStompCooldownTicks", this.gravityStompCooldownTicks);
tag.putInt("TimeFieldCooldownTicks", this.timeFieldCooldownTicks);
tag.putInt("MomentumLockCooldownTicks", this.momentumLockCooldownTicks);
tag.putInt("CrushingGravityCooldownTicks", this.crushingGravityCooldownTicks);
tag.putInt("AccelerationCooldownTicks", this.accelerationCooldownTicks);
tag.putInt("BlackHoleCooldownTicks", this.blackHoleCooldownTicks);
}
@@ -1,6 +1,8 @@
package com.craisinlord.antarchy.content.entity.royal;
import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.content.time.TimeDilationApi;
import com.craisinlord.antarchy.config.AntarchySettings;
import java.util.UUID;
import net.minecraft.core.particles.ParticleTypes;
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.Vec3;
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 =
SynchedEntityData.defineId(RoyalBlackHoleEntity.class, EntityDataSerializers.FLOAT);
private static final EntityDataAccessor<Boolean> COLLAPSING =
@@ -36,6 +46,7 @@ public class RoyalBlackHoleEntity extends Entity {
private UUID ownerId;
private int age;
private int activeTicks = DEFAULT_ACTIVE_TICKS;
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
public RoyalBlackHoleEntity(EntityType<? extends RoyalBlackHoleEntity> entityType, Level 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) {
RoyalBlackHoleEntity hole = new RoyalBlackHoleEntity(AntarchyObjects.ROYAL_BLACK_HOLE.get(), level);
hole.setPos(center.x, center.y, center.z);
hole.setRadius(radius);
hole.activeTicks = Math.max(20, activeTicks);
hole.setRadius(radius > 0.0D ? radius : AntarchySettings.queenBlackHoleRadius());
hole.activeTicks = activeTicks > 0 ? Math.max(20, activeTicks) : AntarchySettings.queenBlackHoleActiveTicks();
hole.ownerId = ownerId;
return hole;
}
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
@@ -84,6 +95,12 @@ public class RoyalBlackHoleEntity extends Entity {
this.age++;
if (!this.isCollapsing()) {
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) {
this.entityData.set(COLLAPSING, true);
this.age = 0;
@@ -108,7 +125,7 @@ public class RoyalBlackHoleEntity extends Entity {
if (distance < 0.5D || distance > radius) {
continue;
}
double strength = 0.16D * (1.0D - distance / radius);
double strength = AntarchySettings.queenBlackHolePullStrength() * (1.0D - distance / radius);
Vec3 pull = toCenter.scale(strength / distance);
entity.setDeltaMovement(entity.getDeltaMovement().scale(0.86D).add(pull));
entity.hasImpulse = true;
@@ -201,4 +218,18 @@ public class RoyalBlackHoleEntity extends Entity {
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.RoyalBeamTerrainMode;
import com.craisinlord.antarchy.content.entity.royal.beam.RoyalBeamElement;
import com.craisinlord.antarchy.content.entity.royal.attack.RoyalAttackLane;
import com.craisinlord.antarchy.content.entity.royal.attack.RoyalAttackScheduler;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi;
import com.craisinlord.antarchy.content.worldgen.thoraxis.ThoraxisUndersideManager;
import java.util.List;
import net.minecraft.network.chat.Component;
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 double CRUSH_MAX_RESISTANCE = 60.0D;
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_PREFERRED_HOVER = 6.0D;
@@ -116,6 +125,7 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
protected final RoyalBeamController beamController = new RoyalBeamController(this);
protected final RoyalAttackScheduler attackScheduler = new RoyalAttackScheduler();
private final ServerBossEvent bossEvent;
private final RoyalHead[] heads = {
new RoyalHead(RoyalHead.Slot.LEFT),
@@ -127,6 +137,7 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
private RoyalHead activeBeamHead;
private int beamLoopSoundTicks;
private int beamHeadCursor;
private boolean beamStarted;
@Nullable
private Entity[] multipartParts;
@@ -152,6 +163,11 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
protected abstract SoundEvent royalDeathSound();
@Nullable
protected SoundEvent royalFlyLoopSound() {
return null;
}
protected abstract SoundEvent royalBiteSound();
protected abstract SoundEvent royalBeamShootSound();
@@ -275,7 +291,11 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
@Override
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
@@ -292,8 +312,21 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
@Override
public void 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()) {
this.setNoGravity(true);
this.setNoGravity(!AntarchyGravityApi.isGravityInverted(this));
}
if (this.level().isClientSide) {
return;
@@ -303,9 +336,10 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
}
this.bossEvent.setProgress(this.getHealth() / this.getMaxHealth());
this.attackScheduler.tick();
for (RoyalHead head : this.heads) {
head.tick();
if (this.isFlyingBoss() && this.tickCount % 40 == 0 && this.royalFlyLoopSound() != null) {
this.playRoyalSound(this.royalFlyLoopSound(), 0.92F + this.random.nextFloat() * 0.12F);
}
if (this.isDeadOrDying()) {
@@ -326,16 +360,10 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
this.steerTowardTarget(primaryTarget);
this.tickBodyCrush();
for (RoyalHead head : this.heads) {
if (head.tickBiteHit()) {
this.applyBiteHit(head);
}
}
Phase phase = this.phase();
int activeHeadAttacks = 0;
for (RoyalHead head : this.heads) {
if (head.busy()) {
if (this.attackScheduler.laneBusy(this.headLane(head))) {
activeHeadAttacks++;
}
}
@@ -344,7 +372,7 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
if (activeHeadAttacks >= phase.maxConcurrentHeadAttacks()) {
break;
}
if (!head.readyToAttack()) {
if (!head.readyToAttack() || this.attackScheduler.laneBusy(this.headLane(head))) {
continue;
}
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) {
if (this.beamCooldownTicks > 0) {
if (this.beamCooldownTicks > 0 || this.attackScheduler.laneBusy(this.beamLane())) {
this.beamCooldownTicks--;
}
@@ -364,10 +398,12 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
LivingEntity beamTarget = this.activeBeamHead.target(this.level());
if (!this.activeBeamHead.shooting() || beamTarget == null || !beamTarget.isAlive()) {
this.stopRoyalBeam();
} else if (!this.beamStarted) {
return;
} else {
if (this.beamLoopSoundTicks-- <= 0) {
this.beamLoopSoundTicks = 20;
this.playSound(this.royalBeamLoopSound(), 2.5F, 1.0F);
this.playRoyalSound(this.royalBeamLoopSound(), 1.0F);
}
this.beamController.tick(
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++) {
RoyalHead head = this.heads[(this.beamHeadCursor + offset) % this.heads.length];
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)) {
continue;
}
int duration = Math.max(1, this.royalBeamSettings().durationTicks());
int cooldown = Math.max(1, Mth.floor(this.royalBeamSettings().cooldownTicks() * phase.cooldownScale()));
head.startShoot(duration, cooldown);
RoyalBeamSettings beamSettings = this.royalBeamSettings();
int duration = Math.max(1, beamSettings.durationTicks());
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);
this.activeBeamHead = head;
this.beamHeadCursor = (head.slot().partIndex()) % this.heads.length;
this.beamCooldownTicks = cooldown;
this.beamController.start(this.headAnchor(head), target.getEyePosition(), duration);
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);
this.beamStarted = false;
return;
}
}
@@ -418,11 +486,14 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
private void stopRoyalBeam() {
if (this.activeBeamHead != null) {
this.activeBeamHead.setBeamActive(false);
this.activeBeamHead.stopShoot();
}
this.activeBeamHead = null;
this.beamStarted = false;
this.attackScheduler.cancel(this.beamLane());
this.beamController.stop();
this.entityData.set(BEAM_ELEMENT, RoyalBeamElement.GENERIC.ordinal());
this.playSound(this.royalBeamEndSound(), 2.5F, 1.0F);
this.playRoyalSound(this.royalBeamEndSound(), 1.0F);
this.setFiringRoyalBeam(false);
this.setRoyalBeamEndPosition(null);
}
@@ -484,8 +555,11 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
return;
}
boolean inverted = AntarchyGravityApi.isGravityInverted(this);
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_MAX_HOVER_ABOVE_GROUND);
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() {
if (AntarchyGravityApi.isGravityInverted(this)) {
return;
}
if (this.onGround()) {
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) {
int cooldown = Math.max(10, Mth.floor(AntarchySettings.royalBossBiteCooldownTicks() * phase.cooldownScale()));
head.startBite(BITE_DURATION_TICKS, BITE_HIT_TICK, cooldown);
this.triggerAnim(head.slot().controllerName(), "bite");
this.playSound(this.royalBiteSound(), 3.0F, 0.9F + this.random.nextFloat() * 0.2F);
if (!this.attackScheduler.start("bite_" + head.slot().name(), headLane(head), cooldown,
BITE_HIT_TICK, 1, BITE_DURATION_TICKS - BITE_HIT_TICK,
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) {
@@ -578,7 +690,7 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
return AntarchySettings.royalBossBiteReach();
}
private Vec3 headAnchor(RoyalHead head) {
protected Vec3 headAnchor(RoyalHead head) {
int partIndex = head.slot().partIndex();
Entity[] parts = this.multipartParts;
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());
}
protected RoyalHead royalHead(RoyalHead.Slot slot) {
return this.heads[slot.ordinal()];
}
@Override
public void die(DamageSource damageSource) {
if (!this.level().isClientSide) {
@@ -667,7 +783,7 @@ public abstract class RoyalBossEntity extends Monster implements GeoEntity, Mult
@Override
public boolean canBeAffected(net.minecraft.world.effect.MobEffectInstance effectInstance) {
if (effectInstance.is(com.craisinlord.antarchy.content.AntarchyObjects.INVERTED_EFFECT.get())) {
return false;
return ThoraxisUndersideManager.isThoraxis(this.level());
}
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 int targetId = -1;
private int attackCooldown;
private int biteTicks;
private int biteHitTick = -1;
private int shootTicks;
private boolean shooting;
private boolean beamActive;
public RoyalHead(Slot slot) {
@@ -68,22 +65,19 @@ public final class RoyalHead {
}
public boolean busy() {
return this.biteTicks > 0 || this.shootTicks > 0 || this.beamActive;
return this.shooting || this.beamActive;
}
public boolean readyToAttack() {
return !this.busy() && this.attackCooldown <= 0;
return !this.busy();
}
public void startBite(int durationTicks, int hitTick, int cooldownTicks) {
this.biteTicks = durationTicks;
this.biteHitTick = hitTick;
this.attackCooldown = cooldownTicks;
public void startShoot() {
this.shooting = true;
}
public void startShoot(int durationTicks, int cooldownTicks) {
this.shootTicks = durationTicks;
this.attackCooldown = cooldownTicks;
public void stopShoot() {
this.shooting = false;
}
public void setBeamActive(boolean beamActive) {
@@ -95,36 +89,12 @@ public final class RoyalHead {
}
public boolean shooting() {
return this.shootTicks > 0;
}
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--;
}
return this.shooting;
}
public void reset() {
this.targetId = -1;
this.attackCooldown = 0;
this.biteTicks = 0;
this.biteHitTick = -1;
this.shootTicks = 0;
this.shooting = 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;
public final class RoyalBeamController {
private static final int INITIAL_TRACKING_DELAY_TICKS = 8;
private final Mob owner;
private Vec3 serverTarget;
private Vec3 beamEndPosition;
private int beamTicks;
private int beamAgeTicks;
private int trackingDelayTicks;
private int terrainMutationsThisTick;
private static final int ICE_MUTATIONS_PER_TICK = 3;
private static final float ICE_IMPACT_RADIUS = 2.0F;
@@ -55,12 +58,15 @@ public final class RoyalBeamController {
this.serverTarget = target;
this.beamEndPosition = target;
this.beamTicks = Math.max(1, durationTicks);
this.beamAgeTicks = 0;
this.trackingDelayTicks = INITIAL_TRACKING_DELAY_TICKS;
}
public void stop() {
this.serverTarget = null;
this.beamEndPosition = null;
this.beamTicks = 0;
this.beamAgeTicks = 0;
}
public void tick(
@@ -76,8 +82,11 @@ public final class RoyalBeamController {
}
this.beamTicks--;
this.beamAgeTicks++;
this.terrainMutationsThisTick = 0;
if (target != null && target.isAlive()) {
if (this.trackingDelayTicks > 0) {
this.trackingDelayTicks--;
} else if (target != null && target.isAlive()) {
Vec3 targetEye = target.getEyePosition();
this.serverTarget = this.serverTarget == null
? targetEye
@@ -93,7 +102,10 @@ public final class RoyalBeamController {
if (direction.lengthSqr() < 1.0E-7D) {
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(
shootFrom,
clipEnd,
@@ -5,6 +5,7 @@ public record RoyalBeamSettings(
double targetTracking,
double pathStep,
int durationTicks,
int travelTicks,
int cooldownTicks,
float pathDamageRadius,
float impactDamageRadius,
@@ -27,6 +28,7 @@ public record RoyalBeamSettings(
0.1D,
7.5D,
30,
12,
90,
6.0F,
6.0F,
@@ -50,6 +52,7 @@ public record RoyalBeamSettings(
targetTracking = Math.clamp(targetTracking, 0.0D, 1.0D);
pathStep = Math.max(1.0D, pathStep);
durationTicks = Math.max(1, durationTicks);
travelTicks = Math.max(1, travelTicks);
cooldownTicks = Math.max(1, cooldownTicks);
pathDamageRadius = Math.max(0.0F, pathDamageRadius);
impactDamageRadius = Math.max(0.0F, impactDamageRadius);
@@ -5,11 +5,9 @@ import net.minecraft.world.entity.LivingEntity;
public final class AdvanceDecree implements RoyalDecree {
private static final double REQUIRED_RANGE = 14.0D;
public String translationKey() { return "decree.antarchy.advance"; }
public void apply(ServerLevel level, KingEntity king, LivingEntity target) {
king.setDecreeRetreatPressure(true);
if (target.distanceTo(king) > REQUIRED_RANGE) {
king.invokeJudgment(target);
}
public void apply(ServerLevel level, KingEntity king, LivingEntity target) { king.setDecreeRetreatPressure(true); }
@Override public Evaluation evaluate(ServerLevel level, KingEntity king, LivingEntity target) {
return target.distanceTo(king) > REQUIRED_RANGE ? Evaluation.VIOLATED : Evaluation.COMPLIANT;
}
@Override
public int contextWeight(KingEntity king, LivingEntity target) {
@@ -5,6 +5,9 @@ import net.minecraft.world.entity.LivingEntity;
public final class ComeNoCloserDecree implements RoyalDecree {
public String translationKey() { return "decree.antarchy.come_no_closer"; }
public void apply(ServerLevel level, KingEntity king, LivingEntity target) { }
@Override public Evaluation evaluate(ServerLevel level, KingEntity king, LivingEntity target) {
return target.distanceTo(king) <= 16.0D ? Evaluation.VIOLATED : Evaluation.COMPLIANT;
}
@Override
public int contextWeight(KingEntity king, LivingEntity target) {
return 4 + king.behaviorScore(KingEntity.Behavior.HUGGING);
@@ -4,7 +4,10 @@ import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.LivingEntity;
public final class DoNotRunDecree implements RoyalDecree {
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
public int contextWeight(KingEntity king, LivingEntity target) {
return 4 + king.behaviorScore(KingEntity.Behavior.SPRINTING);
@@ -13,4 +13,8 @@ public final class FightMeCowardDecree implements RoyalDecree {
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 {
private static final double INNER_RADIUS = 16.0D;
public String translationKey() { return "decree.antarchy.keep_your_distance"; }
public void apply(ServerLevel level, KingEntity king, LivingEntity target) {
if (target.distanceTo(king) <= INNER_RADIUS) {
king.invokeJudgment(target);
}
public void apply(ServerLevel level, KingEntity king, LivingEntity target) { }
@Override public Evaluation evaluate(ServerLevel level, KingEntity king, LivingEntity target) {
return target.distanceTo(king) <= INNER_RADIUS ? Evaluation.VIOLATED : Evaluation.COMPLIANT;
}
@Override
public int contextWeight(KingEntity king, LivingEntity target) {
@@ -12,7 +12,7 @@ public final class KneelDecree implements RoyalDecree {
UUID id = target.getUUID();
if (target.isCrouching()) { deadlines.remove(id); king.clearActiveDecree(target); return; }
int deadline = deadlines.computeIfAbsent(id, ignored -> target.tickCount + 100);
if (target.tickCount >= deadline) { king.invokeJudgment(target); deadlines.put(id, target.tickCount + 100); }
if (target.tickCount >= deadline) { king.failActiveDecree(target); }
}
@Override
@@ -5,9 +5,19 @@ import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.LivingEntity;
public interface RoyalDecree {
enum Evaluation {
COMPLIANT,
VIOLATED,
COMPLETE
}
String translationKey();
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) {
return -1;
}
@@ -6,8 +6,9 @@ public final class ShowNoMercyDecree implements RoyalDecree {
private static final long IDLE_DAMAGE_LIMIT_TICKS = 80L;
public String translationKey() { return "decree.antarchy.show_no_mercy"; }
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;
public final class SkyIsMineDecree implements RoyalDecree {
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
public int contextWeight(KingEntity king, LivingEntity target) {
return 4 + king.behaviorScore(KingEntity.Behavior.AIRBORNE);
@@ -5,8 +5,8 @@ import net.minecraft.world.entity.LivingEntity;
public final class StandTallDecree implements RoyalDecree {
public String translationKey() { return "decree.antarchy.stand_tall"; }
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 void apply(ServerLevel level, KingEntity king, LivingEntity target) {
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
public void onEnded() {
@@ -8,6 +8,7 @@ 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.network.protocol.game.ClientboundSetEntityMotionPacket;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
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 TRAVELLING_KEY = "Travelling";
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 DRIFT_FRICTION = 0.995D;
@@ -82,15 +85,20 @@ public class WindVortexEntity extends Entity {
}
private int age;
private int durationTicks = 140;
private int durationTicks = 240;
private double pullStrength = 0.32D;
private double launchStrength = 1.0D;
private float damageOverride = -1.0F;
private Vec3 travelVelocity = Vec3.ZERO;
private boolean travelling = false;
private boolean homing = false;
private boolean bounceOnImpact = false;
private int travelTicksRemaining;
private int fadeOutTicksRemaining;
private boolean anchoredAtBlock;
private double flowTurnRate;
private double flowTurnTarget;
private int flowTurnChangeTicks;
private final Map<UUID, Double> carriedProgress = new HashMap<>();
@Nullable
private UUID ownerUuid;
@@ -148,6 +156,18 @@ public class WindVortexEntity extends Entity {
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;
if (!lensVortex && (this.isInLava() || this.isInsideSolidBlock())) {
this.fadeOut();
@@ -164,20 +184,27 @@ public class WindVortexEntity extends Entity {
if (this.homing) {
this.steerTowardPrey();
}
if (this.bounceOnImpact) {
this.updateFluidDrift();
}
Vec3 from = this.position();
Vec3 to = from.add(this.travelVelocity);
HitResult blockHit = this.level().clip(
new ClipContext(from, to, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, this));
if (blockHit.getType() != HitResult.Type.MISS) {
this.fadeOut();
return;
}
this.setDeltaMovement(this.travelVelocity);
this.move(net.minecraft.world.entity.MoverType.SELF, this.travelVelocity);
if (--this.travelTicksRemaining <= 0) {
this.travelling = false;
this.noPhysics = false;
if (this.bounceOnImpact) {
this.flowAroundBlock(blockHit);
} else {
this.anchorAtBlock(blockHit);
}
} else {
this.setDeltaMovement(this.travelVelocity);
this.move(net.minecraft.world.entity.MoverType.SELF, this.travelVelocity);
if (--this.travelTicksRemaining <= 0) {
this.travelling = false;
this.noPhysics = false;
this.setDeltaMovement(this.travelVelocity);
}
}
} else if (!lensVortex) {
Vec3 current = this.getDeltaMovement();
@@ -192,11 +219,15 @@ public class WindVortexEntity extends Entity {
HitResult blockHit = this.level().clip(
new ClipContext(from, to, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, this));
if (blockHit.getType() != HitResult.Type.MISS) {
this.fadeOut();
return;
if (this.bounceOnImpact) {
this.flowAroundBlock(blockHit);
} else {
this.anchorAtBlock(blockHit);
}
} else {
this.setDeltaMovement(drift);
this.move(net.minecraft.world.entity.MoverType.SELF, drift);
}
this.setDeltaMovement(drift);
this.move(net.minecraft.world.entity.MoverType.SELF, drift);
} else {
Vec3 drift = this.getDeltaMovement().multiply(DRIFT_FRICTION, DRIFT_FRICTION, DRIFT_FRICTION);
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
public void handleEntityEvent(byte id) {
if (id == 60) {
@@ -431,18 +529,13 @@ public class WindVortexEntity extends Entity {
updatedMovement = new Vec3(updatedMovement.x, Math.max(updatedMovement.y, minRise), updatedMovement.z);
entity.setOnGround(false);
}
entity.setDeltaMovement(updatedMovement);
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.setDeltaMovement(updatedMovement);
entity.fallDistance = 0.0F;
entity.hasImpulse = true;
this.syncPlayerMotion(entity);
}
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);
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;
this.syncPlayerMotion(entity);
}
private void hurtCaughtEntity(Entity entity) {
@@ -487,12 +581,20 @@ public class WindVortexEntity extends Entity {
entity.fallDistance = 0.0F;
entity.hasImpulse = true;
entity.hurtMarked = true;
this.syncPlayerMotion(entity);
if (this.isDamaging() && entity instanceof LivingEntity living
&& !living.getType().is(AntarchyTags.Entities.WIND_VORTEX_IMMUNE)) {
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() {
Entity owner = this.getOwnerEntity();
if (owner instanceof LivingEntity living) {
@@ -582,6 +684,10 @@ public class WindVortexEntity extends Entity {
this.homing = homing;
}
public void setBounceOnImpact(boolean bounceOnImpact) {
this.bounceOnImpact = bounceOnImpact;
}
public void setVortexStrengths(double pullStrength, double launchStrength) {
this.pullStrength = Math.max(0.0D, pullStrength);
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.travelling = tag.getBoolean(TRAVELLING_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));
if (this.travelling) {
this.noPhysics = true;
@@ -654,6 +762,8 @@ public class WindVortexEntity extends Entity {
tag.putFloat(AXIS_Z_KEY, (float) this.getAxis().z);
tag.putBoolean(TRAVELLING_KEY, this.travelling);
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_Y_KEY, this.travelVelocity.y);
tag.putDouble(TRAVEL_Z_KEY, this.travelVelocity.z);
@@ -1,6 +1,8 @@
package com.craisinlord.antarchy.content.gravity;
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.level.PathNavigationRegion;
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;
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[][] INVERTED_CARDINAL_NEIGHBOR_OFFSETS = {
{1, 0, 0},
@@ -26,10 +33,23 @@ public class GravityWalkNodeEvaluator extends WalkNodeEvaluator {
@Override
public void prepare(PathNavigationRegion sourceIn, Mob mob) {
super.prepare(sourceIn, mob);
this.antarchy$prepareStartNanos = System.nanoTime();
this.antarchy$nodeProbeCount = 0;
}
@Override
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();
}
@@ -43,10 +63,9 @@ public class GravityWalkNodeEvaluator extends WalkNodeEvaluator {
int z = Mth.floor(this.mob.getZ());
int y = Mth.floor(this.mob.getY() + this.mob.getBbHeight()) - 1;
Node node = this.antarchy$findStartNode(x, y, z);
if (node == null) {
return super.getStart();
}
// A floor-oriented start is a different coordinate model and must not
// be mixed into an inverted search. PathFinder treats null as a clean
// no-path result.
return node;
}
@@ -66,7 +85,8 @@ public class GravityWalkNodeEvaluator extends WalkNodeEvaluator {
}
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;
}
@@ -82,9 +102,16 @@ public class GravityWalkNodeEvaluator extends WalkNodeEvaluator {
return node;
}
private boolean antarchy$hasCeilingSupport(int x, int y, int z) {
PathType ceilingType = this.getPathType(this.currentContext, x, y + 1, z);
return this.mob.getPathfindingMalus(ceilingType) < 0.0F;
private boolean antarchy$hasCeilingSupport(PathfindingContext context, int x, int y, int z) {
for (int dx = 0; dx < this.entityWidth; dx++) {
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
@@ -93,8 +120,7 @@ public class GravityWalkNodeEvaluator extends WalkNodeEvaluator {
return super.getPathTypeOfMob(context, x, y, z, mob);
}
PathType ceilingType = this.getPathType(context, x, y + 1, z);
if (mob.getPathfindingMalus(ceilingType) >= 0.0F) {
if (!this.antarchy$hasCeilingSupport(context, x, y, z)) {
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;
}
@@ -159,12 +197,16 @@ public class GravityWalkNodeEvaluator extends WalkNodeEvaluator {
}
private Node antarchy$getNeighbor(int x, int y, int z) {
this.antarchy$nodeProbeCount++;
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;
}
Node node = this.getNode(x, y, z);
if (node.closed) {
return null;
}
node.type = type;
node.costMalus = this.mob.getPathfindingMalus(type);
return node;
@@ -137,6 +137,7 @@ public class EyeOfTheStormItem extends Item implements GeoItem {
vortex.setDamageOverride(AntarchySettings.eyeOfTheStormSurgeDamage());
vortex.setTravel(dir.scale(SURGE_PROJECTILE_SPEED));
vortex.setHoming(true);
vortex.setBounceOnImpact(true);
level.addFreshEntity(vortex);
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) {
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);
}
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.moveTo(x, y, z);
lightningBolt.setVisualOnly(true);
level.addFreshEntity(lightningBolt);
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 {
private static final ResourceLocation REPAIR_MATERIAL_ID = ResourceLocation.fromNamespaceAndPath("antarchy", "manticore_wing");
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<>();
public ManticoreWingsItem(Properties properties) {
@@ -41,10 +43,16 @@ public final class ManticoreWingsItem extends ElytraItem {
return;
}
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 ->
living != player && living.isAlive() && !player.isAlliedTo(living) && !living.isAlliedTo(player))) {
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);
}
}
@@ -1,5 +1,6 @@
package com.craisinlord.antarchy.content.item;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.content.item.royal.RoyalGearHelper;
@@ -142,8 +143,18 @@ public class RoyalAssailantArmorItem extends ArmorItem {
}
MobEffectInstance existing = player.getEffect(AntarchyObjects.INVERTED_EFFECT.get());
if (existing != null && existing.isInfiniteDuration()) {
Antarchy.LOGGER.info(
"[antarchy-gravity] royal chestplate toggling inversion off player={} uuid={} pos=({}, {}, {}) dim={} existingDuration={} gameTime={}",
player.getGameProfile().getName(), player.getUUID(), player.getX(), player.getY(), player.getZ(),
player.level().dimension().location(), existing.getDuration(), player.level().getGameTime()
);
player.removeEffect(AntarchyObjects.INVERTED_EFFECT.get());
} else {
Antarchy.LOGGER.info(
"[antarchy-gravity] royal chestplate toggling inversion on player={} uuid={} pos=({}, {}, {}) dim={} existingDuration={} gameTime={}",
player.getGameProfile().getName(), player.getUUID(), player.getX(), player.getY(), player.getZ(),
player.level().dimension().location(), existing != null ? existing.getDuration() : -1, player.level().getGameTime()
);
player.addEffect(new MobEffectInstance(AntarchyObjects.INVERTED_EFFECT.get(), -1, 0, false, false, true));
}
}
@@ -65,6 +65,18 @@ public final class TimeDilationApi {
SYNCED_CLIENT_RATES.put(entityUuid, clampedRate);
}
/** Clears both the authoritative entity state and any client-side UUID cache. */
public static void clearRate(Entity entity) {
if (entity instanceof TimeDilationEntityAccess access) {
boolean wasDilated = 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() {
SYNCED_CLIENT_RATES.clear();
}
@@ -1,19 +1,16 @@
package com.craisinlord.antarchy.content.time;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
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 {
private static final Set<ResourceKey<Level>> DILATED_LEVELS = new HashSet<>();
private TimeDilationManager() {
}
@@ -21,15 +18,10 @@ public final class TimeDilationManager {
for (ServerLevel level : server.getAllLevels()) {
List<TimeDilationFieldEntity> fields = collectFields(level);
syncFieldSnapshots(level, fields);
if (fields.isEmpty()) {
if (DILATED_LEVELS.remove(level.dimension())) {
resetRates(level);
}
continue;
}
DILATED_LEVELS.add(level.dimension());
updateEntities(level, fields);
TimeDilationParticles.spawnFieldBorders(level, fields);
if (!fields.isEmpty()) {
TimeDilationParticles.spawnFieldBorders(level, fields);
}
}
}
@@ -60,32 +52,30 @@ public final class TimeDilationManager {
if (!(entity instanceof TimeDilationEntityAccess access) || entity instanceof TimeDilationFieldEntity) {
continue;
}
if (entity.getType().is(com.craisinlord.antarchy.content.AntarchyTags.Entities.TIME_DILATION_IMMUNE)) {
if (access.antarchy$getTimeDilationRate() < TimeDilationMath.NORMAL_RATE) {
access.antarchy$setTimeDilationRate(TimeDilationMath.NORMAL_RATE);
TimeDilationApi.syncEntityRate(entity, TimeDilationMath.NORMAL_RATE);
if (entity.getType().is(com.craisinlord.antarchy.content.AntarchyTags.Entities.TIME_DILATION_IMMUNE)
|| (entity instanceof net.minecraft.world.entity.LivingEntity living && !RoyalEffectEligibility.canApplyDilated(living))) {
if (RoyalEffectHooks.dilatedHolder() != null && entity instanceof net.minecraft.world.entity.LivingEntity living) {
living.removeEffect(RoyalEffectHooks.dilatedHolder());
}
TimeDilationApi.clearRate(entity);
continue;
}
double previousRate = access.antarchy$getTimeDilationRate();
double rate = TimeDilationFieldSampler.sample(fields, entity.position());
access.antarchy$setTimeDilationRate(rate);
if (Math.abs(previousRate - rate) > 0.001D) {
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);
}
if (rate < TimeDilationMath.NORMAL_RATE && Math.abs(previousRate - rate) > 0.001D) {
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.FeaturePlaceContext;
import net.minecraft.world.level.levelgen.feature.configurations.NoneFeatureConfiguration;
import java.util.HashMap;
import java.util.Map;
public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
private static final String SIZEABLE_FOLIAGE_MODID = "sizeable_foliage";
@@ -38,6 +40,8 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
WorldGenLevel level = context.level();
RandomSource random = context.random();
BlockPos origin = context.origin();
Map<Long, Integer> surfaceHeightCache = new HashMap<>();
Map<Long, Integer> oceanFloorHeightCache = new HashMap<>();
int attempts = switch (this.variant) {
case FOREST -> 185;
case MEADOW -> 95;
@@ -68,7 +72,7 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
case FLOWER_FOREST_MILKWEED -> 0.0F;
};
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 FLOWER_FOREST_MILKWEED -> 0.0F;
}) {
placedAny |= this.placePitcherPatch(level, origin, random);
placedAny |= this.placePitcherPatch(level, origin, random, surfaceHeightCache);
}
if (random.nextFloat() < switch (this.variant) {
case MEADOW -> 0.16F;
@@ -88,7 +92,7 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
case FOREST -> 0.06F;
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) {
case MEADOW -> 0.16F;
@@ -97,20 +101,20 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
case FOREST -> 0.06F;
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) {
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) {
placedAny |= this.placeBigBushes(level, origin, random, radius);
placedAny |= this.placeBigBushes(level, origin, random, radius, surfaceHeightCache);
}
for (int i = 0; i < attempts; i++) {
int x = origin.getX() + 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 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;
}
@@ -227,7 +231,7 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
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"));
if (!(bushBlock instanceof BigBushBlock)) {
return false;
@@ -241,7 +245,7 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
for (int i = 0; i < bushAttempts; i++) {
int x = origin.getX() + 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);
if (!isValidPlantSpot(level, plantPos, plantPos.below())) {
@@ -333,8 +337,8 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
return true;
}
private boolean placeTorchflowerPatch(WorldGenLevel level, BlockPos origin, RandomSource random) {
BlockPos patchCenter = sampleSurface(level, origin, random, this.variant == Variant.FOREST ? 8 : 12);
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, surfaceHeightCache);
if (patchCenter == null) {
return false;
}
@@ -346,7 +350,7 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
boolean bushEligible = this.variant == Variant.FOREST || this.variant == Variant.MEADOW;
boolean placed = false;
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) {
continue;
}
@@ -369,8 +373,8 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
return placed;
}
private boolean placePitcherPatch(WorldGenLevel level, BlockPos origin, RandomSource random) {
BlockPos patchCenter = sampleSurface(level, origin, random, this.variant == Variant.FOREST ? 8 : 13);
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, surfaceHeightCache);
if (patchCenter == null) {
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);
boolean placed = false;
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) {
continue;
}
@@ -396,8 +400,8 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
return placed;
}
private boolean placeMilkweedPatch(WorldGenLevel level, BlockPos origin, RandomSource random, Block flower) {
BlockPos patchCenter = sampleSurface(level, origin, random, this.variant == Variant.FOREST ? 8 : 13);
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, surfaceHeightCache);
if (patchCenter == null) {
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);
boolean placed = false;
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) {
continue;
}
@@ -423,7 +427,7 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
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) {
case FOREST -> 3;
case MEADOW -> 5;
@@ -436,7 +440,7 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
for (int i = 0; i < attempts; i++) {
int x = origin.getX() + 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 floorPos = seagrassPos.below();
@@ -455,11 +459,11 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
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++) {
int x = origin.getX() + 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);
if (isValidPlantSpot(level, plantPos, plantPos.below())) {
return plantPos;
@@ -468,14 +472,25 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
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 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);
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) {
if (variant != Variant.PEACH_FOREST) {
return;
@@ -7,7 +7,10 @@ import com.craisinlord.antarchy.content.block.OuranwoodLeavesBlock;
import com.craisinlord.antarchy.content.entity.flying_squirrel.FlyingSquirrelEntity;
import com.mojang.serialization.Codec;
import java.util.ArrayList;
import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
@@ -1113,43 +1116,87 @@ public class OuranwoodTreeFeature extends Feature<OuranwoodTreeConfiguration> {
int minY = Math.max(level.getMinBuildHeight(), origin.getY() - 2);
int maxY = Math.min(level.getMaxBuildHeight() - 1, origin.getY() + height + canopyDepth + 8);
List<BlockPos> leafPositions = new ArrayList<>();
Map<BlockPos, Integer> distances = new HashMap<>();
ArrayDeque<BlockPos> queue = new ArrayDeque<>();
BlockPos.MutableBlockPos cursor = new BlockPos.MutableBlockPos();
for (int x = origin.getX() - horizontalRadius; x <= origin.getX() + horizontalRadius; x++) {
for (int z = origin.getZ() - horizontalRadius; z <= origin.getZ() + horizontalRadius; z++) {
for (int y = minY; y <= maxY; y++) {
cursor.set(x, y, z);
if (level.getBlockState(cursor).getBlock() instanceof LeavesBlock) {
leafPositions.add(cursor.immutable());
BlockState state = level.getBlockState(cursor);
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) {
BlockState state = level.getBlockState(pos);
IntegerProperty distanceProperty = this.getLeafDistanceProperty(state);
if (!(state.getBlock() instanceof LeavesBlock) || distanceProperty == null) {
continue;
for (BlockPos pos : leafPositions) {
BlockState state = level.getBlockState(pos);
int limit = this.getLeafDistanceLimit(state);
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;
}
int computedDistance = this.computeLeafDistance(level, pos, state);
if (computedDistance == state.getValue(distanceProperty)) {
continue;
if (!distances.containsKey(neighborPos)) {
IntegerProperty neighborProperty = this.getLeafDistanceProperty(neighbor);
if (neighborProperty != null) {
bestDistance = Math.min(bestDistance, neighbor.getValue(neighborProperty) + 1);
}
}
BlockState updatedState;
if (state.getBlock() instanceof OuranwoodLeavesBlock) {
updatedState = OuranwoodLeavesBlock.setOuranwoodDistanceForWorldgen(state, computedDistance);
} else {
updatedState = state.setValue(distanceProperty, computedDistance);
}
setBlock(level, pos, updatedState);
anyChanged = true;
}
if (!anyChanged) break;
if (bestDistance < distances.get(pos)) {
distances.put(pos, bestDistance);
queue.add(pos);
}
}
while (!queue.isEmpty()) {
BlockPos pos = queue.removeFirst();
int distance = distances.get(pos);
for (Direction direction : Direction.values()) {
BlockPos neighborPos = pos.relative(direction);
Integer neighborDistance = distances.get(neighborPos);
if (neighborDistance == null) {
continue;
}
BlockState neighborState = level.getBlockState(neighborPos);
int candidate = Math.min(this.getLeafDistanceLimit(neighborState), distance + 1);
if (candidate < neighborDistance) {
distances.put(neighborPos, candidate);
queue.addLast(neighborPos);
}
}
}
for (BlockPos pos : leafPositions) {
BlockState state = level.getBlockState(pos);
IntegerProperty distanceProperty = this.getLeafDistanceProperty(state);
if (distanceProperty == null) {
continue;
}
int computedDistance = distances.get(pos);
if (computedDistance == state.getValue(distanceProperty)) {
continue;
}
BlockState updatedState;
if (state.getBlock() instanceof OuranwoodLeavesBlock) {
updatedState = OuranwoodLeavesBlock.setOuranwoodDistanceForWorldgen(state, computedDistance);
} else {
updatedState = state.setValue(distanceProperty, computedDistance);
}
setBlock(level, pos, updatedState);
}
}
@@ -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 SIMULATION_DISTANCE_MARGIN_CHUNKS = 1;
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 EXIT_UNDERSIDE_Y = GRAVITY_FLIP_Y + 4;
private static final int FLIP_COOLDOWN_TICKS = 40;
@@ -55,12 +57,20 @@ public final class ThoraxisUndersideManager {
return;
}
long startNanos = System.nanoTime();
long now = level.getGameTime();
int discoveredLiving = 0;
int discoveredItems = 0;
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 refreshedLiving = 0;
int flippedItems = 0;
int restoredItems = 0;
for (Entity entity : tracking.entities) {
if (!entity.isAlive() || entity.isSpectator()) {
continue;
@@ -71,6 +81,7 @@ public final class ThoraxisUndersideManager {
int threshold = hasEffect ? EXIT_UNDERSIDE_Y : ENTER_UNDERSIDE_Y;
if (living.getY() < threshold) {
refreshInvertedEffect(living);
refreshedLiving++;
}
continue;
}
@@ -94,6 +105,7 @@ public final class ThoraxisUndersideManager {
flipsRemaining--;
tracking.lastFlipTick.put(uuid, now);
AntarchyGravityApi.setForcedGravityDirection(entity, AntarchyGravityDirection.UP, TRANSITION);
flippedItems++;
}
continue;
}
@@ -108,6 +120,7 @@ public final class ThoraxisUndersideManager {
if (isForcedUp) {
tracking.lastFlipTick.put(uuid, now);
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.forcedItems.removeIf(entity -> !entity.isAlive() || entity.isRemoved());
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 final Set<Entity> entities = Collections.newSetFromMap(new IdentityHashMap<>());
private final Set<Entity> forcedItems = Collections.newSetFromMap(new IdentityHashMap<>());
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 discoveredLiving = 0;
int discoveredItems = 0;
for (ServerPlayer player : level.players()) {
AABB area = new AABB(
player.getX() - radius, level.getMinBuildHeight(), player.getZ() - radius,
player.getX() + radius, level.getMaxBuildHeight(), player.getZ() + radius
);
entities.add(player);
entities.addAll(level.getEntitiesOfClass(LivingEntity.class, area,
entity -> !entity.isSpectator() && entity.getY() < EXIT_UNDERSIDE_Y));
entities.addAll(level.getEntitiesOfClass(ItemEntity.class, area,
entity -> !entity.isSpectator() && entity.getY() < EXIT_UNDERSIDE_Y));
java.util.List<LivingEntity> livingEntities = level.getEntitiesOfClass(LivingEntity.class, area,
entity -> !entity.isSpectator() && entity.getY() < EXIT_UNDERSIDE_Y);
java.util.List<ItemEntity> itemEntities = level.getEntitiesOfClass(ItemEntity.class, area,
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() {
@@ -149,6 +180,9 @@ public final class ThoraxisUndersideManager {
return last == null || now - last >= FLIP_COOLDOWN_TICKS;
}
private record DiscoveryStats(int livingEntities, int itemEntities) {
}
public static void applyUndersideInversion(LivingEntity living) {
refreshInvertedEffect(living);
}
@@ -184,4 +218,8 @@ public final class ThoraxisUndersideManager {
public static boolean isThoraxis(Level level) {
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(
BlockStateProvider trunkProvider,
IntProvider height
IntProvider height,
int tuftRadius,
int tuftHeight
) implements FeatureConfiguration {
public static final Codec<TruffaloTreeConfiguration> CODEC = RecordCodecBuilder.create(instance -> instance.group(
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));
}
@@ -58,9 +58,9 @@ public class TruffaloTreeFeature extends Feature<TruffaloTreeConfiguration> {
return false;
}
}
for (int dy = 0; dy <= 2; dy++) {
for (int dx = -1; dx <= 1; dx++) {
for (int dz = -1; dz <= 1; dz++) {
for (int dy = 0; dy < config.tuftHeight(); dy++) {
for (int dx = -config.tuftRadius(); dx <= config.tuftRadius(); dx++) {
for (int dz = -config.tuftRadius(); dz <= config.tuftRadius(); dz++) {
if (!canReplace(level, capBase.offset(dx, dy, dz))) {
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"));
BlockState tuftState = tuft.defaultBlockState();
for (int dy = 0; dy <= 2; dy++) {
for (int dx = -1; dx <= 1; dx++) {
for (int dz = -1; dz <= 1; dz++) {
for (int dy = 0; dy < config.tuftHeight(); dy++) {
for (int dx = -config.tuftRadius(); dx <= config.tuftRadius(); dx++) {
for (int dz = -config.tuftRadius(); dz <= config.tuftRadius(); dz++) {
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.AntarchyGravityRotationUtil;
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.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.entity.Entity;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.phys.AABB;
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$gravityTransitionDuration = Math.max(0, tag.getInt(ANTARCHY_GRAVITY_TRANSITION_DURATION_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"))
@@ -134,6 +185,11 @@ public abstract class EntityGravityMixin implements AntarchyGravityAccess {
}
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) {
AntarchyGravityApi.notifyGravityStateChanged(this.antarchy$entity());
}
@@ -172,6 +228,29 @@ public abstract class EntityGravityMixin implements AntarchyGravityAccess {
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
private void antarchy$adjustPositionForGravityChange(AntarchyGravityDirection oldDirection, AntarchyGravityDirection newDirection) {
if (oldDirection == newDirection) {
@@ -1,26 +1,89 @@
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.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.level.pathfinder.NodeEvaluator;
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.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(GroundPathNavigation.class)
/*
* Makes ground pathing read gravity-aware floor checks.
*/
public abstract class GroundPathNavigationMixin {
@Inject(method = "createPathFinder", at = @At("HEAD"), cancellable = true)
private void antarchy$useGravityEvaluator(int maxVisitedNodes, CallbackInfoReturnable<PathFinder> cir) {
var accessor = (PathNavigationAccessor) (Object) this;
var mob = accessor.antarchy$getMob();
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);
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",
"GrindstoneMenuMixin",
"LivingEntityJumpingAccessor",
"CommandedMobMixin",
"LivingEntityRoyalEffectsMixin",
"MobCommandedTargetMixin",
"RoyalNoRespiteMixin",
"MantisClawWallClimbMixin",
"MilkBucketSizeResetMixin",
@@ -64,12 +67,15 @@
"gravity.FireworkRocketGravityMixin",
"gravity.GroundPathNavigationMixin",
"gravity.LivingEntityMixin",
"gravity.MeleeAttackGoalGravityMixin",
"gravity.MoveControlGravityMixin",
"gravity.MobHurtMixin",
"gravity.PathNavigationAccessor",
"gravity.PlayerAttackMixin",
"gravity.PlayerGravityMixin",
"gravity.PlayerPoseFitMixin",
"gravity.PlayerSneakMixin",
"gravity.RandomStrollGoalGravityMixin",
"gravity.ProjectileGravityMixin",
"gravity.ServerChunkCacheAccessor",
"gravity.ServerEntityAccessor",
@@ -85,7 +91,8 @@
"time.LivingEntityKnockbackTimeDilationMixin",
"time.CreeperTimeDilationMixin",
"time.EndCrystalTimeDilationMixin",
"time.LivingEntityTimeDilationMixin",
"time.LivingEntityTimeDilationMixin",
"time.LivingEntityTimeDilationCleanupMixin",
"time.LivingEntityAttackCooldownTimeDilationMixin",
"time.LookControlTimeDilationMixin",
"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": {
"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": {
"model": "antarchy:block/typhonite_spike_up_base"
},
"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": {
"model": "antarchy:block/typhonite_spike_up_frustum"
},
"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": {
"model": "antarchy:block/typhonite_spike_up_middle"
},
"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": {
"model": "antarchy:block/typhonite_spike_up_tip"
},
"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": {
"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