king and queen progress. vortex lens fixes. time dilation fields for queen
This commit is contained in:
1 parent
357f41d9d6
commit
c573c574b9
181 files changed
+8555
-198
No files matched your search
@@ -231,6 +231,19 @@ public final class AntarchySettings {
|
||||
private static int royalGuardianShieldDurability = 1008;
|
||||
private static double royalBoltDamage = 6.0D;
|
||||
private static int royalEggHatchChance = 3;
|
||||
private static double kingHealth = 6000.0D;
|
||||
private static double queenHealth = 6000.0D;
|
||||
private static double kingAttackDamage = 50.0D;
|
||||
private static double queenAttackDamage = 45.0D;
|
||||
private static double royalBossArmor = 20.0D;
|
||||
private static double royalBossFollowRange = 128.0D;
|
||||
private static double royalBossMovementSpeed = 0.22D;
|
||||
private static double royalBossKnockbackResistance = 1.0D;
|
||||
private static double royalBossStepHeight = 3.0D;
|
||||
private static double royalBossMaxSingleHitDamage = 250.0D;
|
||||
private static double royalBossBiteReach = 10.0D;
|
||||
private static double royalBossBiteDamageMultiplier = 1.6D;
|
||||
private static int royalBossBiteCooldownTicks = 45;
|
||||
private static boolean basiliskPetrifyingGazeEnabled = true;
|
||||
private static int basiliskSpawnMaxLightLevel = 5;
|
||||
private static double basiliskHealth = 320.0D;
|
||||
@@ -261,7 +274,7 @@ public final class AntarchySettings {
|
||||
private static double diamondMinecartCoastDeceleration = 0.02D;
|
||||
private static boolean diamondMinecartMobDamageEnabled = false;
|
||||
private static double diamondMinecartMaxMobDamage = 4.0D;
|
||||
private static double hoverboardMaxSpeed = 3.0D;
|
||||
private static double hoverboardMaxSpeed = 6.0D;
|
||||
private static double hoverboardAcceleration = 0.05D;
|
||||
private static boolean dreadHallucinationSoundsEnabled = true;
|
||||
private static double dreadHallucinationSoundMinInterval = 3.0D;
|
||||
@@ -386,18 +399,18 @@ public final class AntarchySettings {
|
||||
private static int eyeOfTheStormSurgeCooldownTicks = 70;
|
||||
private static double eyeOfTheStormSurgeRange = 16.0D;
|
||||
private static int eyeOfTheStormSurgeDurationTicks = 300;
|
||||
private static double eyeOfTheStormSurgeRadius = 3.5D;
|
||||
private static double eyeOfTheStormSurgeHeight = 5.0D;
|
||||
private static double eyeOfTheStormSurgePullStrength = 0.45D;
|
||||
private static double eyeOfTheStormSurgeRadius = 4.0D;
|
||||
private static double eyeOfTheStormSurgeHeight = 6.0D;
|
||||
private static double eyeOfTheStormSurgePullStrength = 0.85D;
|
||||
private static double eyeOfTheStormSurgeReturnStrength = 1.4D;
|
||||
private static float eyeOfTheStormSurgeDamage = 5.0F;
|
||||
private static double vortexLensMinRadius = 1.5D;
|
||||
private static double vortexLensMaxRadius = 10.0D;
|
||||
private static double vortexLensMinHeight = 3.0D;
|
||||
private static double vortexLensMaxHeight = 20.0D;
|
||||
private static double vortexLensPullStrength = 0.42D;
|
||||
private static double vortexLensPushStrength = 0.38D;
|
||||
private static double vortexLensLaunchStrength = 1.15D;
|
||||
private static double vortexLensPullStrength = 0.65D;
|
||||
private static double vortexLensPushStrength = 0.65D;
|
||||
private static double vortexLensLaunchStrength = 1.4D;
|
||||
private static double missileSquidHealth = 60.0D;
|
||||
private static double missileSquidAttackDamage = 9.0D;
|
||||
private static double missileSquidMovementSpeed = 0.84D;
|
||||
@@ -496,6 +509,10 @@ public final class AntarchySettings {
|
||||
private static double creepingHorrorAttackDamage = 6.0D;
|
||||
private static double lurkingTerrorHealth = 15.0D;
|
||||
private static double lurkingTerrorAttackDamage = 6.0D;
|
||||
private static double manticoreHealth = 40.0D;
|
||||
private static double manticoreAttackDamage = 7.0D;
|
||||
private static int manticoreStingPoisonTicks = 100;
|
||||
private static int queenManticoreCap = 15;
|
||||
private static double jerryInfantHealth = 20.0D;
|
||||
private static double jerryInfantAttackDamage = 2.0D;
|
||||
private static double jerryMatureHealth = 40.0D;
|
||||
@@ -1914,6 +1931,45 @@ public final class AntarchySettings {
|
||||
public static int royalEggHatchChance() { return royalEggHatchChance; }
|
||||
public static void setRoyalEggHatchChance(int v) { royalEggHatchChance = v; }
|
||||
|
||||
public static double kingHealth() { return kingHealth; }
|
||||
public static void setKingHealth(double v) { kingHealth = v; }
|
||||
|
||||
public static double queenHealth() { return queenHealth; }
|
||||
public static void setQueenHealth(double v) { queenHealth = v; }
|
||||
|
||||
public static double kingAttackDamage() { return kingAttackDamage; }
|
||||
public static void setKingAttackDamage(double v) { kingAttackDamage = v; }
|
||||
|
||||
public static double queenAttackDamage() { return queenAttackDamage; }
|
||||
public static void setQueenAttackDamage(double v) { queenAttackDamage = v; }
|
||||
|
||||
public static double royalBossArmor() { return royalBossArmor; }
|
||||
public static void setRoyalBossArmor(double v) { royalBossArmor = v; }
|
||||
|
||||
public static double royalBossFollowRange() { return royalBossFollowRange; }
|
||||
public static void setRoyalBossFollowRange(double v) { royalBossFollowRange = v; }
|
||||
|
||||
public static double royalBossMovementSpeed() { return royalBossMovementSpeed; }
|
||||
public static void setRoyalBossMovementSpeed(double v) { royalBossMovementSpeed = v; }
|
||||
|
||||
public static double royalBossKnockbackResistance() { return royalBossKnockbackResistance; }
|
||||
public static void setRoyalBossKnockbackResistance(double v) { royalBossKnockbackResistance = v; }
|
||||
|
||||
public static double royalBossStepHeight() { return royalBossStepHeight; }
|
||||
public static void setRoyalBossStepHeight(double v) { royalBossStepHeight = v; }
|
||||
|
||||
public static double royalBossMaxSingleHitDamage() { return royalBossMaxSingleHitDamage; }
|
||||
public static void setRoyalBossMaxSingleHitDamage(double v) { royalBossMaxSingleHitDamage = v; }
|
||||
|
||||
public static double royalBossBiteReach() { return royalBossBiteReach; }
|
||||
public static void setRoyalBossBiteReach(double v) { royalBossBiteReach = v; }
|
||||
|
||||
public static double royalBossBiteDamageMultiplier() { return royalBossBiteDamageMultiplier; }
|
||||
public static void setRoyalBossBiteDamageMultiplier(double v) { royalBossBiteDamageMultiplier = v; }
|
||||
|
||||
public static int royalBossBiteCooldownTicks() { return royalBossBiteCooldownTicks; }
|
||||
public static void setRoyalBossBiteCooldownTicks(int v) { royalBossBiteCooldownTicks = v; }
|
||||
|
||||
public static double nightmareSwordBaseDamage() { return nightmareSwordBaseDamage; }
|
||||
public static void setNightmareSwordBaseDamage(double v) { nightmareSwordBaseDamage = v; }
|
||||
|
||||
@@ -2685,6 +2741,14 @@ public final class AntarchySettings {
|
||||
public static void setLurkingTerrorHealth(double v) { lurkingTerrorHealth = v; }
|
||||
public static double lurkingTerrorAttackDamage() { return lurkingTerrorAttackDamage; }
|
||||
public static void setLurkingTerrorAttackDamage(double v) { lurkingTerrorAttackDamage = v; }
|
||||
public static double manticoreHealth() { return manticoreHealth; }
|
||||
public static void setManticoreHealth(double v) { manticoreHealth = v; }
|
||||
public static double manticoreAttackDamage() { return manticoreAttackDamage; }
|
||||
public static void setManticoreAttackDamage(double v) { manticoreAttackDamage = v; }
|
||||
public static int manticoreStingPoisonTicks() { return manticoreStingPoisonTicks; }
|
||||
public static void setManticoreStingPoisonTicks(int v) { manticoreStingPoisonTicks = v; }
|
||||
public static int queenManticoreCap() { return queenManticoreCap; }
|
||||
public static void setQueenManticoreCap(int v) { queenManticoreCap = v; }
|
||||
public static double jerryInfantHealth() { return jerryInfantHealth; }
|
||||
public static void setJerryInfantHealth(double v) { jerryInfantHealth = v; }
|
||||
public static double jerryInfantAttackDamage() { return jerryInfantAttackDamage; }
|
||||
|
||||
@@ -32,6 +32,7 @@ import com.craisinlord.antarchy.content.entity.lucid.LucidEntity;
|
||||
import com.craisinlord.antarchy.content.entity.vortex.VortexChargeProjectileEntity;
|
||||
import com.craisinlord.antarchy.content.entity.vortex.VortexEntity;
|
||||
import com.craisinlord.antarchy.content.entity.vortex.WindVortexEntity;
|
||||
import com.craisinlord.antarchy.content.time.TimeDilationFieldEntity;
|
||||
import com.craisinlord.antarchy.content.entity.nightmare.NightmareEntity;
|
||||
import com.craisinlord.antarchy.content.entity.nightmare.NightmareBiteEntity;
|
||||
import com.craisinlord.antarchy.content.entity.nightmare.NightmarePortalEntity;
|
||||
@@ -81,6 +82,11 @@ public final class AntarchyObjects {
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Supplier<EntityType<DimensionalTearEntity>> DIMENSIONAL_TEAR = (Supplier<EntityType<DimensionalTearEntity>>) UNBOUND;
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Supplier<Block> DIMENSIONAL_TEAR_MARKER = (Supplier<Block>) UNBOUND;
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Supplier<BlockEntityType<com.craisinlord.antarchy.content.block.entity.DimensionalTearMarkerBlockEntity>> DIMENSIONAL_TEAR_MARKER_BLOCK_ENTITY =
|
||||
(Supplier<BlockEntityType<com.craisinlord.antarchy.content.block.entity.DimensionalTearMarkerBlockEntity>>) UNBOUND;
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Supplier<EntityType<NightmareBiteEntity>> NIGHTMARE_BITE = (Supplier<EntityType<NightmareBiteEntity>>) UNBOUND;
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Supplier<EntityType<LucidEntity>> LUCID = (Supplier<EntityType<LucidEntity>>) UNBOUND;
|
||||
@@ -91,6 +97,8 @@ public final class AntarchyObjects {
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Supplier<EntityType<VortexChargeProjectileEntity>> VORTEX_CHARGE_PROJECTILE = (Supplier<EntityType<VortexChargeProjectileEntity>>) UNBOUND;
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Supplier<EntityType<TimeDilationFieldEntity>> TIME_DILATION_FIELD = (Supplier<EntityType<TimeDilationFieldEntity>>) UNBOUND;
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Supplier<Block> VORTEX_LENS = (Supplier<Block>) UNBOUND;
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Supplier<BlockEntityType<VortexLensBlockEntity>> VORTEX_LENS_BLOCK_ENTITY = (Supplier<BlockEntityType<VortexLensBlockEntity>>) UNBOUND;
|
||||
@@ -129,6 +137,8 @@ public final class AntarchyObjects {
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Supplier<EntityType<LurkingTerrorEntity>> LURKING_TERROR = (Supplier<EntityType<LurkingTerrorEntity>>) UNBOUND;
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Supplier<EntityType<com.craisinlord.antarchy.content.entity.ManticoreEntity>> MANTICORE = (Supplier<EntityType<com.craisinlord.antarchy.content.entity.ManticoreEntity>>) UNBOUND;
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Supplier<EntityType<StinkBugEntity>> STINK_BUG = (Supplier<EntityType<StinkBugEntity>>) UNBOUND;
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Supplier<EntityType<com.craisinlord.antarchy.content.entity.RollyPollyEntity>> ROLLY_POLLY = (Supplier<EntityType<com.craisinlord.antarchy.content.entity.RollyPollyEntity>>) UNBOUND;
|
||||
@@ -145,6 +155,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.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;
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Supplier<Block> PRINCE_EGG = (Supplier<Block>) UNBOUND;
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Supplier<Block> PRINCESS_EGG = (Supplier<Block>) UNBOUND;
|
||||
@@ -625,6 +639,16 @@ public final class AntarchyObjects {
|
||||
DIMENSIONAL_TEAR = supplier;
|
||||
}
|
||||
|
||||
public static void setDimensionalTearMarker(Supplier<Block> supplier) {
|
||||
DIMENSIONAL_TEAR_MARKER = supplier;
|
||||
}
|
||||
|
||||
public static void setDimensionalTearMarkerBlockEntity(
|
||||
Supplier<BlockEntityType<com.craisinlord.antarchy.content.block.entity.DimensionalTearMarkerBlockEntity>> supplier
|
||||
) {
|
||||
DIMENSIONAL_TEAR_MARKER_BLOCK_ENTITY = supplier;
|
||||
}
|
||||
|
||||
public static void setNightmare(Supplier<EntityType<NightmareEntity>> supplier) {
|
||||
NIGHTMARE = supplier;
|
||||
}
|
||||
@@ -649,6 +673,10 @@ public final class AntarchyObjects {
|
||||
VORTEX_CHARGE_PROJECTILE = supplier;
|
||||
}
|
||||
|
||||
public static void setTimeDilationField(Supplier<EntityType<TimeDilationFieldEntity>> supplier) {
|
||||
TIME_DILATION_FIELD = supplier;
|
||||
}
|
||||
|
||||
public static void setVortexLens(Supplier<Block> supplier) {
|
||||
VORTEX_LENS = supplier;
|
||||
}
|
||||
@@ -689,6 +717,18 @@ public final class AntarchyObjects {
|
||||
ROYAL_BOLT = supplier;
|
||||
}
|
||||
|
||||
public static void setKing(Supplier<EntityType<com.craisinlord.antarchy.content.entity.royal.KingEntity>> supplier) {
|
||||
KING = supplier;
|
||||
}
|
||||
|
||||
public static void setManticore(Supplier<EntityType<com.craisinlord.antarchy.content.entity.ManticoreEntity>> supplier) {
|
||||
MANTICORE = supplier;
|
||||
}
|
||||
|
||||
public static void setQueen(Supplier<EntityType<com.craisinlord.antarchy.content.entity.royal.QueenEntity>> supplier) {
|
||||
QUEEN = supplier;
|
||||
}
|
||||
|
||||
public static void setPrinceEgg(Supplier<Block> supplier) {
|
||||
PRINCE_EGG = supplier;
|
||||
}
|
||||
|
||||
+25
-1
@@ -178,6 +178,14 @@ public final class AntarchySoundEvents {
|
||||
public static Supplier<SoundEvent> BLOOD_CRYSTAL_ARMOR_EQUIP = (Supplier<SoundEvent>) UNBOUND;
|
||||
public static Supplier<SoundEvent> BLOODGLASS_WARD_HEART_BREAK = (Supplier<SoundEvent>) UNBOUND;
|
||||
public static Supplier<SoundEvent> BLOODGLASS_WARD_HEART_REGEN = (Supplier<SoundEvent>) UNBOUND;
|
||||
public static Supplier<SoundEvent> KING_IDLE = (Supplier<SoundEvent>) UNBOUND;
|
||||
public static Supplier<SoundEvent> KING_HURT = (Supplier<SoundEvent>) UNBOUND;
|
||||
public static Supplier<SoundEvent> KING_DEATH = (Supplier<SoundEvent>) UNBOUND;
|
||||
public static Supplier<SoundEvent> KING_BITE = (Supplier<SoundEvent>) UNBOUND;
|
||||
public static Supplier<SoundEvent> QUEEN_IDLE = (Supplier<SoundEvent>) UNBOUND;
|
||||
public static Supplier<SoundEvent> QUEEN_HURT = (Supplier<SoundEvent>) UNBOUND;
|
||||
public static Supplier<SoundEvent> QUEEN_DEATH = (Supplier<SoundEvent>) UNBOUND;
|
||||
public static Supplier<SoundEvent> QUEEN_BITE = (Supplier<SoundEvent>) UNBOUND;
|
||||
|
||||
private AntarchySoundEvents() {
|
||||
}
|
||||
@@ -351,7 +359,15 @@ public final class AntarchySoundEvents {
|
||||
Supplier<SoundEvent> bloodCrystalKatanaDash,
|
||||
Supplier<SoundEvent> bloodCrystalArmorEquip,
|
||||
Supplier<SoundEvent> bloodglassWardHeartBreak,
|
||||
Supplier<SoundEvent> bloodglassWardHeartRegen
|
||||
Supplier<SoundEvent> bloodglassWardHeartRegen,
|
||||
Supplier<SoundEvent> kingIdle,
|
||||
Supplier<SoundEvent> kingHurt,
|
||||
Supplier<SoundEvent> kingDeath,
|
||||
Supplier<SoundEvent> kingBite,
|
||||
Supplier<SoundEvent> queenIdle,
|
||||
Supplier<SoundEvent> queenHurt,
|
||||
Supplier<SoundEvent> queenDeath,
|
||||
Supplier<SoundEvent> queenBite
|
||||
) {
|
||||
SQUIDZOOKA_FIRE = squidzookaFire;
|
||||
SHRINK_RAY_CHARGE = shrinkRayCharge;
|
||||
@@ -522,5 +538,13 @@ public final class AntarchySoundEvents {
|
||||
BLOOD_CRYSTAL_ARMOR_EQUIP = bloodCrystalArmorEquip;
|
||||
BLOODGLASS_WARD_HEART_BREAK = bloodglassWardHeartBreak;
|
||||
BLOODGLASS_WARD_HEART_REGEN = bloodglassWardHeartRegen;
|
||||
KING_IDLE = kingIdle;
|
||||
KING_HURT = kingHurt;
|
||||
KING_DEATH = kingDeath;
|
||||
KING_BITE = kingBite;
|
||||
QUEEN_IDLE = queenIdle;
|
||||
QUEEN_HURT = queenHurt;
|
||||
QUEEN_DEATH = queenDeath;
|
||||
QUEEN_BITE = queenBite;
|
||||
}
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
package com.craisinlord.antarchy.content.block;
|
||||
|
||||
import com.craisinlord.antarchy.content.block.entity.DimensionalTearMarkerBlockEntity;
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import java.util.function.Supplier;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.BlockGetter;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.BaseEntityBlock;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.RenderShape;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityTicker;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.state.BlockBehaviour;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.shapes.CollisionContext;
|
||||
import net.minecraft.world.phys.shapes.Shapes;
|
||||
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||
|
||||
public class DimensionalTearMarkerBlock extends BaseEntityBlock {
|
||||
private static final VoxelShape EMPTY = Shapes.empty();
|
||||
private final Supplier<? extends BlockEntityType<DimensionalTearMarkerBlockEntity>> blockEntityTypeSupplier;
|
||||
private final MapCodec<DimensionalTearMarkerBlock> codec;
|
||||
|
||||
public DimensionalTearMarkerBlock(Supplier<? extends BlockEntityType<DimensionalTearMarkerBlockEntity>> blockEntityTypeSupplier, BlockBehaviour.Properties properties) {
|
||||
super(properties);
|
||||
this.blockEntityTypeSupplier = blockEntityTypeSupplier;
|
||||
this.codec = Block.simpleCodec(ignored -> this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapCodec<DimensionalTearMarkerBlock> codec() {
|
||||
return this.codec;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RenderShape getRenderShape(BlockState state) {
|
||||
return RenderShape.INVISIBLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
|
||||
return EMPTY;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected VoxelShape getCollisionShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
|
||||
return EMPTY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
|
||||
return new DimensionalTearMarkerBlockEntity(pos, state, this.blockEntityTypeSupplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level, BlockState state, BlockEntityType<T> blockEntityType) {
|
||||
if (level.isClientSide) {
|
||||
return null;
|
||||
}
|
||||
return createTickerHelper(blockEntityType, this.blockEntityTypeSupplier.get(), (tickLevel, tickPos, tickState, blockEntity) -> {
|
||||
if (tickLevel instanceof ServerLevel serverLevel) {
|
||||
DimensionalTearMarkerBlockEntity.serverTick(serverLevel, tickPos, tickState, blockEntity);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package com.craisinlord.antarchy.content.block;
|
||||
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.particles.DustParticleOptions;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.state.BlockBehaviour;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import org.joml.Vector3f;
|
||||
|
||||
public class NadirVeilBlock extends Block {
|
||||
public static final MapCodec<NadirVeilBlock> CODEC = simpleCodec(NadirVeilBlock::new);
|
||||
|
||||
private static final int GLOW_LIGHT_LEVEL = 6;
|
||||
private static final DustParticleOptions DRIP_PARTICLE =
|
||||
new DustParticleOptions(new Vector3f(0.63F, 0.26F, 0.92F), 1.0F);
|
||||
|
||||
public NadirVeilBlock(BlockBehaviour.Properties properties) {
|
||||
super(properties.lightLevel(state -> GLOW_LIGHT_LEVEL));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MapCodec<? extends Block> codec() {
|
||||
return CODEC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void animateTick(BlockState state, Level level, BlockPos pos, RandomSource random) {
|
||||
if (random.nextInt(6) != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
double x = pos.getX() + 0.5D + (random.nextDouble() - 0.5D) * 0.7D;
|
||||
double y = pos.getY() + 0.15D + random.nextDouble() * 0.4D;
|
||||
double z = pos.getZ() + 0.5D + (random.nextDouble() - 0.5D) * 0.7D;
|
||||
level.addParticle(DRIP_PARTICLE, x, y, z, 0.0D, 0.02D + random.nextDouble() * 0.02D, 0.0D);
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,9 @@ import com.craisinlord.antarchy.content.block.entity.RoyalEggBlockEntity;
|
||||
import com.craisinlord.antarchy.content.entity.royal.RoyalMountEntity;
|
||||
import java.util.UUID;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.core.particles.ParticleTypes;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.sounds.SoundEvents;
|
||||
@@ -17,20 +19,35 @@ import net.minecraft.world.entity.MobSpawnType;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.entity.item.FallingBlockEntity;
|
||||
import net.minecraft.world.level.LevelAccessor;
|
||||
import net.minecraft.world.level.block.BaseEntityBlock;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.FallingBlock;
|
||||
import net.minecraft.world.level.block.RenderShape;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.StateDefinition;
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
|
||||
import net.minecraft.world.level.block.state.properties.IntegerProperty;
|
||||
import net.minecraft.world.level.BlockGetter;
|
||||
import net.minecraft.world.phys.shapes.CollisionContext;
|
||||
import net.minecraft.world.phys.shapes.Shapes;
|
||||
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public abstract class RoyalEggBlock extends BaseEntityBlock {
|
||||
public static final IntegerProperty HATCH = BlockStateProperties.HATCH;
|
||||
private static final int MAX_HATCH = 2;
|
||||
private static final int FALL_DELAY = 2;
|
||||
|
||||
private static final VoxelShape SHAPE = Shapes.or(
|
||||
Block.box(3.0D, 0.0D, 3.0D, 13.0D, 13.0D, 13.0D),
|
||||
Block.box(2.0D, 1.0D, 2.0D, 14.0D, 11.0D, 14.0D),
|
||||
Block.box(4.0D, 13.0D, 4.0D, 12.0D, 14.0D, 12.0D),
|
||||
Block.box(5.0D, 14.0D, 5.0D, 11.0D, 15.0D, 11.0D),
|
||||
Block.box(6.0D, 15.0D, 6.0D, 10.0D, 16.0D, 10.0D));
|
||||
|
||||
protected RoyalEggBlock(Properties properties) {
|
||||
super(properties);
|
||||
@@ -49,6 +66,11 @@ public abstract class RoyalEggBlock extends BaseEntityBlock {
|
||||
return RenderShape.MODEL;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
|
||||
return SHAPE;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
|
||||
@@ -94,6 +116,31 @@ public abstract class RoyalEggBlock extends BaseEntityBlock {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPlace(BlockState state, Level level, BlockPos pos, BlockState oldState, boolean movedByPiston) {
|
||||
level.scheduleTick(pos, this, FALL_DELAY);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor level, BlockPos pos, BlockPos neighborPos) {
|
||||
level.scheduleTick(pos, this, FALL_DELAY);
|
||||
return super.updateShape(state, direction, neighborState, level, pos, neighborPos);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
|
||||
if (!FallingBlock.isFree(level.getBlockState(pos.below())) || pos.getY() < level.getMinBuildHeight()) {
|
||||
return;
|
||||
}
|
||||
CompoundTag blockData = level.getBlockEntity(pos) instanceof RoyalEggBlockEntity egg
|
||||
? egg.saveWithoutMetadata(level.registryAccess())
|
||||
: null;
|
||||
FallingBlockEntity falling = FallingBlockEntity.fall(level, pos, state);
|
||||
if (blockData != null && !blockData.isEmpty()) {
|
||||
falling.blockData = blockData;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isRandomlyTicking(BlockState state) {
|
||||
return true;
|
||||
|
||||
+9
-3
@@ -26,6 +26,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
public class VortexLensBlock extends BaseEntityBlock {
|
||||
public static final DirectionProperty FACING = BlockStateProperties.FACING;
|
||||
public static final BooleanProperty POWERED = BlockStateProperties.POWERED;
|
||||
public static final BooleanProperty PUSHING = BooleanProperty.create("pushing");
|
||||
|
||||
private final Supplier<? extends BlockEntityType<VortexLensBlockEntity>> blockEntityTypeSupplier;
|
||||
private final MapCodec<VortexLensBlock> codec;
|
||||
@@ -35,7 +36,8 @@ public class VortexLensBlock extends BaseEntityBlock {
|
||||
super(properties);
|
||||
this.blockEntityTypeSupplier = blockEntityTypeSupplier;
|
||||
this.codec = Block.simpleCodec(ignored -> this);
|
||||
this.registerDefaultState(this.stateDefinition.any().setValue(FACING, Direction.UP).setValue(POWERED, false));
|
||||
this.registerDefaultState(this.stateDefinition.any()
|
||||
.setValue(FACING, Direction.UP).setValue(POWERED, false).setValue(PUSHING, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -45,14 +47,18 @@ public class VortexLensBlock extends BaseEntityBlock {
|
||||
|
||||
@Override
|
||||
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
|
||||
builder.add(FACING, POWERED);
|
||||
builder.add(FACING, POWERED, PUSHING);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockState getStateForPlacement(BlockPlaceContext context) {
|
||||
Level level = context.getLevel();
|
||||
BlockPos pos = context.getClickedPos();
|
||||
boolean bluestone = BluestoneSignalHelper.getBestNeighborSignal(level, pos) > 0;
|
||||
return this.defaultBlockState()
|
||||
.setValue(FACING, context.getClickedFace())
|
||||
.setValue(POWERED, hasAnySignal(context.getLevel(), context.getClickedPos()));
|
||||
.setValue(POWERED, hasAnySignal(level, pos))
|
||||
.setValue(PUSHING, bluestone);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
package com.craisinlord.antarchy.content.block.entity;
|
||||
|
||||
import com.craisinlord.antarchy.config.AntarchySettings;
|
||||
import com.craisinlord.antarchy.content.entity.portal.DimensionalTearEntity;
|
||||
import java.util.function.Supplier;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.HolderLookup;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.util.Mth;
|
||||
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.Vec3;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class DimensionalTearMarkerBlockEntity extends BlockEntity {
|
||||
private static final int MAX_ATTEMPT_TICKS = 100;
|
||||
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 int attemptTicks;
|
||||
|
||||
public DimensionalTearMarkerBlockEntity(
|
||||
BlockPos pos,
|
||||
BlockState blockState,
|
||||
Supplier<? extends BlockEntityType<DimensionalTearMarkerBlockEntity>> blockEntityTypeSupplier
|
||||
) {
|
||||
super(blockEntityTypeSupplier.get(), pos, blockState);
|
||||
}
|
||||
|
||||
public static void serverTick(ServerLevel level, BlockPos pos, BlockState state, DimensionalTearMarkerBlockEntity blockEntity) {
|
||||
blockEntity.attemptTicks++;
|
||||
boolean spawned = blockEntity.trySpawnLinkedPair(level, pos);
|
||||
if (spawned || blockEntity.attemptTicks >= MAX_ATTEMPT_TICKS) {
|
||||
level.removeBlock(pos, false);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean trySpawnLinkedPair(ServerLevel level, BlockPos origin) {
|
||||
RandomSource random = level.getRandom();
|
||||
BlockPos first = findOpenPocket(level, origin, random, LOCAL_SEARCH_ATTEMPTS);
|
||||
if (first == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
BlockPos second = null;
|
||||
for (int i = 0; i < PARTNER_ORIGIN_ATTEMPTS && second == null; i++) {
|
||||
second = findOpenPocket(level, linkedOrigin(first, random), random, PARTNER_SEARCH_ATTEMPTS);
|
||||
}
|
||||
if (second == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int lifetime = Math.max(1200, AntarchySettings.dimensionalTearLifetimeTicks());
|
||||
float yawA = random.nextFloat() * 360.0F;
|
||||
float yawB = Mth.wrapDegrees(yawA + 140.0F + random.nextFloat() * 80.0F);
|
||||
DimensionalTearEntity tearA = DimensionalTearEntity.create(level, center(first), yawA, lifetime);
|
||||
DimensionalTearEntity tearB = DimensionalTearEntity.create(level, center(second), yawB, lifetime);
|
||||
tearA.linkTo(tearB);
|
||||
tearB.linkTo(tearA);
|
||||
level.addFreshEntity(tearA);
|
||||
level.addFreshEntity(tearB);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static BlockPos linkedOrigin(BlockPos first, RandomSource random) {
|
||||
double angle = random.nextDouble() * Math.PI * 2.0D;
|
||||
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);
|
||||
return first.offset(dx, dy, dz);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static BlockPos findOpenPocket(ServerLevel level, BlockPos origin, RandomSource random, int attempts) {
|
||||
int minY = level.getMinBuildHeight() + 8;
|
||||
int maxY = level.getMaxBuildHeight() - 8;
|
||||
for (int i = 0; i < attempts; i++) {
|
||||
BlockPos candidate = origin.offset(
|
||||
Mth.nextInt(random, -16, 16),
|
||||
Mth.nextInt(random, -24, 24),
|
||||
Mth.nextInt(random, -16, 16)
|
||||
);
|
||||
candidate = new BlockPos(candidate.getX(), Mth.clamp(candidate.getY(), minY, maxY), candidate.getZ());
|
||||
if (isUsablePocket(level, candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean isUsablePocket(ServerLevel level, BlockPos pos) {
|
||||
for (int dx = -2; dx <= 2; dx++) {
|
||||
for (int dy = -2; dy <= 3; dy++) {
|
||||
for (int dz = -4; dz <= 4; dz++) {
|
||||
BlockPos checkPos = pos.offset(dx, dy, dz);
|
||||
if (!level.isLoaded(checkPos)) {
|
||||
return false;
|
||||
}
|
||||
if (!level.getBlockState(checkPos).getCollisionShape(level, checkPos).isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static Vec3 center(BlockPos pos) {
|
||||
return new Vec3(pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void saveAdditional(CompoundTag tag, HolderLookup.Provider registries) {
|
||||
super.saveAdditional(tag, registries);
|
||||
tag.putInt("attempt_ticks", this.attemptTicks);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadAdditional(CompoundTag tag, HolderLookup.Provider registries) {
|
||||
super.loadAdditional(tag, registries);
|
||||
this.attemptTicks = tag.getInt("attempt_ticks");
|
||||
}
|
||||
}
|
||||
+10
-5
@@ -66,7 +66,7 @@ public class VortexLensBlockEntity extends BlockEntity {
|
||||
|
||||
if (signal <= 0) {
|
||||
this.removeActiveVortex();
|
||||
this.updatePoweredState(level, pos, state, false);
|
||||
this.updateBlockState(level, pos, state, false, false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ public class VortexLensBlockEntity extends BlockEntity {
|
||||
vortex.resetVortexAge();
|
||||
vortex.setVortexStrengths(strength, AntarchySettings.vortexLensLaunchStrength());
|
||||
vortex.setDamaging(false);
|
||||
this.updatePoweredState(level, pos, state, true);
|
||||
this.updateBlockState(level, pos, state, true, pushMode);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -131,9 +131,14 @@ public class VortexLensBlockEntity extends BlockEntity {
|
||||
return null;
|
||||
}
|
||||
|
||||
private void updatePoweredState(ServerLevel level, BlockPos pos, BlockState state, boolean powered) {
|
||||
if (state.getBlock() instanceof VortexLensBlock && state.getValue(VortexLensBlock.POWERED) != powered) {
|
||||
level.setBlock(pos, state.setValue(VortexLensBlock.POWERED, powered), Block.UPDATE_CLIENTS);
|
||||
private void updateBlockState(ServerLevel level, BlockPos pos, BlockState state, boolean powered, boolean pushing) {
|
||||
if (!(state.getBlock() instanceof VortexLensBlock)) {
|
||||
return;
|
||||
}
|
||||
if (state.getValue(VortexLensBlock.POWERED) != powered || state.getValue(VortexLensBlock.PUSHING) != pushing) {
|
||||
level.setBlock(pos,
|
||||
state.setValue(VortexLensBlock.POWERED, powered).setValue(VortexLensBlock.PUSHING, pushing),
|
||||
Block.UPDATE_CLIENTS);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
package com.craisinlord.antarchy.content.client;
|
||||
|
||||
import com.craisinlord.antarchy.content.time.TimeDilationEntityAccess;
|
||||
import com.craisinlord.antarchy.content.time.TimeDilationFieldEntity;
|
||||
import com.craisinlord.antarchy.content.time.TimeDilationFieldSampler;
|
||||
import com.craisinlord.antarchy.content.time.TimeDilationMath;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.WeakHashMap;
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
|
||||
public final class ClientTimeDilationTicker {
|
||||
private static boolean hadFields;
|
||||
private static volatile List<TimeDilationFieldEntity> activeFields = List.of();
|
||||
private static final Map<Entity, Double> ANIM_OFFSETS = new WeakHashMap<>();
|
||||
|
||||
private ClientTimeDilationTicker() {
|
||||
}
|
||||
|
||||
public static void tick(ClientLevel level) {
|
||||
List<TimeDilationFieldEntity> fields = new ArrayList<>();
|
||||
for (Entity entity : level.entitiesForRendering()) {
|
||||
if (entity instanceof TimeDilationFieldEntity field && field.isAlive()) {
|
||||
fields.add(field);
|
||||
}
|
||||
}
|
||||
activeFields = fields;
|
||||
|
||||
if (fields.isEmpty()) {
|
||||
if (hadFields) {
|
||||
resetRates(level);
|
||||
hadFields = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
hadFields = true;
|
||||
|
||||
for (Entity entity : level.entitiesForRendering()) {
|
||||
if (!(entity instanceof TimeDilationEntityAccess access) || entity instanceof TimeDilationFieldEntity) {
|
||||
continue;
|
||||
}
|
||||
double rate = TimeDilationFieldSampler.sample(fields, entity.position());
|
||||
access.antarchy$setTimeDilationRate(rate);
|
||||
if (rate < TimeDilationMath.NORMAL_RATE) {
|
||||
ANIM_OFFSETS.merge(entity, rate - TimeDilationMath.NORMAL_RATE, Double::sum);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static float dilateAnimationTime(Entity entity, float vanillaTime) {
|
||||
Double offset = ANIM_OFFSETS.get(entity);
|
||||
return offset == null ? vanillaTime : (float) (vanillaTime + offset);
|
||||
}
|
||||
|
||||
public static double rateAt(double x, double y, double z) {
|
||||
List<TimeDilationFieldEntity> fields = activeFields;
|
||||
if (fields.isEmpty()) {
|
||||
return TimeDilationMath.NORMAL_RATE;
|
||||
}
|
||||
return TimeDilationFieldSampler.sample(fields, x, y, z);
|
||||
}
|
||||
|
||||
private static void resetRates(ClientLevel level) {
|
||||
for (Entity entity : level.entitiesForRendering()) {
|
||||
if (!(entity instanceof TimeDilationEntityAccess access) || entity instanceof TimeDilationFieldEntity) {
|
||||
continue;
|
||||
}
|
||||
if (access.antarchy$getTimeDilationRate() < TimeDilationMath.NORMAL_RATE) {
|
||||
access.antarchy$setTimeDilationRate(TimeDilationMath.NORMAL_RATE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
-1
@@ -256,7 +256,24 @@ public final class HoverboardTrailSystem {
|
||||
if (color == null) {
|
||||
return DEFAULT_COLOR;
|
||||
}
|
||||
return rgb(color.getTextureDiffuseColor());
|
||||
return switch (color) {
|
||||
case PURPLE -> rgb(0x00FF3C);
|
||||
case WHITE -> rgb(0xFF1919);
|
||||
case ORANGE -> rgb(0x33CCFF);
|
||||
case LIGHT_BLUE -> rgb(0xFF8A00);
|
||||
case YELLOW -> rgb(0xB026FF);
|
||||
case PINK -> rgb(0x66E0FF);
|
||||
case LIME -> rgb(0xFF00E6);
|
||||
case GRAY -> rgb(0xFF9F4D);
|
||||
case LIGHT_GRAY -> rgb(0x9BFF1A);
|
||||
case BLUE -> rgb(0x2ED9A0);
|
||||
case BROWN -> rgb(0x66C6FF);
|
||||
case GREEN -> rgb(0xFF3B1F);
|
||||
case RED -> rgb(0xFF7A00);
|
||||
case BLACK -> rgb(0xFFFFFF);
|
||||
case MAGENTA -> rgb(0x00E5FF);
|
||||
case CYAN -> rgb(0xFF3DDB);
|
||||
};
|
||||
}
|
||||
|
||||
private static float[] rgb(int color) {
|
||||
|
||||
+8
-2
@@ -7,6 +7,8 @@ import com.craisinlord.antarchy.content.entity.HerculesBeetleEntity;
|
||||
import com.craisinlord.antarchy.content.entity.ToreterrorEntity;
|
||||
import com.craisinlord.antarchy.content.entity.brutalfly.BrutalflyEntity;
|
||||
import com.craisinlord.antarchy.content.entity.kraken.KrakenEntity;
|
||||
import com.craisinlord.antarchy.content.entity.royal.KingEntity;
|
||||
import com.craisinlord.antarchy.content.entity.royal.QueenEntity;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
@@ -29,7 +31,9 @@ public final class CustomBossBarRenderer {
|
||||
AlphaMantisEntity.class, textures("green", "alpha_mantis", 205, 49, 6),
|
||||
BrutalflyEntity.class, textures("brutalfly", "brutalfly", 198, 38, 3),
|
||||
HerculesBeetleEntity.class, textures("beetle", "hercules_beetle", 194, 33, 1),
|
||||
EmperorScorpionEntity.class, textures("purple", "scorpion", 218, 34, -2)
|
||||
EmperorScorpionEntity.class, textures("purple", "scorpion", 218, 34, -2),
|
||||
KingEntity.class, textures("king", "king", 256, 67, 4),
|
||||
QueenEntity.class, textures("queen", "queen", 253, 50, 6)
|
||||
);
|
||||
private static final Map<String, Textures> BY_LOCALIZED_NAME_AND_COLOR = Map.of(
|
||||
key(Component.translatable("entity.antarchy.kraken").getString(), BossEvent.BossBarColor.BLUE), BY_ENTITY_CLASS.get(KrakenEntity.class),
|
||||
@@ -37,7 +41,9 @@ public final class CustomBossBarRenderer {
|
||||
key(Component.translatable("entity.antarchy.alpha_mantis").getString(), BossEvent.BossBarColor.GREEN), BY_ENTITY_CLASS.get(AlphaMantisEntity.class),
|
||||
key(Component.translatable("entity.antarchy.brutalfly").getString(), BossEvent.BossBarColor.YELLOW), BY_ENTITY_CLASS.get(BrutalflyEntity.class),
|
||||
key(Component.translatable("entity.antarchy.hercules_beetle").getString(), BossEvent.BossBarColor.RED), BY_ENTITY_CLASS.get(HerculesBeetleEntity.class),
|
||||
key(Component.translatable("entity.antarchy.emperor_scorpion").getString(), BossEvent.BossBarColor.PURPLE), BY_ENTITY_CLASS.get(EmperorScorpionEntity.class)
|
||||
key(Component.translatable("entity.antarchy.emperor_scorpion").getString(), BossEvent.BossBarColor.PURPLE), BY_ENTITY_CLASS.get(EmperorScorpionEntity.class),
|
||||
key(Component.translatable("entity.antarchy.king").getString(), BossEvent.BossBarColor.YELLOW), BY_ENTITY_CLASS.get(KingEntity.class),
|
||||
key(Component.translatable("entity.antarchy.queen").getString(), BossEvent.BossBarColor.PURPLE), BY_ENTITY_CLASS.get(QueenEntity.class)
|
||||
);
|
||||
|
||||
private CustomBossBarRenderer() {
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package com.craisinlord.antarchy.content.client.model;
|
||||
|
||||
import com.craisinlord.antarchy.Antarchy;
|
||||
import com.craisinlord.antarchy.content.entity.ManticoreEntity;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import software.bernie.geckolib.model.GeoModel;
|
||||
|
||||
public class ManticoreModel extends GeoModel<ManticoreEntity> {
|
||||
@Override
|
||||
public ResourceLocation getModelResource(ManticoreEntity animatable) {
|
||||
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "geo/manticore.geo.json");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceLocation getTextureResource(ManticoreEntity animatable) {
|
||||
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "textures/entity/manticore.png");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceLocation getAnimationResource(ManticoreEntity animatable) {
|
||||
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "animations/manticore.animation.json");
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package com.craisinlord.antarchy.content.client.model;
|
||||
|
||||
import com.craisinlord.antarchy.Antarchy;
|
||||
import com.craisinlord.antarchy.content.entity.royal.RoyalBossEntity;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import software.bernie.geckolib.model.GeoModel;
|
||||
|
||||
public class RoyalBossModel extends GeoModel<RoyalBossEntity> {
|
||||
@Override
|
||||
public ResourceLocation getModelResource(RoyalBossEntity animatable) {
|
||||
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "geo/" + animatable.geoNameForRender() + ".geo.json");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceLocation getTextureResource(RoyalBossEntity animatable) {
|
||||
String name = animatable.geoNameForRender();
|
||||
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "textures/entity/" + name + "/" + name + ".png");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceLocation getAnimationResource(RoyalBossEntity animatable) {
|
||||
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "animations/" + animatable.geoNameForRender() + ".animation.json");
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package com.craisinlord.antarchy.content.client.renderer;
|
||||
|
||||
import com.craisinlord.antarchy.content.client.model.ManticoreModel;
|
||||
import com.craisinlord.antarchy.content.entity.ManticoreEntity;
|
||||
import net.minecraft.client.renderer.entity.EntityRendererProvider;
|
||||
import software.bernie.geckolib.renderer.GeoEntityRenderer;
|
||||
|
||||
public class ManticoreRenderer extends GeoEntityRenderer<ManticoreEntity> {
|
||||
public ManticoreRenderer(EntityRendererProvider.Context context) {
|
||||
super(context, new ManticoreModel());
|
||||
this.shadowRadius = 0.9F;
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package com.craisinlord.antarchy.content.client.renderer;
|
||||
|
||||
import com.craisinlord.antarchy.content.client.model.RoyalBossModel;
|
||||
import com.craisinlord.antarchy.content.entity.royal.RoyalBossEntity;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.client.renderer.entity.EntityRendererProvider;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import software.bernie.geckolib.cache.object.BakedGeoModel;
|
||||
import software.bernie.geckolib.renderer.GeoEntityRenderer;
|
||||
|
||||
public class RoyalBossRenderer extends GeoEntityRenderer<RoyalBossEntity> {
|
||||
public RoyalBossRenderer(EntityRendererProvider.Context context) {
|
||||
super(context, new RoyalBossModel());
|
||||
this.shadowRadius = 8.0F;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RenderType getRenderType(RoyalBossEntity animatable, ResourceLocation texture, @Nullable MultiBufferSource bufferSource, float partialTick) {
|
||||
return RenderType.entityCutoutNoCull(texture);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preRender(PoseStack poseStack, RoyalBossEntity animatable, BakedGeoModel model, @Nullable MultiBufferSource bufferSource,
|
||||
@Nullable VertexConsumer buffer, boolean isReRender, float partialTick, int packedLight, int packedOverlay, int colour) {
|
||||
super.preRender(poseStack, animatable, model, bufferSource, buffer, isReRender, partialTick, packedLight, packedOverlay, colour);
|
||||
poseStack.scale(RoyalBossEntity.MODEL_RENDER_SCALE, RoyalBossEntity.MODEL_RENDER_SCALE, RoyalBossEntity.MODEL_RENDER_SCALE);
|
||||
this.shadowRadius = 8.0F * RoyalBossEntity.MODEL_RENDER_SCALE;
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package com.craisinlord.antarchy.content.client.renderer;
|
||||
|
||||
import com.craisinlord.antarchy.content.time.TimeDilationFieldEntity;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.entity.EntityRenderer;
|
||||
import net.minecraft.client.renderer.entity.EntityRendererProvider;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
public class TimeDilationFieldRenderer extends EntityRenderer<TimeDilationFieldEntity> {
|
||||
public TimeDilationFieldRenderer(EntityRendererProvider.Context context) {
|
||||
super(context);
|
||||
this.shadowRadius = 0.0F;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(TimeDilationFieldEntity entity, float entityYaw, float partialTick, PoseStack poseStack,
|
||||
MultiBufferSource bufferSource, int packedLight) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceLocation getTextureLocation(TimeDilationFieldEntity entity) {
|
||||
return ResourceLocation.withDefaultNamespace("textures/misc/unknown_pack.png");
|
||||
}
|
||||
}
|
||||
+8
-3
@@ -406,7 +406,7 @@ public final class CreativeTabContents {
|
||||
entry("shrink_ray", "combat"),
|
||||
entry("growth_ray", "combat"),
|
||||
entry("gravity_gun", "combat"),
|
||||
entry("portal_gun", "combat"),
|
||||
// entry("portal_gun", "combat"), // deferred — out of scope for 2.0
|
||||
entry("eye_of_the_storm", "combat"),
|
||||
entry("water_cannon", "combat"),
|
||||
entry("attitude_adjuster", "combat"),
|
||||
@@ -436,8 +436,13 @@ public final class CreativeTabContents {
|
||||
entry("royal_assailant_chestplate", "combat"),
|
||||
entry("royal_assailant_leggings", "combat"),
|
||||
entry("royal_assailant_boots", "combat"),
|
||||
entry("prince_egg", "spawn_eggs"),
|
||||
entry("princess_egg", "spawn_eggs"),
|
||||
entry("prince_egg", "functional_blocks"),
|
||||
entry("princess_egg", "functional_blocks"),
|
||||
entry("king_spawn_egg", "spawn_eggs"),
|
||||
entry("queen_spawn_egg", "spawn_eggs"),
|
||||
entry("manticore_spawn_egg", "spawn_eggs"),
|
||||
entry("prince_spawn_egg", "spawn_eggs"),
|
||||
entry("princess_spawn_egg", "spawn_eggs"),
|
||||
entry("tigers_eye_helmet", "combat"),
|
||||
entry("tigers_eye_chestplate", "combat"),
|
||||
entry("tigers_eye_leggings", "combat"),
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package com.craisinlord.antarchy.content.dispenser;
|
||||
|
||||
import com.craisinlord.antarchy.content.item.EyeOfTheStormItem;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.core.dispenser.BlockSource;
|
||||
import net.minecraft.core.dispenser.DefaultDispenseItemBehavior;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.block.DispenserBlock;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
public class EyeOfTheStormDispenseBehavior extends DefaultDispenseItemBehavior {
|
||||
@Override
|
||||
protected ItemStack execute(BlockSource source, ItemStack stack) {
|
||||
ServerLevel level = source.level();
|
||||
Direction direction = source.state().getValue(DispenserBlock.FACING);
|
||||
BlockPos pos = source.pos().relative(direction);
|
||||
Vec3 origin = new Vec3(pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D);
|
||||
Vec3 aim = new Vec3(direction.getStepX(), direction.getStepY(), direction.getStepZ());
|
||||
|
||||
EyeOfTheStormItem.launchStormVortex(level, origin, aim, null);
|
||||
|
||||
if (stack.isDamageableItem()) {
|
||||
stack.setDamageValue(stack.getDamageValue() + 1);
|
||||
if (stack.getDamageValue() >= stack.getMaxDamage()) {
|
||||
stack.shrink(1);
|
||||
}
|
||||
}
|
||||
return stack;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void playSound(BlockSource source) {
|
||||
source.level().levelEvent(1002, source.pos(), 0);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -51,7 +51,7 @@ public class HoverboardEntity extends PathfinderMob implements GeoEntity {
|
||||
private static final RawAnimation MOVE_ANIM = RawAnimation.begin().thenLoop("move");
|
||||
|
||||
private static final double MAX_HOVER_HEIGHT = 5.0D;
|
||||
private static final double ASCEND_SPEED = 0.15D;
|
||||
private static final double ASCEND_SPEED = 0.2D;
|
||||
private static final double SETTLE_DESCEND_SPEED = 0.08D;
|
||||
private static final double FAST_DESCEND_SPEED = 0.9D;
|
||||
private static final double FAST_DESCEND_RAMP = 0.2D;
|
||||
|
||||
+367
@@ -0,0 +1,367 @@
|
||||
package com.craisinlord.antarchy.content.entity;
|
||||
|
||||
import com.craisinlord.antarchy.config.AntarchySettings;
|
||||
import com.craisinlord.antarchy.content.entity.royal.QueenEntity;
|
||||
import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.Difficulty;
|
||||
import net.minecraft.world.DifficultyInstance;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.damagesource.DamageSource;
|
||||
import net.minecraft.world.effect.MobEffectInstance;
|
||||
import net.minecraft.world.effect.MobEffects;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.Mob;
|
||||
import net.minecraft.world.entity.MobSpawnType;
|
||||
import net.minecraft.world.entity.SpawnGroupData;
|
||||
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
|
||||
import net.minecraft.world.entity.ai.attributes.Attributes;
|
||||
import net.minecraft.world.entity.ai.control.FlyingMoveControl;
|
||||
import net.minecraft.world.entity.ai.goal.FloatGoal;
|
||||
import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal;
|
||||
import net.minecraft.world.entity.ai.goal.MeleeAttackGoal;
|
||||
import net.minecraft.world.entity.ai.goal.RandomLookAroundGoal;
|
||||
import net.minecraft.world.entity.ai.goal.WaterAvoidingRandomStrollGoal;
|
||||
import net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal;
|
||||
import net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal;
|
||||
import net.minecraft.world.entity.ai.navigation.FlyingPathNavigation;
|
||||
import net.minecraft.world.entity.ai.navigation.PathNavigation;
|
||||
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.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
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 ManticoreEntity extends Monster implements GeoEntity {
|
||||
private static final String SUMMONER_KEY = "QueenSummoner";
|
||||
private static final int LOST_SUMMONER_GRACE_TICKS = 200;
|
||||
private static final int TAKEOFF_COOLDOWN_TICKS = 40;
|
||||
private static final int STING_COOLDOWN_TICKS = 60;
|
||||
private static final float STING_CHANCE = 0.35F;
|
||||
private static final int SWARM_RETARGET_INTERVAL = 40;
|
||||
|
||||
private static final RawAnimation IDLE_ANIM = RawAnimation.begin().thenLoop("idle");
|
||||
private static final RawAnimation WALK_ANIM = RawAnimation.begin().thenLoop("walk");
|
||||
private static final RawAnimation FLY_ANIM = RawAnimation.begin().thenLoop("fly");
|
||||
private static final RawAnimation BITE_ANIM = RawAnimation.begin().thenPlay("bite");
|
||||
private static final RawAnimation FLY_BITE_ANIM = RawAnimation.begin().thenPlay("fly_bite");
|
||||
private static final RawAnimation STING_ANIM = RawAnimation.begin().thenPlay("sting");
|
||||
private static final RawAnimation FLY_START_ANIM = RawAnimation.begin().thenPlay("fly_start");
|
||||
private static final RawAnimation FLY_LAND_ANIM = RawAnimation.begin().thenPlay("fly_land");
|
||||
|
||||
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
|
||||
|
||||
@Nullable
|
||||
private UUID summonerId;
|
||||
private int lostSummonerTicks;
|
||||
private int takeoffCooldown;
|
||||
private int stingCooldown;
|
||||
private boolean wasFlyingLastTick;
|
||||
|
||||
public ManticoreEntity(EntityType<? extends ManticoreEntity> type, Level level) {
|
||||
super(type, level);
|
||||
this.moveControl = new FlyingMoveControl(this, 10, true);
|
||||
this.xpReward = 12;
|
||||
}
|
||||
|
||||
public static AttributeSupplier.Builder createAttributes() {
|
||||
return Mob.createMobAttributes()
|
||||
.add(Attributes.MAX_HEALTH, AntarchySettings.manticoreHealth())
|
||||
.add(Attributes.MOVEMENT_SPEED, 0.3D)
|
||||
.add(Attributes.FLYING_SPEED, 0.6D)
|
||||
.add(Attributes.ATTACK_DAMAGE, AntarchySettings.manticoreAttackDamage())
|
||||
.add(Attributes.FOLLOW_RANGE, 28.0D)
|
||||
.add(Attributes.KNOCKBACK_RESISTANCE, 0.2D);
|
||||
}
|
||||
|
||||
public static boolean canSpawn(EntityType<ManticoreEntity> type, ServerLevelAccessor level, MobSpawnType spawnReason, BlockPos pos, RandomSource random) {
|
||||
if (spawnReason == MobSpawnType.SPAWN_EGG || spawnReason == MobSpawnType.SPAWNER || spawnReason == MobSpawnType.COMMAND) {
|
||||
return true;
|
||||
}
|
||||
BlockState below = level.getBlockState(pos.below());
|
||||
BlockState above = level.getBlockState(pos.above());
|
||||
boolean floorSupport = !below.is(Blocks.BEDROCK) && below.isFaceSturdy(level, pos.below(), Direction.UP);
|
||||
boolean ceilingSupport = !above.is(Blocks.BEDROCK) && above.isFaceSturdy(level, pos.above(), Direction.DOWN);
|
||||
return level.getDifficulty() != Difficulty.PEACEFUL
|
||||
&& (floorSupport || ceilingSupport)
|
||||
&& level.isEmptyBlock(pos)
|
||||
&& Monster.checkMonsterSpawnRules(type, level, spawnReason, pos, random);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PathNavigation createNavigation(Level level) {
|
||||
FlyingPathNavigation navigation = new FlyingPathNavigation(this, level);
|
||||
navigation.setCanOpenDoors(false);
|
||||
navigation.setCanFloat(true);
|
||||
navigation.setCanPassDoors(true);
|
||||
return navigation;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void registerGoals() {
|
||||
this.goalSelector.addGoal(0, new FloatGoal(this));
|
||||
this.goalSelector.addGoal(1, new ManticoreAttackGoal(this));
|
||||
this.goalSelector.addGoal(3, new WaterAvoidingRandomStrollGoal(this, 0.9D));
|
||||
this.goalSelector.addGoal(4, new LookAtPlayerGoal(this, Player.class, 10.0F));
|
||||
this.goalSelector.addGoal(5, new RandomLookAroundGoal(this));
|
||||
this.targetSelector.addGoal(1, new HurtByTargetGoal(this));
|
||||
this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpawnGroupData finalizeSpawn(ServerLevelAccessor level, DifficultyInstance difficulty, MobSpawnType spawnReason, @Nullable SpawnGroupData spawnData) {
|
||||
ConfiguredMobSpawnUtil.applyConfiguredHealth(this, AntarchySettings.manticoreHealth());
|
||||
return super.finalizeSpawn(level, difficulty, spawnReason, spawnData);
|
||||
}
|
||||
|
||||
public void markQueenSummoned(UUID queenId) {
|
||||
this.summonerId = queenId;
|
||||
this.lostSummonerTicks = 0;
|
||||
this.setPersistenceRequired();
|
||||
}
|
||||
|
||||
public boolean isSummonedBy(UUID queenId) {
|
||||
return queenId.equals(this.summonerId);
|
||||
}
|
||||
|
||||
public boolean isQueenSummoned() {
|
||||
return this.summonerId != null;
|
||||
}
|
||||
|
||||
public static int countSummonedBy(ServerLevel level, UUID queenId) {
|
||||
int count = 0;
|
||||
for (Entity entity : level.getAllEntities()) {
|
||||
if (entity instanceof ManticoreEntity manticore && manticore.isAlive() && manticore.isSummonedBy(queenId)) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeWhenFarAway(double distanceToClosestPlayer) {
|
||||
return this.summonerId == null && super.removeWhenFarAway(distanceToClosestPlayer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresCustomPersistence() {
|
||||
return this.summonerId != null || super.requiresCustomPersistence();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
super.tick();
|
||||
if (this.level().isClientSide) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean flying = !this.onGround();
|
||||
if (flying != this.wasFlyingLastTick) {
|
||||
this.triggerAnim("controller", flying ? "fly_start" : "fly_land");
|
||||
this.wasFlyingLastTick = flying;
|
||||
}
|
||||
|
||||
if (this.takeoffCooldown > 0) {
|
||||
this.takeoffCooldown--;
|
||||
}
|
||||
if (this.stingCooldown > 0) {
|
||||
this.stingCooldown--;
|
||||
}
|
||||
|
||||
this.tickQueenSummonCleanup();
|
||||
|
||||
LivingEntity target = this.getTarget();
|
||||
if (target != null && target.isAlive()) {
|
||||
if (this.onGround() && this.takeoffCooldown <= 0 && this.distanceToSqr(target) > 6.0D) {
|
||||
this.takeoff();
|
||||
}
|
||||
if (this.tickCount % SWARM_RETARGET_INTERVAL == 0) {
|
||||
this.spreadSwarmTarget(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void tickQueenSummonCleanup() {
|
||||
if (this.summonerId == null || !(this.level() instanceof ServerLevel serverLevel)) {
|
||||
return;
|
||||
}
|
||||
Entity summoner = serverLevel.getEntity(this.summonerId);
|
||||
if (summoner instanceof QueenEntity queen && queen.isAlive()) {
|
||||
this.lostSummonerTicks = 0;
|
||||
return;
|
||||
}
|
||||
if (++this.lostSummonerTicks > LOST_SUMMONER_GRACE_TICKS) {
|
||||
this.discard();
|
||||
}
|
||||
}
|
||||
|
||||
private void takeoff() {
|
||||
this.takeoffCooldown = TAKEOFF_COOLDOWN_TICKS;
|
||||
this.setDeltaMovement(this.getDeltaMovement().add(0.0D, 0.55D, 0.0D));
|
||||
this.hasImpulse = true;
|
||||
LivingEntity target = this.getTarget();
|
||||
if (target != null) {
|
||||
Vec3 above = target.position().add(oppositeGravity(this).scale(3.0D));
|
||||
this.getMoveControl().setWantedPosition(above.x, above.y, above.z, 1.2D);
|
||||
}
|
||||
}
|
||||
|
||||
private void spreadSwarmTarget(LivingEntity currentTarget) {
|
||||
List<Player> nearbyPlayers = this.level().getEntitiesOfClass(
|
||||
Player.class,
|
||||
this.getBoundingBox().inflate(this.getAttributeValue(Attributes.FOLLOW_RANGE)),
|
||||
player -> player.isAlive() && this.canAttack(player));
|
||||
if (nearbyPlayers.size() < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<ManticoreEntity> swarm = this.level().getEntitiesOfClass(
|
||||
ManticoreEntity.class,
|
||||
this.getBoundingBox().inflate(32.0D),
|
||||
manticore -> manticore != this && manticore.isAlive());
|
||||
|
||||
Player bestPlayer = null;
|
||||
int bestLoad = Integer.MAX_VALUE;
|
||||
for (Player player : nearbyPlayers) {
|
||||
int load = 0;
|
||||
for (ManticoreEntity manticore : swarm) {
|
||||
if (manticore.getTarget() == player) {
|
||||
load++;
|
||||
}
|
||||
}
|
||||
if (load < bestLoad) {
|
||||
bestLoad = load;
|
||||
bestPlayer = player;
|
||||
}
|
||||
}
|
||||
|
||||
int currentLoad = 0;
|
||||
for (ManticoreEntity manticore : swarm) {
|
||||
if (manticore.getTarget() == currentTarget) {
|
||||
currentLoad++;
|
||||
}
|
||||
}
|
||||
if (bestPlayer != null && bestPlayer != currentTarget && currentLoad - bestLoad >= 2) {
|
||||
this.setTarget(bestPlayer);
|
||||
}
|
||||
}
|
||||
|
||||
private static Vec3 oppositeGravity(Entity entity) {
|
||||
return AntarchyGravityApi.isGravityInverted(entity) ? new Vec3(0.0D, -1.0D, 0.0D) : new Vec3(0.0D, 1.0D, 0.0D);
|
||||
}
|
||||
|
||||
void performSting(LivingEntity enemy) {
|
||||
this.swing(InteractionHand.MAIN_HAND);
|
||||
if (enemy.hurt(this.damageSources().mobAttack(this), (float) this.getAttributeValue(Attributes.ATTACK_DAMAGE))) {
|
||||
enemy.addEffect(new MobEffectInstance(MobEffects.POISON, AntarchySettings.manticoreStingPoisonTicks(), 0), this);
|
||||
}
|
||||
this.stingCooldown = STING_COOLDOWN_TICKS;
|
||||
this.triggerAnim("controller", "sting");
|
||||
}
|
||||
|
||||
boolean stingReady() {
|
||||
return this.stingCooldown <= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doHurtTarget(Entity target) {
|
||||
boolean hurt = super.doHurtTarget(target);
|
||||
if (hurt) {
|
||||
this.triggerAnim("controller", this.onGround() ? "bite" : "fly_bite");
|
||||
}
|
||||
return hurt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean causeFallDamage(float fallDistance, float multiplier, DamageSource source) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInWall() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAdditionalSaveData(CompoundTag tag) {
|
||||
super.addAdditionalSaveData(tag);
|
||||
if (this.summonerId != null) {
|
||||
tag.putUUID(SUMMONER_KEY, this.summonerId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readAdditionalSaveData(CompoundTag tag) {
|
||||
super.readAdditionalSaveData(tag);
|
||||
this.summonerId = tag.hasUUID(SUMMONER_KEY) ? tag.getUUID(SUMMONER_KEY) : null;
|
||||
this.lostSummonerTicks = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
|
||||
controllers.add(new AnimationController<>(this, "controller", 3, this::locomotionPredicate)
|
||||
.triggerableAnim("bite", BITE_ANIM)
|
||||
.triggerableAnim("fly_bite", FLY_BITE_ANIM)
|
||||
.triggerableAnim("sting", STING_ANIM)
|
||||
.triggerableAnim("fly_start", FLY_START_ANIM)
|
||||
.triggerableAnim("fly_land", FLY_LAND_ANIM));
|
||||
}
|
||||
|
||||
private PlayState locomotionPredicate(AnimationState<ManticoreEntity> state) {
|
||||
if (!this.onGround()) {
|
||||
return state.setAndContinue(FLY_ANIM);
|
||||
}
|
||||
if (state.isMoving()) {
|
||||
return state.setAndContinue(WALK_ANIM);
|
||||
}
|
||||
return state.setAndContinue(IDLE_ANIM);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnimatableInstanceCache getAnimatableInstanceCache() {
|
||||
return this.geoCache;
|
||||
}
|
||||
|
||||
private static class ManticoreAttackGoal extends MeleeAttackGoal {
|
||||
private final ManticoreEntity manticore;
|
||||
|
||||
ManticoreAttackGoal(ManticoreEntity manticore) {
|
||||
super(manticore, 1.1D, true);
|
||||
this.manticore = manticore;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void checkAndPerformAttack(LivingEntity enemy) {
|
||||
if (this.canPerformAttack(enemy) && this.isTimeToAttack()) {
|
||||
this.resetAttackCooldown();
|
||||
if (this.manticore.onGround() && this.manticore.stingReady() && this.manticore.getRandom().nextFloat() < STING_CHANCE) {
|
||||
this.manticore.performSting(enemy);
|
||||
return;
|
||||
}
|
||||
this.manticore.swing(InteractionHand.MAIN_HAND);
|
||||
this.manticore.doHurtTarget(enemy);
|
||||
return;
|
||||
}
|
||||
super.checkAndPerformAttack(enemy);
|
||||
}
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package com.craisinlord.antarchy.content.entity.royal;
|
||||
|
||||
import com.craisinlord.antarchy.config.AntarchySettings;
|
||||
import com.craisinlord.antarchy.content.AntarchySoundEvents;
|
||||
import net.minecraft.sounds.SoundEvent;
|
||||
import net.minecraft.world.BossEvent;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
|
||||
import net.minecraft.world.level.Level;
|
||||
|
||||
public class KingEntity extends RoyalBossEntity {
|
||||
public KingEntity(EntityType<? extends KingEntity> entityType, Level level) {
|
||||
super(entityType, level);
|
||||
}
|
||||
|
||||
public static AttributeSupplier.Builder createAttributes() {
|
||||
return createBaseAttributes(AntarchySettings.kingHealth(), AntarchySettings.kingAttackDamage());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String geoName() {
|
||||
return "king";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isFlyingBoss() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BossEvent.BossBarColor bossBarColor() {
|
||||
return BossEvent.BossBarColor.YELLOW;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SoundEvent royalIdleSound() {
|
||||
return AntarchySoundEvents.KING_IDLE.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SoundEvent royalHurtSound() {
|
||||
return AntarchySoundEvents.KING_HURT.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SoundEvent royalDeathSound() {
|
||||
return AntarchySoundEvents.KING_DEATH.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SoundEvent royalBiteSound() {
|
||||
return AntarchySoundEvents.KING_BITE.get();
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package com.craisinlord.antarchy.content.entity.royal;
|
||||
|
||||
import com.craisinlord.antarchy.config.AntarchySettings;
|
||||
import com.craisinlord.antarchy.content.AntarchySoundEvents;
|
||||
import net.minecraft.sounds.SoundEvent;
|
||||
import net.minecraft.world.BossEvent;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
|
||||
import net.minecraft.world.level.Level;
|
||||
|
||||
public class QueenEntity extends RoyalBossEntity {
|
||||
public QueenEntity(EntityType<? extends QueenEntity> entityType, Level level) {
|
||||
super(entityType, level);
|
||||
}
|
||||
|
||||
public static AttributeSupplier.Builder createAttributes() {
|
||||
return createBaseAttributes(AntarchySettings.queenHealth(), AntarchySettings.queenAttackDamage());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String geoName() {
|
||||
return "queen";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isFlyingBoss() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BossEvent.BossBarColor bossBarColor() {
|
||||
return BossEvent.BossBarColor.PURPLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SoundEvent royalIdleSound() {
|
||||
return AntarchySoundEvents.QUEEN_IDLE.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SoundEvent royalHurtSound() {
|
||||
return AntarchySoundEvents.QUEEN_HURT.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SoundEvent royalDeathSound() {
|
||||
return AntarchySoundEvents.QUEEN_DEATH.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SoundEvent royalBiteSound() {
|
||||
return AntarchySoundEvents.QUEEN_BITE.get();
|
||||
}
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
package com.craisinlord.antarchy.content.entity.royal;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.GameRules;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
public final class RoyalBlockDestruction {
|
||||
private RoyalBlockDestruction() {
|
||||
}
|
||||
|
||||
public static int destroySphere(
|
||||
ServerLevel level,
|
||||
Entity source,
|
||||
Vec3 center,
|
||||
double radius,
|
||||
int maxBlocks,
|
||||
double maxResistance,
|
||||
float dropChance
|
||||
) {
|
||||
if (maxBlocks <= 0 || radius <= 0.0D || !level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int minX = net.minecraft.util.Mth.floor(center.x - radius);
|
||||
int minY = net.minecraft.util.Mth.floor(center.y - radius);
|
||||
int minZ = net.minecraft.util.Mth.floor(center.z - radius);
|
||||
int maxX = net.minecraft.util.Mth.floor(center.x + radius);
|
||||
int maxY = net.minecraft.util.Mth.floor(center.y + radius);
|
||||
int maxZ = net.minecraft.util.Mth.floor(center.z + radius);
|
||||
double radiusSqr = radius * radius;
|
||||
|
||||
int destroyed = 0;
|
||||
for (BlockPos cursor : BlockPos.betweenClosed(minX, minY, minZ, maxX, maxY, maxZ)) {
|
||||
if (destroyed >= maxBlocks) {
|
||||
break;
|
||||
}
|
||||
if (cursor.distToCenterSqr(center.x, center.y, center.z) > radiusSqr
|
||||
|| cursor.getY() < level.getMinBuildHeight()
|
||||
|| cursor.getY() >= level.getMaxBuildHeight()
|
||||
|| !level.getWorldBorder().isWithinBounds(cursor)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
BlockPos pos = cursor.immutable();
|
||||
BlockState existing = level.getBlockState(pos);
|
||||
if (existing.isAir()
|
||||
|| level.getBlockEntity(pos) != null
|
||||
|| existing.getDestroySpeed(level, pos) < 0.0F
|
||||
|| existing.getBlock().getExplosionResistance() > maxResistance
|
||||
|| existing.getCollisionShape(level, pos).isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (source.getRandom().nextFloat() <= dropChance) {
|
||||
level.destroyBlock(pos, true, source);
|
||||
} else {
|
||||
level.levelEvent(2001, pos, Block.getId(existing));
|
||||
level.setBlockAndUpdate(pos, Blocks.AIR.defaultBlockState());
|
||||
}
|
||||
destroyed++;
|
||||
}
|
||||
return destroyed;
|
||||
}
|
||||
}
|
||||
+568
@@ -0,0 +1,568 @@
|
||||
package com.craisinlord.antarchy.content.entity.royal;
|
||||
|
||||
import com.craisinlord.antarchy.config.AntarchySettings;
|
||||
import com.craisinlord.antarchy.content.entity.multipart.MultipartEntityOwner;
|
||||
import com.craisinlord.antarchy.content.entity.multipart.MultipartLayout;
|
||||
import com.craisinlord.antarchy.content.entity.multipart.MultipartPartDefinition;
|
||||
import com.craisinlord.antarchy.content.entity.royal.beam.RoyalBeamController;
|
||||
import java.util.List;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.syncher.EntityDataAccessor;
|
||||
import net.minecraft.network.syncher.EntityDataSerializers;
|
||||
import net.minecraft.network.syncher.SynchedEntityData;
|
||||
import net.minecraft.server.level.ServerBossEvent;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.sounds.SoundEvent;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.BossEvent;
|
||||
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.Mob;
|
||||
import net.minecraft.world.entity.MoverType;
|
||||
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
|
||||
import net.minecraft.world.entity.ai.attributes.Attributes;
|
||||
import net.minecraft.world.entity.ai.control.FlyingMoveControl;
|
||||
import net.minecraft.world.entity.ai.goal.FloatGoal;
|
||||
import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal;
|
||||
import net.minecraft.world.entity.ai.goal.RandomLookAroundGoal;
|
||||
import net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal;
|
||||
import net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal;
|
||||
import net.minecraft.world.entity.ai.navigation.FlyingPathNavigation;
|
||||
import net.minecraft.world.entity.ai.navigation.PathNavigation;
|
||||
import net.minecraft.world.entity.monster.Monster;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
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 abstract class RoyalBossEntity extends Monster implements GeoEntity, MultipartEntityOwner {
|
||||
public static final float MODEL_RENDER_SCALE = 2.0F;
|
||||
public static final float GAMEPLAY_WIDTH = 30.0F;
|
||||
public static final float GAMEPLAY_HEIGHT = 30.0F;
|
||||
|
||||
public enum Phase {
|
||||
ONE(1, 1.00F),
|
||||
TWO(2, 0.72F),
|
||||
THREE(3, 0.48F);
|
||||
|
||||
private final int maxConcurrentHeadAttacks;
|
||||
private final float cooldownScale;
|
||||
|
||||
Phase(int maxConcurrentHeadAttacks, float cooldownScale) {
|
||||
this.maxConcurrentHeadAttacks = maxConcurrentHeadAttacks;
|
||||
this.cooldownScale = cooldownScale;
|
||||
}
|
||||
|
||||
public int maxConcurrentHeadAttacks() {
|
||||
return this.maxConcurrentHeadAttacks;
|
||||
}
|
||||
|
||||
public float cooldownScale() {
|
||||
return this.cooldownScale;
|
||||
}
|
||||
}
|
||||
|
||||
private static final float PHASE_TWO_THRESHOLD = 0.70F;
|
||||
private static final float PHASE_THREE_THRESHOLD = 0.35F;
|
||||
|
||||
private static final int BITE_DURATION_TICKS = 16;
|
||||
private static final int BITE_HIT_TICK = 6;
|
||||
private static final int DEATH_TICKS = 60;
|
||||
private static final double CRUSH_RADIUS = 3.5D;
|
||||
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 EntityDataAccessor<Boolean> FIRING =
|
||||
SynchedEntityData.defineId(RoyalBossEntity.class, EntityDataSerializers.BOOLEAN);
|
||||
private static final EntityDataAccessor<Boolean> HAS_BEAM_END =
|
||||
SynchedEntityData.defineId(RoyalBossEntity.class, EntityDataSerializers.BOOLEAN);
|
||||
private static final EntityDataAccessor<Float> BEAM_END_X =
|
||||
SynchedEntityData.defineId(RoyalBossEntity.class, EntityDataSerializers.FLOAT);
|
||||
private static final EntityDataAccessor<Float> BEAM_END_Y =
|
||||
SynchedEntityData.defineId(RoyalBossEntity.class, EntityDataSerializers.FLOAT);
|
||||
private static final EntityDataAccessor<Float> BEAM_END_Z =
|
||||
SynchedEntityData.defineId(RoyalBossEntity.class, EntityDataSerializers.FLOAT);
|
||||
|
||||
private static final RawAnimation IDLE_ANIM = RawAnimation.begin().thenLoop("idle");
|
||||
private static final RawAnimation WALK_ANIM = RawAnimation.begin().thenLoop("walk");
|
||||
private static final RawAnimation FLY_ANIM = RawAnimation.begin().thenLoop("fly");
|
||||
private static final RawAnimation DEATH_ANIM = RawAnimation.begin().thenPlayAndHold("death");
|
||||
|
||||
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
|
||||
protected final RoyalBeamController beamController = new RoyalBeamController(this);
|
||||
private final ServerBossEvent bossEvent;
|
||||
private final RoyalHead[] heads = {
|
||||
new RoyalHead(RoyalHead.Slot.LEFT),
|
||||
new RoyalHead(RoyalHead.Slot.CENTER),
|
||||
new RoyalHead(RoyalHead.Slot.RIGHT)
|
||||
};
|
||||
|
||||
@Nullable
|
||||
private Entity[] multipartParts;
|
||||
|
||||
protected RoyalBossEntity(EntityType<? extends RoyalBossEntity> entityType, Level level) {
|
||||
super(entityType, level);
|
||||
this.xpReward = 500;
|
||||
if (this.isFlyingBoss()) {
|
||||
this.moveControl = new FlyingMoveControl(this, 20, true);
|
||||
}
|
||||
this.bossEvent = new com.craisinlord.antarchy.content.boss.EntityLinkedServerBossEvent(
|
||||
this.getUUID(),
|
||||
Component.translatable(entityType.getDescriptionId()),
|
||||
this.bossBarColor(),
|
||||
BossEvent.BossBarOverlay.PROGRESS);
|
||||
}
|
||||
|
||||
protected abstract BossEvent.BossBarColor bossBarColor();
|
||||
|
||||
protected abstract SoundEvent royalIdleSound();
|
||||
|
||||
protected abstract SoundEvent royalHurtSound();
|
||||
|
||||
protected abstract SoundEvent royalDeathSound();
|
||||
|
||||
protected abstract SoundEvent royalBiteSound();
|
||||
|
||||
public static AttributeSupplier.Builder createBaseAttributes(double health, double attackDamage) {
|
||||
return Mob.createMobAttributes()
|
||||
.add(Attributes.MAX_HEALTH, health)
|
||||
.add(Attributes.ATTACK_DAMAGE, attackDamage)
|
||||
.add(Attributes.ARMOR, AntarchySettings.royalBossArmor())
|
||||
.add(Attributes.FOLLOW_RANGE, AntarchySettings.royalBossFollowRange())
|
||||
.add(Attributes.MOVEMENT_SPEED, AntarchySettings.royalBossMovementSpeed())
|
||||
.add(Attributes.FLYING_SPEED, AntarchySettings.royalBossMovementSpeed())
|
||||
.add(Attributes.KNOCKBACK_RESISTANCE, AntarchySettings.royalBossKnockbackResistance())
|
||||
.add(Attributes.STEP_HEIGHT, AntarchySettings.royalBossStepHeight());
|
||||
}
|
||||
|
||||
protected abstract String geoName();
|
||||
|
||||
protected abstract boolean isFlyingBoss();
|
||||
|
||||
public String geoNameForRender() {
|
||||
return this.geoName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MultipartLayout antarchy$getMultipartLayout() {
|
||||
return RoyalBossMultipartLayout.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Entity[] antarchy$getMultipartParts() {
|
||||
return this.multipartParts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void antarchy$setMultipartParts(@Nullable Entity[] parts) {
|
||||
this.multipartParts = parts;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PathNavigation createNavigation(Level level) {
|
||||
if (this.isFlyingBoss()) {
|
||||
FlyingPathNavigation navigation = new FlyingPathNavigation(this, level);
|
||||
navigation.setCanOpenDoors(false);
|
||||
navigation.setCanFloat(true);
|
||||
return navigation;
|
||||
}
|
||||
return super.createNavigation(level);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void registerGoals() {
|
||||
this.goalSelector.addGoal(0, new FloatGoal(this));
|
||||
this.goalSelector.addGoal(7, new LookAtPlayerGoal(this, Player.class, 48.0F));
|
||||
this.goalSelector.addGoal(8, new RandomLookAroundGoal(this));
|
||||
this.targetSelector.addGoal(1, new HurtByTargetGoal(this));
|
||||
this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void defineSynchedData(SynchedEntityData.Builder builder) {
|
||||
super.defineSynchedData(builder);
|
||||
builder.define(FIRING, false);
|
||||
builder.define(HAS_BEAM_END, false);
|
||||
builder.define(BEAM_END_X, 0.0F);
|
||||
builder.define(BEAM_END_Y, 0.0F);
|
||||
builder.define(BEAM_END_Z, 0.0F);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeWhenFarAway(double distanceToClosestPlayer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkDespawn() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SoundEvent getAmbientSound() {
|
||||
return this.royalIdleSound();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SoundEvent getHurtSound(DamageSource damageSource) {
|
||||
return this.royalHurtSound();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SoundEvent getDeathSound() {
|
||||
return this.royalDeathSound();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected float getSoundVolume() {
|
||||
return 2.5F;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void travel(Vec3 travelVector) {
|
||||
if (this.isFlyingBoss() && this.isEffectiveAi()) {
|
||||
this.moveRelative(this.getSpeed(), travelVector);
|
||||
this.move(MoverType.SELF, this.getDeltaMovement());
|
||||
this.setDeltaMovement(this.getDeltaMovement().scale(0.91D));
|
||||
return;
|
||||
}
|
||||
super.travel(travelVector);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
super.tick();
|
||||
if (this.isFlyingBoss()) {
|
||||
this.setNoGravity(true);
|
||||
}
|
||||
if (this.level().isClientSide) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.bossEvent.setProgress(this.getHealth() / this.getMaxHealth());
|
||||
|
||||
for (RoyalHead head : this.heads) {
|
||||
head.tick();
|
||||
}
|
||||
|
||||
if (this.isDeadOrDying()) {
|
||||
return;
|
||||
}
|
||||
|
||||
LivingEntity primaryTarget = this.getTarget();
|
||||
if (primaryTarget == null || !primaryTarget.isAlive() || !this.canAttack(primaryTarget)) {
|
||||
for (RoyalHead head : this.heads) {
|
||||
head.setTarget(null);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.getLookControl().setLookAt(primaryTarget, 30.0F, 30.0F);
|
||||
this.assignHeadTargets(primaryTarget);
|
||||
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()) {
|
||||
activeHeadAttacks++;
|
||||
}
|
||||
}
|
||||
|
||||
for (RoyalHead head : this.heads) {
|
||||
if (activeHeadAttacks >= phase.maxConcurrentHeadAttacks()) {
|
||||
break;
|
||||
}
|
||||
if (!head.readyToAttack()) {
|
||||
continue;
|
||||
}
|
||||
LivingEntity headTarget = head.target(this.level());
|
||||
if (headTarget != null && this.headWithinBiteReach(head, headTarget)) {
|
||||
this.startBite(head, phase);
|
||||
activeHeadAttacks++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Phase phase() {
|
||||
float fraction = this.getMaxHealth() <= 0.0F ? 1.0F : this.getHealth() / this.getMaxHealth();
|
||||
if (fraction > PHASE_TWO_THRESHOLD) {
|
||||
return Phase.ONE;
|
||||
}
|
||||
if (fraction > PHASE_THREE_THRESHOLD) {
|
||||
return Phase.TWO;
|
||||
}
|
||||
return Phase.THREE;
|
||||
}
|
||||
|
||||
private void assignHeadTargets(LivingEntity fallback) {
|
||||
List<Player> candidates = this.level().getEntitiesOfClass(
|
||||
Player.class,
|
||||
this.getBoundingBox().inflate(AntarchySettings.royalBossFollowRange()),
|
||||
player -> player.isAlive() && this.canAttack(player));
|
||||
if (candidates.isEmpty()) {
|
||||
for (RoyalHead head : this.heads) {
|
||||
head.setTarget(fallback);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
int[] assignedCounts = new int[candidates.size()];
|
||||
for (RoyalHead head : this.heads) {
|
||||
Vec3 anchor = this.headAnchor(head);
|
||||
int bestIndex = 0;
|
||||
int bestCount = Integer.MAX_VALUE;
|
||||
double bestDistance = Double.MAX_VALUE;
|
||||
for (int i = 0; i < candidates.size(); i++) {
|
||||
double distance = candidates.get(i).position().distanceToSqr(anchor);
|
||||
if (assignedCounts[i] < bestCount || (assignedCounts[i] == bestCount && distance < bestDistance)) {
|
||||
bestIndex = i;
|
||||
bestCount = assignedCounts[i];
|
||||
bestDistance = distance;
|
||||
}
|
||||
}
|
||||
assignedCounts[bestIndex]++;
|
||||
head.setTarget(candidates.get(bestIndex));
|
||||
}
|
||||
}
|
||||
|
||||
private void steerTowardTarget(LivingEntity target) {
|
||||
double reach = this.getBbWidth() * 0.5D + this.biteReach() * 0.5D;
|
||||
double dx = target.getX() - this.getX();
|
||||
double dz = target.getZ() - this.getZ();
|
||||
if ((dx * dx + dz * dz) > reach * reach) {
|
||||
double wantedY = this.isFlyingBoss() ? target.getY() + 3.0D : target.getY();
|
||||
this.getMoveControl().setWantedPosition(target.getX(), wantedY, target.getZ(), this.isFlyingBoss() ? 1.1D : 1.0D);
|
||||
} else {
|
||||
this.getNavigation().stop();
|
||||
}
|
||||
}
|
||||
|
||||
private void tickBodyCrush() {
|
||||
if (!(this.level() instanceof ServerLevel serverLevel) || this.tickCount % 10 != 0) {
|
||||
return;
|
||||
}
|
||||
if (this.getDeltaMovement().horizontalDistanceSqr() < 0.0016D) {
|
||||
return;
|
||||
}
|
||||
double yaw = this.yBodyRot * Mth.DEG_TO_RAD;
|
||||
Vec3 forward = new Vec3(-Math.sin(yaw), 0.0D, Math.cos(yaw));
|
||||
Vec3 center = new Vec3(
|
||||
this.getX() + forward.x * (this.getBbWidth() * 0.45D),
|
||||
this.getY() + (this.isFlyingBoss() ? 0.0D : 1.0D),
|
||||
this.getZ() + forward.z * (this.getBbWidth() * 0.45D));
|
||||
RoyalBlockDestruction.destroySphere(serverLevel, this, center, CRUSH_RADIUS, CRUSH_MAX_BLOCKS, CRUSH_MAX_RESISTANCE, CRUSH_DROP_CHANCE);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
private void applyBiteHit(RoyalHead head) {
|
||||
if (this.level().isClientSide) {
|
||||
return;
|
||||
}
|
||||
Vec3 anchor = this.headAnchor(head);
|
||||
double reach = this.biteReach();
|
||||
float damage = (float) (this.getAttributeValue(Attributes.ATTACK_DAMAGE) * AntarchySettings.royalBossBiteDamageMultiplier());
|
||||
DamageSource damageSource = this.damageSources().mobAttack(this);
|
||||
AABB box = new AABB(anchor, anchor).inflate(reach);
|
||||
for (LivingEntity living : this.level().getEntitiesOfClass(LivingEntity.class, box, entity -> entity.isAlive() && entity != this)) {
|
||||
if (living instanceof RoyalBossEntity || living.getType() == this.getType()) {
|
||||
continue;
|
||||
}
|
||||
if (living.position().distanceToSqr(anchor) > reach * reach) {
|
||||
continue;
|
||||
}
|
||||
if (living.hurt(damageSource, damage)) {
|
||||
Vec3 push = living.position().subtract(anchor).multiply(1.0D, 0.0D, 1.0D);
|
||||
if (push.lengthSqr() < 1.0E-4D) {
|
||||
push = this.getViewVector(1.0F).multiply(1.0D, 0.0D, 1.0D);
|
||||
}
|
||||
push = push.normalize().scale(1.4D);
|
||||
living.push(push.x, 0.42D, push.z);
|
||||
living.hurtMarked = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean headWithinBiteReach(RoyalHead head, LivingEntity target) {
|
||||
Vec3 anchor = this.headAnchor(head);
|
||||
double reach = this.biteReach() + target.getBbWidth();
|
||||
return target.position().distanceToSqr(anchor) <= reach * reach;
|
||||
}
|
||||
|
||||
private double biteReach() {
|
||||
return AntarchySettings.royalBossBiteReach();
|
||||
}
|
||||
|
||||
private Vec3 headAnchor(RoyalHead head) {
|
||||
int partIndex = head.slot().partIndex();
|
||||
Entity[] parts = this.multipartParts;
|
||||
if (parts != null && partIndex < parts.length && parts[partIndex] != null) {
|
||||
Entity part = parts[partIndex];
|
||||
return part.position().add(0.0D, part.getBbHeight() * 0.5D, 0.0D);
|
||||
}
|
||||
|
||||
MultipartPartDefinition spec = RoyalBossMultipartLayout.INSTANCE.parts()[partIndex];
|
||||
double yaw = this.yBodyRot * Mth.DEG_TO_RAD;
|
||||
Vec3 forward = new Vec3(-Math.sin(yaw), 0.0D, Math.cos(yaw));
|
||||
Vec3 right = new Vec3(forward.z, 0.0D, -forward.x);
|
||||
return new Vec3(
|
||||
this.getX() + forward.x * spec.forwardOffset() + right.x * spec.lateralOffset(),
|
||||
this.getY() + spec.yOffset(),
|
||||
this.getZ() + forward.z * spec.forwardOffset() + right.z * spec.lateralOffset());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void die(DamageSource damageSource) {
|
||||
if (!this.level().isClientSide) {
|
||||
for (RoyalHead head : this.heads) {
|
||||
head.reset();
|
||||
}
|
||||
this.beamController.stop();
|
||||
this.setFiringRoyalBeam(false);
|
||||
this.setRoyalBeamEndPosition(null);
|
||||
this.getNavigation().stop();
|
||||
this.setDeltaMovement(Vec3.ZERO);
|
||||
}
|
||||
super.die(damageSource);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void tickDeath() {
|
||||
this.deathTime++;
|
||||
if (this.deathTime == 20 && !this.level().isClientSide()) {
|
||||
this.level().broadcastEntityEvent(this, (byte) 60);
|
||||
}
|
||||
if (this.deathTime >= DEATH_TICKS) {
|
||||
this.remove(RemovalReason.KILLED);
|
||||
this.dropExperience(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startSeenByPlayer(ServerPlayer player) {
|
||||
super.startSeenByPlayer(player);
|
||||
this.bossEvent.addPlayer(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopSeenByPlayer(ServerPlayer player) {
|
||||
super.stopSeenByPlayer(player);
|
||||
this.bossEvent.removePlayer(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hurt(DamageSource source, float amount) {
|
||||
float cappedAmount = (float) Math.min(amount, AntarchySettings.royalBossMaxSingleHitDamage());
|
||||
return super.hurt(source, cappedAmount);
|
||||
}
|
||||
|
||||
public boolean isFiringRoyalBeam() {
|
||||
return this.entityData.get(FIRING);
|
||||
}
|
||||
|
||||
public void setFiringRoyalBeam(boolean firing) {
|
||||
this.entityData.set(FIRING, firing);
|
||||
}
|
||||
|
||||
public RoyalBeamController beamController() {
|
||||
return this.beamController;
|
||||
}
|
||||
|
||||
public void setRoyalBeamEndPosition(@Nullable Vec3 position) {
|
||||
this.entityData.set(HAS_BEAM_END, position != null);
|
||||
if (position != null) {
|
||||
this.entityData.set(BEAM_END_X, (float) position.x);
|
||||
this.entityData.set(BEAM_END_Y, (float) position.y);
|
||||
this.entityData.set(BEAM_END_Z, (float) position.z);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Vec3 getRoyalBeamEndPosition() {
|
||||
if (!this.entityData.get(HAS_BEAM_END)) {
|
||||
return null;
|
||||
}
|
||||
return new Vec3(this.entityData.get(BEAM_END_X), this.entityData.get(BEAM_END_Y), this.entityData.get(BEAM_END_Z));
|
||||
}
|
||||
|
||||
public Vec3 getRoyalBeamShootFrom(float partialTicks) {
|
||||
double x = Mth.lerp(partialTicks, this.xo, this.getX());
|
||||
double y = Mth.lerp(partialTicks, this.yo, this.getY()) + this.getBbHeight() * 0.72D;
|
||||
double z = Mth.lerp(partialTicks, this.zo, this.getZ());
|
||||
return new Vec3(x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
|
||||
controllers.add(new AnimationController<>(this, "locomotion", 4, this::locomotionPredicate));
|
||||
controllers.add(new AnimationController<>(this, "head_left", 0, RoyalBossEntity::headIdle)
|
||||
.triggerableAnim("bite", RawAnimation.begin().thenPlay("bite_1"))
|
||||
.triggerableAnim("shoot", RawAnimation.begin().thenLoop("shoot_1")));
|
||||
controllers.add(new AnimationController<>(this, "head_center", 0, RoyalBossEntity::headIdle)
|
||||
.triggerableAnim("bite", RawAnimation.begin().thenPlay("bite_2"))
|
||||
.triggerableAnim("shoot", RawAnimation.begin().thenLoop("shoot_2")));
|
||||
controllers.add(new AnimationController<>(this, "head_right", 0, RoyalBossEntity::headIdle)
|
||||
.triggerableAnim("bite", RawAnimation.begin().thenPlay("bite_3"))
|
||||
.triggerableAnim("shoot", RawAnimation.begin().thenLoop("shoot_3")));
|
||||
controllers.add(new AnimationController<>(this, "body_action", 0, this::bodyActionPredicate)
|
||||
.triggerableAnim("stomp", RawAnimation.begin().thenPlay("stomp"))
|
||||
.triggerableAnim("wing_gust", RawAnimation.begin().thenPlay("wing_gust"))
|
||||
.triggerableAnim("minion_spawn", RawAnimation.begin().thenPlay("minion_spawn")));
|
||||
}
|
||||
|
||||
private PlayState locomotionPredicate(AnimationState<RoyalBossEntity> state) {
|
||||
if (this.isDeadOrDying()) {
|
||||
return PlayState.STOP;
|
||||
}
|
||||
if (this.isFlyingBoss()) {
|
||||
state.setAndContinue(FLY_ANIM);
|
||||
} else if (state.isMoving()) {
|
||||
state.setAndContinue(WALK_ANIM);
|
||||
} else {
|
||||
state.setAndContinue(IDLE_ANIM);
|
||||
}
|
||||
return PlayState.CONTINUE;
|
||||
}
|
||||
|
||||
private PlayState bodyActionPredicate(AnimationState<RoyalBossEntity> state) {
|
||||
if (this.isDeadOrDying()) {
|
||||
return state.setAndContinue(DEATH_ANIM);
|
||||
}
|
||||
return PlayState.STOP;
|
||||
}
|
||||
|
||||
private static PlayState headIdle(AnimationState<RoyalBossEntity> state) {
|
||||
return PlayState.STOP;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnimatableInstanceCache getAnimatableInstanceCache() {
|
||||
return this.geoCache;
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package com.craisinlord.antarchy.content.entity.royal;
|
||||
|
||||
import com.craisinlord.antarchy.content.entity.multipart.MultipartLayout;
|
||||
import com.craisinlord.antarchy.content.entity.multipart.MultipartPartDefinition;
|
||||
|
||||
public final class RoyalBossMultipartLayout implements MultipartLayout {
|
||||
public static final RoyalBossMultipartLayout INSTANCE = new RoyalBossMultipartLayout();
|
||||
|
||||
public static final MultipartPartDefinition BODY = new MultipartPartDefinition(
|
||||
"body", 20.0F, 24.0F, 1.00F, 0.0D, 3.0D, 0.0D, true);
|
||||
public static final MultipartPartDefinition LEFT_HEAD = new MultipartPartDefinition(
|
||||
"left_head", 8.0F, 8.0F, 1.30F, 12.0D, 20.0D, -7.0D, true);
|
||||
public static final MultipartPartDefinition CENTER_HEAD = new MultipartPartDefinition(
|
||||
"center_head", 9.0F, 9.0F, 1.20F, 13.0D, 22.0D, 0.0D, true);
|
||||
public static final MultipartPartDefinition RIGHT_HEAD = new MultipartPartDefinition(
|
||||
"right_head", 8.0F, 8.0F, 1.30F, 12.0D, 20.0D, 7.0D, true);
|
||||
public static final MultipartPartDefinition LEFT_WING = new MultipartPartDefinition(
|
||||
"left_wing", 14.0F, 16.0F, 0.85F, -2.0D, 14.0D, -13.0D, true);
|
||||
public static final MultipartPartDefinition RIGHT_WING = new MultipartPartDefinition(
|
||||
"right_wing", 14.0F, 16.0F, 0.85F, -2.0D, 14.0D, 13.0D, true);
|
||||
|
||||
private static final MultipartPartDefinition[] PARTS = {
|
||||
BODY,
|
||||
LEFT_HEAD,
|
||||
CENTER_HEAD,
|
||||
RIGHT_HEAD,
|
||||
LEFT_WING,
|
||||
RIGHT_WING
|
||||
};
|
||||
|
||||
private RoyalBossMultipartLayout() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public MultipartPartDefinition[] parts() {
|
||||
return PARTS;
|
||||
}
|
||||
}
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
package com.craisinlord.antarchy.content.entity.royal;
|
||||
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.level.Level;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public final class RoyalHead {
|
||||
public enum Slot {
|
||||
LEFT(1, "head_left", "bite_1", "shoot_1"),
|
||||
CENTER(2, "head_center", "bite_2", "shoot_2"),
|
||||
RIGHT(3, "head_right", "bite_3", "shoot_3");
|
||||
|
||||
private final int partIndex;
|
||||
private final String controllerName;
|
||||
private final String biteAnimation;
|
||||
private final String shootAnimation;
|
||||
|
||||
Slot(int partIndex, String controllerName, String biteAnimation, String shootAnimation) {
|
||||
this.partIndex = partIndex;
|
||||
this.controllerName = controllerName;
|
||||
this.biteAnimation = biteAnimation;
|
||||
this.shootAnimation = shootAnimation;
|
||||
}
|
||||
|
||||
public int partIndex() {
|
||||
return this.partIndex;
|
||||
}
|
||||
|
||||
public String controllerName() {
|
||||
return this.controllerName;
|
||||
}
|
||||
|
||||
public String biteAnimation() {
|
||||
return this.biteAnimation;
|
||||
}
|
||||
|
||||
public String shootAnimation() {
|
||||
return this.shootAnimation;
|
||||
}
|
||||
}
|
||||
|
||||
private final Slot slot;
|
||||
private int targetId = -1;
|
||||
private int attackCooldown;
|
||||
private int biteTicks;
|
||||
private int biteHitTick = -1;
|
||||
private int shootTicks;
|
||||
private boolean beamActive;
|
||||
|
||||
public RoyalHead(Slot slot) {
|
||||
this.slot = slot;
|
||||
}
|
||||
|
||||
public Slot slot() {
|
||||
return this.slot;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public LivingEntity target(Level level) {
|
||||
if (this.targetId < 0) {
|
||||
return null;
|
||||
}
|
||||
return level.getEntity(this.targetId) instanceof LivingEntity living && living.isAlive() ? living : null;
|
||||
}
|
||||
|
||||
public void setTarget(@Nullable LivingEntity target) {
|
||||
this.targetId = target == null ? -1 : target.getId();
|
||||
}
|
||||
|
||||
public boolean busy() {
|
||||
return this.biteTicks > 0 || this.shootTicks > 0 || this.beamActive;
|
||||
}
|
||||
|
||||
public boolean readyToAttack() {
|
||||
return !this.busy() && this.attackCooldown <= 0;
|
||||
}
|
||||
|
||||
public void startBite(int durationTicks, int hitTick, int cooldownTicks) {
|
||||
this.biteTicks = durationTicks;
|
||||
this.biteHitTick = hitTick;
|
||||
this.attackCooldown = cooldownTicks;
|
||||
}
|
||||
|
||||
public void startShoot(int durationTicks, int cooldownTicks) {
|
||||
this.shootTicks = durationTicks;
|
||||
this.attackCooldown = cooldownTicks;
|
||||
}
|
||||
|
||||
public void setBeamActive(boolean beamActive) {
|
||||
this.beamActive = beamActive;
|
||||
}
|
||||
|
||||
public boolean beamActive() {
|
||||
return this.beamActive;
|
||||
}
|
||||
|
||||
public boolean tickBiteHit() {
|
||||
if (this.biteTicks <= 0) {
|
||||
return false;
|
||||
}
|
||||
this.biteTicks--;
|
||||
boolean hit = this.biteTicks == this.biteHitTick;
|
||||
if (hit) {
|
||||
this.biteHitTick = -1;
|
||||
}
|
||||
return hit;
|
||||
}
|
||||
|
||||
public void tick() {
|
||||
if (this.attackCooldown > 0) {
|
||||
this.attackCooldown--;
|
||||
}
|
||||
if (this.shootTicks > 0) {
|
||||
this.shootTicks--;
|
||||
}
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
this.targetId = -1;
|
||||
this.attackCooldown = 0;
|
||||
this.biteTicks = 0;
|
||||
this.biteHitTick = -1;
|
||||
this.shootTicks = 0;
|
||||
this.beamActive = false;
|
||||
}
|
||||
}
|
||||
+295
@@ -0,0 +1,295 @@
|
||||
package com.craisinlord.antarchy.content.entity.royal.beam;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.damagesource.DamageSource;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.EntitySelector;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.Mob;
|
||||
import net.minecraft.world.entity.ai.attributes.Attributes;
|
||||
import net.minecraft.world.level.ClipContext;
|
||||
import net.minecraft.world.level.GameRules;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
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.HitResult;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public final class RoyalBeamController {
|
||||
private final Mob owner;
|
||||
private Vec3 serverTarget;
|
||||
private Vec3 beamEndPosition;
|
||||
private int beamTicks;
|
||||
private int terrainMutationsThisTick;
|
||||
|
||||
public RoyalBeamController(Mob owner) {
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
public boolean isFiring() {
|
||||
return this.beamTicks > 0;
|
||||
}
|
||||
|
||||
public int beamTicks() {
|
||||
return this.beamTicks;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Vec3 beamEndPosition() {
|
||||
return this.beamEndPosition;
|
||||
}
|
||||
|
||||
public void start(Vec3 shootFrom, Vec3 target, int durationTicks) {
|
||||
this.serverTarget = target;
|
||||
this.beamEndPosition = target;
|
||||
this.beamTicks = Math.max(1, durationTicks);
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
this.serverTarget = null;
|
||||
this.beamEndPosition = null;
|
||||
this.beamTicks = 0;
|
||||
}
|
||||
|
||||
public void tick(
|
||||
Vec3 shootFrom,
|
||||
@Nullable LivingEntity target,
|
||||
RoyalBeamSettings settings,
|
||||
RoyalBeamTerrainMode terrainMode,
|
||||
Consumer<@Nullable Vec3> syncedEndPosition
|
||||
) {
|
||||
if (this.beamTicks <= 0) {
|
||||
syncedEndPosition.accept(null);
|
||||
return;
|
||||
}
|
||||
|
||||
this.beamTicks--;
|
||||
this.terrainMutationsThisTick = 0;
|
||||
if (target != null && target.isAlive()) {
|
||||
Vec3 targetEye = target.getEyePosition();
|
||||
this.serverTarget = this.serverTarget == null
|
||||
? targetEye
|
||||
: this.serverTarget.add(targetEye.subtract(this.serverTarget).scale(settings.targetTracking()));
|
||||
}
|
||||
if (this.serverTarget == null) {
|
||||
this.stop();
|
||||
syncedEndPosition.accept(null);
|
||||
return;
|
||||
}
|
||||
|
||||
Vec3 direction = this.serverTarget.subtract(shootFrom).normalize();
|
||||
if (direction.lengthSqr() < 1.0E-7D) {
|
||||
direction = this.owner.getLookAngle();
|
||||
}
|
||||
Vec3 clipEnd = shootFrom.add(direction.scale(settings.range()));
|
||||
HitResult hit = this.owner.level().clip(new ClipContext(
|
||||
shootFrom,
|
||||
clipEnd,
|
||||
ClipContext.Block.COLLIDER,
|
||||
ClipContext.Fluid.NONE,
|
||||
this.owner
|
||||
));
|
||||
this.beamEndPosition = hit.getLocation();
|
||||
syncedEndPosition.accept(this.beamEndPosition);
|
||||
|
||||
if (!this.owner.level().isClientSide && this.beamEndPosition != null) {
|
||||
marchDamageAndTerrain(shootFrom, this.beamEndPosition, settings, terrainMode);
|
||||
}
|
||||
|
||||
if (this.beamTicks <= 0) {
|
||||
this.stop();
|
||||
syncedEndPosition.accept(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void marchDamageAndTerrain(
|
||||
Vec3 shootFrom,
|
||||
Vec3 beamEnd,
|
||||
RoyalBeamSettings settings,
|
||||
RoyalBeamTerrainMode terrainMode
|
||||
) {
|
||||
Vec3 direction = beamEnd.subtract(shootFrom).normalize();
|
||||
double distance = shootFrom.distanceTo(beamEnd);
|
||||
DamageSource damageSource = this.owner.damageSources().mobAttack(this.owner);
|
||||
for (double walked = settings.pathStep(); walked < Math.min(distance, settings.range()); walked += settings.pathStep()) {
|
||||
Vec3 sample = shootFrom.add(direction.scale(walked));
|
||||
hurtEntitiesAround(sample, settings.pathDamageRadius(), settings.damage(), settings.knockback(), damageSource, settings.requireLineOfSightForDamage());
|
||||
if (shouldMutateTerrain(settings, walked)) {
|
||||
mutateTerrainAround(sample, settings.pathTerrainRadius(), settings, terrainMode);
|
||||
}
|
||||
}
|
||||
|
||||
hurtEntitiesAround(beamEnd, settings.impactDamageRadius(), settings.damage(), settings.knockback(), damageSource, settings.requireLineOfSightForDamage());
|
||||
if (shouldMutateTerrain(settings, shootFrom.distanceTo(beamEnd))) {
|
||||
mutateTerrainAround(beamEnd, settings.impactTerrainRadius(), settings, terrainMode);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldMutateTerrain(RoyalBeamSettings settings, double distanceFromOwner) {
|
||||
return settings.terrainEnabled()
|
||||
&& settings.maxTerrainMutationsPerTick() > 0
|
||||
&& this.beamTicks % settings.terrainIntervalTicks() == 0
|
||||
&& distanceFromOwner <= settings.maxTerrainDistance()
|
||||
&& this.owner.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING);
|
||||
}
|
||||
|
||||
private boolean hurtEntitiesAround(
|
||||
Vec3 center,
|
||||
float radius,
|
||||
float damageAmount,
|
||||
float knockbackAmount,
|
||||
DamageSource damageSource,
|
||||
boolean requireLineOfSight
|
||||
) {
|
||||
if (radius <= 0.0F || damageAmount <= 0.0F) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AABB aabb = new AABB(center.subtract(radius, radius, radius), center.add(radius, radius, radius));
|
||||
List<LivingEntity> entities = this.owner.level().getEntitiesOfClass(
|
||||
LivingEntity.class,
|
||||
aabb,
|
||||
EntitySelector.NO_CREATIVE_OR_SPECTATOR
|
||||
);
|
||||
boolean hurtAny = false;
|
||||
for (LivingEntity living : entities) {
|
||||
if (living.is(this.owner)
|
||||
|| this.owner.isAlliedTo(living)
|
||||
|| living.getType() == this.owner.getType()
|
||||
|| living.distanceToSqr(center) > radius * radius
|
||||
|| requireLineOfSight && !canEntityBeHurtFrom(center, living)) {
|
||||
continue;
|
||||
}
|
||||
if (living.hurt(damageSource, damageAmount)) {
|
||||
hurtAny = true;
|
||||
knockbackTarget(living, knockbackAmount);
|
||||
}
|
||||
}
|
||||
return hurtAny;
|
||||
}
|
||||
|
||||
private boolean canEntityBeHurtFrom(Vec3 center, LivingEntity living) {
|
||||
Vec3 targetCenter = living.position().add(0.0D, living.getBbHeight() * 0.5D, 0.0D);
|
||||
return this.owner.level().clip(new ClipContext(
|
||||
center,
|
||||
targetCenter,
|
||||
ClipContext.Block.COLLIDER,
|
||||
ClipContext.Fluid.NONE,
|
||||
this.owner
|
||||
)).getType() == HitResult.Type.MISS;
|
||||
}
|
||||
|
||||
private void knockbackTarget(Entity target, double strength) {
|
||||
if (strength <= 0.0D) {
|
||||
return;
|
||||
}
|
||||
|
||||
double resistance = target instanceof LivingEntity living
|
||||
? living.getAttributeValue(Attributes.KNOCKBACK_RESISTANCE)
|
||||
: 0.0D;
|
||||
double appliedStrength = strength * Math.max(0.0D, 1.0D - resistance);
|
||||
if (appliedStrength <= 0.0D) {
|
||||
return;
|
||||
}
|
||||
|
||||
Vec3 push = target.position().subtract(this.owner.position()).multiply(1.0D, 0.0D, 1.0D).normalize().scale(appliedStrength);
|
||||
Vec3 movement = target.getDeltaMovement();
|
||||
target.setDeltaMovement(
|
||||
movement.x * 0.5D + push.x,
|
||||
Math.min(0.4D, movement.y * 0.5D + appliedStrength),
|
||||
movement.z * 0.5D + push.z
|
||||
);
|
||||
target.hasImpulse = true;
|
||||
}
|
||||
|
||||
private void mutateTerrainAround(
|
||||
Vec3 center,
|
||||
float radius,
|
||||
RoyalBeamSettings settings,
|
||||
RoyalBeamTerrainMode terrainMode
|
||||
) {
|
||||
if (terrainMode == RoyalBeamTerrainMode.NONE || radius <= 0.0F) {
|
||||
return;
|
||||
}
|
||||
|
||||
Level level = this.owner.level();
|
||||
int minX = Mth.floor(center.x - radius);
|
||||
int minY = Mth.floor(center.y - radius);
|
||||
int minZ = Mth.floor(center.z - radius);
|
||||
int maxX = Mth.floor(center.x + radius);
|
||||
int maxY = Mth.floor(center.y + radius);
|
||||
int maxZ = Mth.floor(center.z + radius);
|
||||
float radiusSqr = radius * radius;
|
||||
|
||||
for (BlockPos cursor : BlockPos.betweenClosed(minX, minY, minZ, maxX, maxY, maxZ)) {
|
||||
if (this.terrainMutationsThisTick >= settings.maxTerrainMutationsPerTick()) {
|
||||
return;
|
||||
}
|
||||
BlockPos pos = cursor.immutable();
|
||||
if (pos.distToCenterSqr(center.x, center.y, center.z) > radiusSqr
|
||||
|| this.owner.getRandom().nextFloat() > settings.terrainMutationChance()
|
||||
|| pos.getY() < level.getMinBuildHeight()
|
||||
|| pos.getY() >= level.getMaxBuildHeight()
|
||||
|| !level.getWorldBorder().isWithinBounds(pos)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
BlockState existing = level.getBlockState(pos);
|
||||
if (terrainMode == RoyalBeamTerrainMode.DESTROY) {
|
||||
destroyBeamBlock(level, pos, existing, settings);
|
||||
} else if (terrainMode == RoyalBeamTerrainMode.BUILD_ICE) {
|
||||
buildIceBlock(level, pos, existing, settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void destroyBeamBlock(Level level, BlockPos pos, BlockState existing, RoyalBeamSettings settings) {
|
||||
if (existing.isAir()
|
||||
|| level.getBlockEntity(pos) != null
|
||||
|| existing.getDestroySpeed(level, pos) < 0.0F
|
||||
|| existing.getBlock().getExplosionResistance() > settings.maxTerrainResistance()
|
||||
|| existing.getCollisionShape(level, pos).isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.owner.getRandom().nextFloat() <= settings.terrainDropChance()) {
|
||||
level.destroyBlock(pos, true, this.owner);
|
||||
} else {
|
||||
level.levelEvent(2001, pos, Block.getId(existing));
|
||||
level.setBlockAndUpdate(pos, Blocks.AIR.defaultBlockState());
|
||||
}
|
||||
this.terrainMutationsThisTick++;
|
||||
}
|
||||
|
||||
private void buildIceBlock(Level level, BlockPos pos, BlockState existing, RoyalBeamSettings settings) {
|
||||
boolean softTarget = existing.isAir()
|
||||
|| existing.canBeReplaced()
|
||||
|| !existing.getFluidState().isEmpty()
|
||||
|| existing.getBlock().getExplosionResistance() <= settings.maxTerrainResistance()
|
||||
&& existing.getDestroySpeed(level, pos) >= 0.0F
|
||||
&& level.getBlockEntity(pos) == null;
|
||||
if (!softTarget) {
|
||||
return;
|
||||
}
|
||||
|
||||
level.levelEvent(2001, pos, Block.getId(existing));
|
||||
level.setBlock(pos, randomIceBlock().defaultBlockState(), Block.UPDATE_ALL);
|
||||
this.terrainMutationsThisTick++;
|
||||
}
|
||||
|
||||
private Block randomIceBlock() {
|
||||
return switch (this.owner.getRandom().nextInt(5)) {
|
||||
case 0 -> Blocks.BLUE_ICE;
|
||||
case 1 -> Blocks.PACKED_ICE;
|
||||
case 2 -> Blocks.FROSTED_ICE;
|
||||
case 3 -> Blocks.SNOW_BLOCK;
|
||||
default -> Blocks.ICE;
|
||||
};
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
package com.craisinlord.antarchy.content.entity.royal.beam;
|
||||
|
||||
public record RoyalBeamSettings(
|
||||
double range,
|
||||
double targetTracking,
|
||||
double pathStep,
|
||||
float pathDamageRadius,
|
||||
float impactDamageRadius,
|
||||
float damage,
|
||||
float knockback,
|
||||
int terrainIntervalTicks,
|
||||
double maxTerrainDistance,
|
||||
float pathTerrainRadius,
|
||||
float impactTerrainRadius,
|
||||
int maxTerrainMutationsPerTick,
|
||||
float terrainMutationChance,
|
||||
float terrainDropChance,
|
||||
float maxTerrainResistance,
|
||||
boolean terrainEnabled,
|
||||
boolean requireLineOfSightForDamage
|
||||
) {
|
||||
public static RoyalBeamSettings tremorzillaLike(float damage, boolean terrainEnabled) {
|
||||
return new RoyalBeamSettings(
|
||||
100.0D,
|
||||
0.1D,
|
||||
7.5D,
|
||||
6.0F,
|
||||
6.0F,
|
||||
damage,
|
||||
1.0F,
|
||||
3,
|
||||
100.0D,
|
||||
5.0F,
|
||||
4.0F,
|
||||
192,
|
||||
1.0F,
|
||||
0.08F,
|
||||
15.0F,
|
||||
terrainEnabled,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
public RoyalBeamSettings {
|
||||
range = Math.max(1.0D, range);
|
||||
targetTracking = Math.clamp(targetTracking, 0.0D, 1.0D);
|
||||
pathStep = Math.max(1.0D, pathStep);
|
||||
pathDamageRadius = Math.max(0.0F, pathDamageRadius);
|
||||
impactDamageRadius = Math.max(0.0F, impactDamageRadius);
|
||||
damage = Math.max(0.0F, damage);
|
||||
knockback = Math.max(0.0F, knockback);
|
||||
terrainIntervalTicks = Math.max(1, terrainIntervalTicks);
|
||||
maxTerrainDistance = Math.max(0.0D, maxTerrainDistance);
|
||||
pathTerrainRadius = Math.max(0.0F, pathTerrainRadius);
|
||||
impactTerrainRadius = Math.max(0.0F, impactTerrainRadius);
|
||||
maxTerrainMutationsPerTick = Math.max(0, maxTerrainMutationsPerTick);
|
||||
terrainMutationChance = Math.clamp(terrainMutationChance, 0.0F, 1.0F);
|
||||
terrainDropChance = Math.clamp(terrainDropChance, 0.0F, 1.0F);
|
||||
maxTerrainResistance = Math.max(0.0F, maxTerrainResistance);
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package com.craisinlord.antarchy.content.entity.royal.beam;
|
||||
|
||||
public enum RoyalBeamTerrainMode {
|
||||
NONE,
|
||||
DESTROY,
|
||||
BUILD_ICE
|
||||
}
|
||||
+69
-12
@@ -62,6 +62,7 @@ public class WindVortexEntity extends Entity {
|
||||
private static final String TRAVEL_Y_KEY = "TravelY";
|
||||
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 double BASE_RADIUS = 0.35D;
|
||||
private static final double DRIFT_FRICTION = 0.96D;
|
||||
@@ -80,6 +81,7 @@ public class WindVortexEntity extends Entity {
|
||||
private float damageOverride = -1.0F;
|
||||
private Vec3 travelVelocity = Vec3.ZERO;
|
||||
private boolean travelling = false;
|
||||
private boolean homing = false;
|
||||
private final Map<UUID, Double> carriedProgress = new HashMap<>();
|
||||
@Nullable
|
||||
private UUID ownerUuid;
|
||||
@@ -139,6 +141,9 @@ public class WindVortexEntity extends Entity {
|
||||
}
|
||||
|
||||
if (this.travelling) {
|
||||
if (this.homing) {
|
||||
this.steerTowardPrey();
|
||||
}
|
||||
Vec3 from = this.position();
|
||||
Vec3 to = from.add(this.travelVelocity);
|
||||
HitResult blockHit = this.level().clip(
|
||||
@@ -160,6 +165,40 @@ public class WindVortexEntity extends Entity {
|
||||
}
|
||||
}
|
||||
|
||||
private void steerTowardPrey() {
|
||||
double speed = this.travelVelocity.length();
|
||||
if (speed < 1.0E-4D) {
|
||||
return;
|
||||
}
|
||||
AABB seekArea = this.getBoundingBox().inflate(12.0D);
|
||||
Entity best = null;
|
||||
double bestDistSqr = Double.MAX_VALUE;
|
||||
for (Entity candidate : this.level().getEntities(this, seekArea, this::isSeekTarget)) {
|
||||
double distSqr = candidate.distanceToSqr(this);
|
||||
if (distSqr < bestDistSqr) {
|
||||
bestDistSqr = distSqr;
|
||||
best = candidate;
|
||||
}
|
||||
}
|
||||
if (best == null) {
|
||||
return;
|
||||
}
|
||||
Vec3 desired = best.getBoundingBox().getCenter().subtract(this.position());
|
||||
if (desired.lengthSqr() < 1.0E-6D) {
|
||||
return;
|
||||
}
|
||||
Vec3 steered = this.travelVelocity.normalize().lerp(desired.normalize(), 0.18D);
|
||||
if (steered.lengthSqr() < 1.0E-6D) {
|
||||
return;
|
||||
}
|
||||
this.travelVelocity = steered.normalize().scale(speed);
|
||||
}
|
||||
|
||||
private boolean isSeekTarget(Entity entity) {
|
||||
return entity instanceof LivingEntity && !(entity instanceof Player) && !this.isOwnedBy(entity)
|
||||
&& this.canAffectEntity(entity);
|
||||
}
|
||||
|
||||
private boolean isInsideSolidBlock() {
|
||||
BlockPos pos = this.blockPosition();
|
||||
BlockState state = this.level().getBlockState(pos);
|
||||
@@ -191,7 +230,7 @@ public class WindVortexEntity extends Entity {
|
||||
for (Entity entity : this.level().getEntities(this, area, this::canAffectEntity)) {
|
||||
Vec3 relative = entity.position().subtract(this.position());
|
||||
double axisDistance = relative.dot(basis.axis);
|
||||
if (axisDistance < -0.25D || axisDistance > height + 0.75D) {
|
||||
if (axisDistance < -1.0D || axisDistance > height + 2.0D) {
|
||||
continue;
|
||||
}
|
||||
double along = Mth.clamp(axisDistance, 0.0D, height);
|
||||
@@ -201,9 +240,11 @@ public class WindVortexEntity extends Entity {
|
||||
double radius = this.radiusAt(shapeProgress);
|
||||
Vec3 axisPoint = basis.axis.scale(axisDistance);
|
||||
Vec3 radialVector = relative.subtract(axisPoint);
|
||||
double captureRadius = radius + (entity instanceof Player ? 1.0D : 0.35D);
|
||||
double captureRadius = radius * 1.35D + (entity instanceof Player ? 1.5D : 1.75D);
|
||||
boolean requireLos = !this.travelling && mode == VortexMode.GATHER_RETURN;
|
||||
|
||||
if (radialVector.lengthSqr() > captureRadius * captureRadius || !this.hasClearPathTo(entity)) {
|
||||
if (radialVector.lengthSqr() > captureRadius * captureRadius
|
||||
|| (requireLos && !this.hasClearPathTo(entity))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -226,6 +267,9 @@ public class WindVortexEntity extends Entity {
|
||||
double progress = entry.getValue();
|
||||
double direction = mode == VortexMode.LENS_PULL ? -1.0D : 1.0D;
|
||||
progress += direction * 0.055D;
|
||||
if (this.travelling) {
|
||||
progress = Math.min(progress, 0.85D);
|
||||
}
|
||||
if (mode == VortexMode.LENS_PULL && progress <= 0.0D) {
|
||||
iterator.remove();
|
||||
continue;
|
||||
@@ -237,7 +281,7 @@ public class WindVortexEntity extends Entity {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (mode == VortexMode.UPWARD && progress >= 0.92D) {
|
||||
if (mode == VortexMode.UPWARD && progress >= 0.92D && !this.travelling) {
|
||||
this.launch(entity, this.radialVectorAt(entity, progress, basis), progress, basis);
|
||||
iterator.remove();
|
||||
continue;
|
||||
@@ -274,9 +318,6 @@ public class WindVortexEntity extends Entity {
|
||||
if (!entity.isAlive() || entity.isSpectator() || entity.noPhysics || entity.getType().is(AntarchyTags.Entities.WIND_VORTEX_IMMUNE)) {
|
||||
return false;
|
||||
}
|
||||
if (entity instanceof Player player && player.isCreative()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -295,7 +336,9 @@ public class WindVortexEntity extends Entity {
|
||||
? Math.max(0.05D, radius * 0.18D)
|
||||
: Mth.lerp(shapeProgress, BASE_RADIUS, radius * 0.82D);
|
||||
double radialError = targetRadius - horizontal;
|
||||
double playerScale = entity instanceof Player ? 1.8D : 1.0D;
|
||||
boolean lensMode = mode == VortexMode.LENS_PULL || mode == VortexMode.LENS_PUSH;
|
||||
double lensLivingBoost = lensMode && entity instanceof LivingEntity ? 1.6D : 1.0D;
|
||||
double playerScale = (entity instanceof Player ? 1.8D : 1.35D) * lensLivingBoost;
|
||||
double pull = this.pullStrength * playerScale * (entity.isShiftKeyDown() ? 0.5D : 1.0D);
|
||||
double massScale = Mth.clamp(1.35D / Math.max(0.35D, entity.getBbWidth() * entity.getBbHeight()), 0.28D, 1.0D);
|
||||
double spin = (0.24D + progress * 0.34D) * massScale * playerScale;
|
||||
@@ -305,8 +348,8 @@ public class WindVortexEntity extends Entity {
|
||||
case UPWARD -> (0.13D + progress * 0.16D) * massScale;
|
||||
case GATHER_RETURN -> (0.05D + progress * 0.08D) * massScale;
|
||||
} * playerScale;
|
||||
if (mode == VortexMode.UPWARD && entity instanceof Player && entity.onGround()) {
|
||||
axialSpeed = Math.max(axialSpeed, 0.38D);
|
||||
if (mode == VortexMode.UPWARD && entity.onGround()) {
|
||||
axialSpeed = Math.max(axialSpeed, entity instanceof Player ? 0.38D : 0.3D);
|
||||
}
|
||||
|
||||
Vec3 wanted = radial.scale(radialError * pull)
|
||||
@@ -318,8 +361,16 @@ public class WindVortexEntity extends Entity {
|
||||
Mth.lerp(0.58D, current.y, wanted.y),
|
||||
Mth.lerp(0.72D, current.z, wanted.z)
|
||||
);
|
||||
if (mode == VortexMode.UPWARD && entity instanceof Player && entity.onGround()) {
|
||||
updatedMovement = new Vec3(updatedMovement.x, Math.max(updatedMovement.y, 0.38D), updatedMovement.z);
|
||||
if (this.travelling) {
|
||||
updatedMovement = updatedMovement.add(this.travelVelocity.x * 0.8D, 0.0D, this.travelVelocity.z * 0.8D);
|
||||
}
|
||||
if (mode == VortexMode.UPWARD && entity.onGround()) {
|
||||
double minRise = entity instanceof Player ? 0.38D : 0.3D;
|
||||
updatedMovement = new Vec3(updatedMovement.x, Math.max(updatedMovement.y, minRise), updatedMovement.z);
|
||||
entity.setOnGround(false);
|
||||
}
|
||||
if (entity instanceof Player) {
|
||||
entity.move(net.minecraft.world.entity.MoverType.SELF, wanted.scale(0.65D));
|
||||
entity.setOnGround(false);
|
||||
}
|
||||
entity.setDeltaMovement(updatedMovement);
|
||||
@@ -455,6 +506,10 @@ public class WindVortexEntity extends Entity {
|
||||
this.noPhysics = true;
|
||||
}
|
||||
|
||||
public void setHoming(boolean homing) {
|
||||
this.homing = homing;
|
||||
}
|
||||
|
||||
public void setVortexStrengths(double pullStrength, double launchStrength) {
|
||||
this.pullStrength = Math.max(0.0D, pullStrength);
|
||||
this.launchStrength = Math.max(0.0D, launchStrength);
|
||||
@@ -491,6 +546,7 @@ public class WindVortexEntity extends Entity {
|
||||
this.setMode(VortexMode.values()[Mth.clamp(tag.getInt(MODE_KEY), 0, VortexMode.values().length - 1)]);
|
||||
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.travelVelocity = new Vec3(tag.getDouble(TRAVEL_X_KEY), tag.getDouble(TRAVEL_Y_KEY), tag.getDouble(TRAVEL_Z_KEY));
|
||||
if (this.travelling) {
|
||||
this.noPhysics = true;
|
||||
@@ -514,6 +570,7 @@ public class WindVortexEntity extends Entity {
|
||||
tag.putFloat(AXIS_Y_KEY, (float) this.getAxis().y);
|
||||
tag.putFloat(AXIS_Z_KEY, (float) this.getAxis().z);
|
||||
tag.putBoolean(TRAVELLING_KEY, this.travelling);
|
||||
tag.putBoolean(HOMING_KEY, this.homing);
|
||||
tag.putDouble(TRAVEL_X_KEY, this.travelVelocity.x);
|
||||
tag.putDouble(TRAVEL_Y_KEY, this.travelVelocity.y);
|
||||
tag.putDouble(TRAVEL_Z_KEY, this.travelVelocity.z);
|
||||
|
||||
+20
-18
@@ -23,7 +23,9 @@ import net.minecraft.stats.Stats;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.InteractionResultHolder;
|
||||
import net.minecraft.world.entity.EquipmentSlot;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.TooltipFlag;
|
||||
@@ -45,8 +47,7 @@ public class EyeOfTheStormItem extends Item implements GeoItem {
|
||||
private static final String IDLE_CONTROLLER = "idle_controller";
|
||||
private static final String IDLE_ANIMATION = "spinning_idle";
|
||||
private static final Map<UUID, Long> LAST_UPDRAFT_TICK = new ConcurrentHashMap<>();
|
||||
private static final Map<UUID, Long> LAST_SURGE_TICK = new ConcurrentHashMap<>();
|
||||
private static final float SURGE_PROJECTILE_SPEED = 0.9F;
|
||||
private static final float SURGE_PROJECTILE_SPEED = 0.65F;
|
||||
|
||||
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
|
||||
|
||||
@@ -107,22 +108,25 @@ public class EyeOfTheStormItem extends Item implements GeoItem {
|
||||
return;
|
||||
}
|
||||
|
||||
long now = level.getGameTime();
|
||||
Long last = LAST_SURGE_TICK.get(player.getUUID());
|
||||
if (last != null && now - last < AntarchySettings.eyeOfTheStormSurgeCooldownTicks()) {
|
||||
Vec3 look = player.getViewVector(1.0F);
|
||||
launchStormVortex(level, player.getEyePosition().add(look), look, player);
|
||||
|
||||
player.awardStat(Stats.ITEM_USED.get(this));
|
||||
stack.hurtAndBreak(1, player, EquipmentSlot.MAINHAND);
|
||||
}
|
||||
|
||||
public static void launchStormVortex(ServerLevel level, Vec3 origin, Vec3 direction, @Nullable LivingEntity owner) {
|
||||
if (!AntarchySettings.eyeOfTheStormEnabled()) {
|
||||
return;
|
||||
}
|
||||
LAST_SURGE_TICK.put(player.getUUID(), now);
|
||||
|
||||
Vec3 look = player.getViewVector(1.0F);
|
||||
Vec3 spawn = player.getEyePosition().add(look);
|
||||
Vec3 dir = direction.lengthSqr() < 1.0E-6D ? new Vec3(0.0D, 0.0D, 1.0D) : direction.normalize();
|
||||
|
||||
WindVortexEntity vortex = WindVortexEntity.create(
|
||||
level,
|
||||
AntarchyObjects.WIND_VORTEX.get(),
|
||||
spawn,
|
||||
origin,
|
||||
Vec3.ZERO,
|
||||
player,
|
||||
owner,
|
||||
true
|
||||
);
|
||||
vortex.setMode(WindVortexEntity.VortexMode.UPWARD);
|
||||
@@ -131,14 +135,12 @@ public class EyeOfTheStormItem extends Item implements GeoItem {
|
||||
vortex.setVortexDurationTicks(AntarchySettings.eyeOfTheStormSurgeDurationTicks());
|
||||
vortex.setVortexStrengths(AntarchySettings.eyeOfTheStormSurgePullStrength(), AntarchySettings.eyeOfTheStormSurgeReturnStrength());
|
||||
vortex.setDamageOverride(AntarchySettings.eyeOfTheStormSurgeDamage());
|
||||
vortex.setTravel(look.scale(SURGE_PROJECTILE_SPEED));
|
||||
vortex.setTravel(dir.scale(SURGE_PROJECTILE_SPEED));
|
||||
vortex.setHoming(true);
|
||||
level.addFreshEntity(vortex);
|
||||
|
||||
level.playSound(null, spawn.x, spawn.y, spawn.z,
|
||||
level.playSound(null, origin.x, origin.y, origin.z,
|
||||
SoundEvents.BREEZE_WIND_CHARGE_BURST, SoundSource.PLAYERS, 1.0F, 0.8F);
|
||||
|
||||
player.awardStat(Stats.ITEM_USED.get(this));
|
||||
stack.hurtAndBreak(1, player, EquipmentSlot.MAINHAND);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -148,8 +150,8 @@ public class EyeOfTheStormItem extends Item implements GeoItem {
|
||||
|
||||
@Override
|
||||
public void appendHoverText(ItemStack stack, Item.TooltipContext context, List<Component> tooltipComponents, TooltipFlag tooltipFlag) {
|
||||
tooltipComponents.add(Component.translatable("tooltip.antarchy.eye_of_the_storm.updraft").withStyle(ChatFormatting.AQUA));
|
||||
tooltipComponents.add(Component.translatable("tooltip.antarchy.eye_of_the_storm.surge").withStyle(ChatFormatting.AQUA));
|
||||
tooltipComponents.add(Component.translatable("tooltip.antarchy.eye_of_the_storm.updraft").withStyle(ChatFormatting.DARK_AQUA));
|
||||
tooltipComponents.add(Component.translatable("tooltip.antarchy.eye_of_the_storm.surge").withStyle(ChatFormatting.DARK_AQUA));
|
||||
super.appendHoverText(stack, context, tooltipComponents, tooltipFlag);
|
||||
}
|
||||
|
||||
|
||||
-10
@@ -1,14 +1,10 @@
|
||||
package com.craisinlord.antarchy.content.item;
|
||||
|
||||
import com.craisinlord.antarchy.config.AntarchySettings;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.ShieldItem;
|
||||
import net.minecraft.world.item.TooltipFlag;
|
||||
import net.minecraft.world.item.crafting.Ingredient;
|
||||
|
||||
public class RoyalGuardianShieldItem extends ShieldItem {
|
||||
@@ -28,10 +24,4 @@ public class RoyalGuardianShieldItem extends ShieldItem {
|
||||
public int getEnchantmentValue() {
|
||||
return AntarchySettings.royalWeaponEnchantability();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendHoverText(ItemStack stack, Item.TooltipContext context, List<Component> tooltipComponents, TooltipFlag tooltipFlag) {
|
||||
tooltipComponents.add(Component.translatable("tooltip.antarchy.royal_guardian_shield").withStyle(ChatFormatting.GOLD));
|
||||
super.appendHoverText(stack, context, tooltipComponents, tooltipFlag);
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package com.craisinlord.antarchy.content.item;
|
||||
|
||||
import java.util.List;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.item.BlockItem;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.TooltipFlag;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
|
||||
public class VortexLensBlockItem extends BlockItem {
|
||||
public VortexLensBlockItem(Block block, Properties properties) {
|
||||
super(block, properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendHoverText(ItemStack stack, Item.TooltipContext context, List<Component> tooltipComponents, TooltipFlag tooltipFlag) {
|
||||
tooltipComponents.add(Component.translatable("tooltip.antarchy.vortex_lens").withStyle(ChatFormatting.DARK_AQUA));
|
||||
super.appendHoverText(stack, context, tooltipComponents, tooltipFlag);
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package com.craisinlord.antarchy.content.network;
|
||||
|
||||
import com.craisinlord.antarchy.Antarchy;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.network.codec.ByteBufCodecs;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
public record TimeDilationRatePayload(int entityId, double rate) implements CustomPacketPayload {
|
||||
public static final Type<TimeDilationRatePayload> TYPE =
|
||||
new Type<>(ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "time_dilation_rate"));
|
||||
public static final StreamCodec<ByteBuf, TimeDilationRatePayload> STREAM_CODEC = StreamCodec.composite(
|
||||
ByteBufCodecs.VAR_INT,
|
||||
TimeDilationRatePayload::entityId,
|
||||
ByteBufCodecs.DOUBLE,
|
||||
TimeDilationRatePayload::rate,
|
||||
TimeDilationRatePayload::new
|
||||
);
|
||||
|
||||
@Override
|
||||
public Type<TimeDilationRatePayload> type() {
|
||||
return TYPE;
|
||||
}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
package com.craisinlord.antarchy.content.time;
|
||||
|
||||
import java.util.function.BiConsumer;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
public final class TimeDilationApi {
|
||||
private static BiConsumer<ServerPlayer, Double> syncDispatcher = (player, rate) -> {
|
||||
};
|
||||
|
||||
private TimeDilationApi() {
|
||||
}
|
||||
|
||||
public static void setSyncDispatcher(BiConsumer<ServerPlayer, Double> dispatcher) {
|
||||
syncDispatcher = dispatcher != null ? dispatcher : (player, rate) -> {
|
||||
};
|
||||
}
|
||||
|
||||
public static TimeDilationFieldEntity createField(ServerLevel level, Vec3 center, double radius, double rate, int durationTicks) {
|
||||
TimeDilationFieldEntity field = TimeDilationFieldEntity.create(level, center, radius, rate, durationTicks);
|
||||
level.addFreshEntity(field);
|
||||
return field;
|
||||
}
|
||||
|
||||
public static double getRate(Entity entity) {
|
||||
if (entity instanceof TimeDilationEntityAccess access) {
|
||||
return access.antarchy$getTimeDilationRate();
|
||||
}
|
||||
return TimeDilationMath.NORMAL_RATE;
|
||||
}
|
||||
|
||||
public static boolean isDilated(Entity entity) {
|
||||
return getRate(entity) < TimeDilationMath.NORMAL_RATE;
|
||||
}
|
||||
|
||||
public static void applySyncedRate(Entity entity, double rate) {
|
||||
if (entity instanceof TimeDilationEntityAccess access) {
|
||||
access.antarchy$setTimeDilationRate(rate);
|
||||
}
|
||||
}
|
||||
|
||||
public static void syncPlayerRate(ServerPlayer player, double rate) {
|
||||
syncDispatcher.accept(player, TimeDilationMath.clampRate(rate));
|
||||
}
|
||||
|
||||
public static boolean consumeTick(Entity entity, String timerKey) {
|
||||
if (entity instanceof Player) {
|
||||
return true;
|
||||
}
|
||||
double rate = getRate(entity);
|
||||
if (rate >= TimeDilationMath.NORMAL_RATE || !(entity instanceof TimeDilationEntityAccess access)) {
|
||||
return true;
|
||||
}
|
||||
return access.antarchy$consumeTimeDilationTick(timerKey, rate);
|
||||
}
|
||||
|
||||
public static int scaleCooldownTicks(Entity entity, int ticks) {
|
||||
double rate = getRate(entity);
|
||||
if (ticks <= 0 || rate >= TimeDilationMath.NORMAL_RATE) {
|
||||
return ticks;
|
||||
}
|
||||
return (int) Math.ceil(ticks / Math.max(TimeDilationMath.MIN_RATE, rate));
|
||||
}
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
package com.craisinlord.antarchy.content.time;
|
||||
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.arguments.DoubleArgumentType;
|
||||
import com.mojang.brigadier.arguments.IntegerArgumentType;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.Commands;
|
||||
import net.minecraft.commands.arguments.coordinates.Vec3Argument;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
public final class TimeDilationCommand {
|
||||
private TimeDilationCommand() {
|
||||
}
|
||||
|
||||
public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
|
||||
dispatcher.register(
|
||||
Commands.literal("timedilation")
|
||||
.requires(source -> source.hasPermission(2))
|
||||
.then(Commands.literal("create")
|
||||
.then(Commands.argument("center", Vec3Argument.vec3())
|
||||
.then(Commands.argument("radius", DoubleArgumentType.doubleArg(0.5D, 128.0D))
|
||||
.then(Commands.argument("rate", DoubleArgumentType.doubleArg(TimeDilationMath.MIN_RATE, 1.0D))
|
||||
.then(Commands.argument("durationTicks", IntegerArgumentType.integer(-1))
|
||||
.executes(TimeDilationCommand::create))))))
|
||||
.then(Commands.literal("create_here")
|
||||
.then(Commands.argument("radius", DoubleArgumentType.doubleArg(0.5D, 128.0D))
|
||||
.then(Commands.argument("rate", DoubleArgumentType.doubleArg(TimeDilationMath.MIN_RATE, 1.0D))
|
||||
.then(Commands.argument("durationTicks", IntegerArgumentType.integer(-1))
|
||||
.executes(TimeDilationCommand::createHere)))))
|
||||
.then(Commands.literal("list").executes(TimeDilationCommand::list))
|
||||
.then(Commands.literal("clear").executes(TimeDilationCommand::clear))
|
||||
);
|
||||
}
|
||||
|
||||
private static int create(CommandContext<CommandSourceStack> context) {
|
||||
return createField(context, Vec3Argument.getVec3(context, "center"));
|
||||
}
|
||||
|
||||
private static int createHere(CommandContext<CommandSourceStack> context) {
|
||||
return createField(context, context.getSource().getPosition());
|
||||
}
|
||||
|
||||
private static int createField(CommandContext<CommandSourceStack> context, Vec3 center) {
|
||||
ServerLevel level = context.getSource().getLevel();
|
||||
double radius = DoubleArgumentType.getDouble(context, "radius");
|
||||
double rate = DoubleArgumentType.getDouble(context, "rate");
|
||||
int durationTicks = IntegerArgumentType.getInteger(context, "durationTicks");
|
||||
TimeDilationFieldEntity field = TimeDilationApi.createField(level, center, radius, rate, durationTicks);
|
||||
context.getSource().sendSuccess(
|
||||
() -> Component.literal("Created time dilation field " + field.getUUID() + " radius=" + radius
|
||||
+ " rate=" + rate + " durationTicks=" + durationTicks),
|
||||
true
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static int list(CommandContext<CommandSourceStack> context) {
|
||||
int count = 0;
|
||||
for (ServerLevel level : context.getSource().getServer().getAllLevels()) {
|
||||
for (Entity entity : level.getAllEntities()) {
|
||||
if (!(entity instanceof TimeDilationFieldEntity field)) {
|
||||
continue;
|
||||
}
|
||||
count++;
|
||||
context.getSource().sendSuccess(
|
||||
() -> Component.literal(field.getUUID() + " " + level.dimension().location() + " @ "
|
||||
+ format(field.position()) + " radius=" + field.fieldRadius() + " rate=" + field.fieldRate()
|
||||
+ " age=" + field.fieldAge()
|
||||
+ (field.isInfinite() ? " duration=infinite" : " duration=" + field.fieldDurationTicks())),
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
private static int clear(CommandContext<CommandSourceStack> context) {
|
||||
List<TimeDilationFieldEntity> fields = new ArrayList<>();
|
||||
for (ServerLevel level : context.getSource().getServer().getAllLevels()) {
|
||||
for (Entity entity : level.getAllEntities()) {
|
||||
if (entity instanceof TimeDilationFieldEntity field) {
|
||||
fields.add(field);
|
||||
}
|
||||
}
|
||||
}
|
||||
fields.forEach(Entity::discard);
|
||||
int count = fields.size();
|
||||
context.getSource().sendSuccess(() -> Component.literal("Cleared " + count + " time dilation field(s)"), true);
|
||||
return count;
|
||||
}
|
||||
|
||||
private static String format(Vec3 vec) {
|
||||
return String.format(java.util.Locale.ROOT, "%.2f %.2f %.2f", vec.x, vec.y, vec.z);
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package com.craisinlord.antarchy.content.time;
|
||||
|
||||
public interface TimeDilationEntityAccess {
|
||||
double antarchy$getTimeDilationRate();
|
||||
|
||||
void antarchy$setTimeDilationRate(double rate);
|
||||
|
||||
boolean antarchy$consumeTimeDilationTick(String timerKey, double rate);
|
||||
|
||||
boolean antarchy$isInTimeDilationMove();
|
||||
|
||||
void antarchy$setInTimeDilationMove(boolean inMove);
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
package com.craisinlord.antarchy.content.time;
|
||||
|
||||
import com.craisinlord.antarchy.content.AntarchyObjects;
|
||||
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.world.entity.Entity;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
public class TimeDilationFieldEntity extends Entity {
|
||||
private static final EntityDataAccessor<Float> RADIUS =
|
||||
SynchedEntityData.defineId(TimeDilationFieldEntity.class, EntityDataSerializers.FLOAT);
|
||||
private static final EntityDataAccessor<Float> RATE =
|
||||
SynchedEntityData.defineId(TimeDilationFieldEntity.class, EntityDataSerializers.FLOAT);
|
||||
|
||||
private static final String RADIUS_KEY = "Radius";
|
||||
private static final String RATE_KEY = "Rate";
|
||||
private static final String DURATION_KEY = "DurationTicks";
|
||||
private static final String AGE_KEY = "Age";
|
||||
|
||||
private int durationTicks = -1;
|
||||
private int age;
|
||||
|
||||
public TimeDilationFieldEntity(EntityType<? extends TimeDilationFieldEntity> entityType, Level level) {
|
||||
super(entityType, level);
|
||||
this.noPhysics = true;
|
||||
this.noCulling = true;
|
||||
}
|
||||
|
||||
public static TimeDilationFieldEntity create(Level level, Vec3 center, double radius, double rate, int durationTicks) {
|
||||
TimeDilationFieldEntity field = new TimeDilationFieldEntity(AntarchyObjects.TIME_DILATION_FIELD.get(), level);
|
||||
field.setPos(center.x, center.y, center.z);
|
||||
field.setFieldRadius(radius);
|
||||
field.setFieldRate(rate);
|
||||
field.durationTicks = durationTicks;
|
||||
return field;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void defineSynchedData(SynchedEntityData.Builder builder) {
|
||||
builder.define(RADIUS, 8.0F);
|
||||
builder.define(RATE, (float) TimeDilationMath.NORMAL_RATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
super.tick();
|
||||
if (this.level().isClientSide) {
|
||||
return;
|
||||
}
|
||||
this.age++;
|
||||
if (this.durationTicks >= 0 && this.age >= this.durationTicks) {
|
||||
this.discard();
|
||||
}
|
||||
}
|
||||
|
||||
public void setFieldRadius(double radius) {
|
||||
this.entityData.set(RADIUS, (float) Math.max(0.5D, radius));
|
||||
}
|
||||
|
||||
public double fieldRadius() {
|
||||
return this.entityData.get(RADIUS);
|
||||
}
|
||||
|
||||
public double fieldRadiusSqr() {
|
||||
double radius = this.fieldRadius();
|
||||
return radius * radius;
|
||||
}
|
||||
|
||||
public void setFieldRate(double rate) {
|
||||
this.entityData.set(RATE, (float) TimeDilationMath.clampRate(rate));
|
||||
}
|
||||
|
||||
public double fieldRate() {
|
||||
return TimeDilationMath.clampRate(this.entityData.get(RATE));
|
||||
}
|
||||
|
||||
public int fieldDurationTicks() {
|
||||
return this.durationTicks;
|
||||
}
|
||||
|
||||
public int fieldAge() {
|
||||
return this.age;
|
||||
}
|
||||
|
||||
public boolean isInfinite() {
|
||||
return this.durationTicks < 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIgnoringBlockTriggers() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void readAdditionalSaveData(CompoundTag tag) {
|
||||
this.setFieldRadius(tag.getDouble(RADIUS_KEY));
|
||||
this.setFieldRate(tag.getDouble(RATE_KEY));
|
||||
this.durationTicks = tag.contains(DURATION_KEY) ? tag.getInt(DURATION_KEY) : -1;
|
||||
this.age = tag.getInt(AGE_KEY);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addAdditionalSaveData(CompoundTag tag) {
|
||||
tag.putDouble(RADIUS_KEY, this.fieldRadius());
|
||||
tag.putDouble(RATE_KEY, this.fieldRate());
|
||||
tag.putInt(DURATION_KEY, this.durationTicks);
|
||||
tag.putInt(AGE_KEY, this.age);
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package com.craisinlord.antarchy.content.time;
|
||||
|
||||
import java.util.List;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
public final class TimeDilationFieldSampler {
|
||||
private TimeDilationFieldSampler() {
|
||||
}
|
||||
|
||||
public static double sample(List<TimeDilationFieldEntity> fields, Vec3 position) {
|
||||
return sample(fields, position.x, position.y, position.z);
|
||||
}
|
||||
|
||||
public static double sample(List<TimeDilationFieldEntity> fields, double x, double y, double z) {
|
||||
double combinedRate = TimeDilationMath.NORMAL_RATE;
|
||||
for (TimeDilationFieldEntity field : fields) {
|
||||
Vec3 center = field.position();
|
||||
double dx = center.x - x;
|
||||
double dy = center.y - y;
|
||||
double dz = center.z - z;
|
||||
double distanceSqr = dx * dx + dy * dy + dz * dz;
|
||||
if (distanceSqr >= field.fieldRadiusSqr()) {
|
||||
continue;
|
||||
}
|
||||
double falloff = TimeDilationMath.gaussianFalloff(Math.sqrt(distanceSqr), field.fieldRadius());
|
||||
combinedRate *= TimeDilationMath.localFieldRate(field.fieldRate(), falloff);
|
||||
}
|
||||
return TimeDilationMath.clampRate(combinedRate);
|
||||
}
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
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;
|
||||
|
||||
public final class TimeDilationManager {
|
||||
private static final Set<ResourceKey<Level>> DILATED_LEVELS = new HashSet<>();
|
||||
|
||||
private TimeDilationManager() {
|
||||
}
|
||||
|
||||
public static void tickServer(MinecraftServer server) {
|
||||
for (ServerLevel level : server.getAllLevels()) {
|
||||
List<TimeDilationFieldEntity> fields = collectFields(level);
|
||||
if (fields.isEmpty()) {
|
||||
if (DILATED_LEVELS.remove(level.dimension())) {
|
||||
resetRates(level);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
DILATED_LEVELS.add(level.dimension());
|
||||
updateEntities(level, fields);
|
||||
TimeDilationParticles.spawnFieldBorders(level, fields);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<TimeDilationFieldEntity> collectFields(ServerLevel level) {
|
||||
List<TimeDilationFieldEntity> fields = new ArrayList<>();
|
||||
for (Entity entity : level.getAllEntities()) {
|
||||
if (entity instanceof TimeDilationFieldEntity field && field.isAlive()) {
|
||||
fields.add(field);
|
||||
}
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
|
||||
private static void updateEntities(ServerLevel level, List<TimeDilationFieldEntity> fields) {
|
||||
for (Entity entity : level.getAllEntities()) {
|
||||
if (!(entity instanceof TimeDilationEntityAccess access) || entity instanceof TimeDilationFieldEntity) {
|
||||
continue;
|
||||
}
|
||||
double previousRate = access.antarchy$getTimeDilationRate();
|
||||
double rate = TimeDilationFieldSampler.sample(fields, entity.position());
|
||||
access.antarchy$setTimeDilationRate(rate);
|
||||
if (entity instanceof ServerPlayer player && Math.abs(previousRate - rate) > 0.001D) {
|
||||
TimeDilationApi.syncPlayerRate(player, 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);
|
||||
if (entity instanceof ServerPlayer player) {
|
||||
TimeDilationApi.syncPlayerRate(player, TimeDilationMath.NORMAL_RATE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package com.craisinlord.antarchy.content.time;
|
||||
|
||||
public final class TimeDilationMath {
|
||||
public static final double MIN_RATE = 0.05D;
|
||||
public static final double NORMAL_RATE = 1.0D;
|
||||
private static final double GAUSSIAN_EDGE_EXPONENT = -4.5D;
|
||||
|
||||
private TimeDilationMath() {
|
||||
}
|
||||
|
||||
public static double clampRate(double rate) {
|
||||
if (Double.isNaN(rate) || Double.isInfinite(rate)) {
|
||||
return NORMAL_RATE;
|
||||
}
|
||||
return Math.max(MIN_RATE, Math.min(NORMAL_RATE, rate));
|
||||
}
|
||||
|
||||
public static double gaussianFalloff(double distance, double radius) {
|
||||
if (distance < 0.0D || distance >= radius || radius <= 0.0D) {
|
||||
return 0.0D;
|
||||
}
|
||||
double normalized = distance / radius;
|
||||
return Math.exp(GAUSSIAN_EDGE_EXPONENT * normalized * normalized);
|
||||
}
|
||||
|
||||
public static double localFieldRate(double fieldRate, double falloff) {
|
||||
double clampedRate = clampRate(fieldRate);
|
||||
double clampedFalloff = Math.max(0.0D, Math.min(1.0D, falloff));
|
||||
if (clampedRate <= 0.0D) {
|
||||
return 0.0D;
|
||||
}
|
||||
return Math.pow(clampedRate, clampedFalloff);
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package com.craisinlord.antarchy.content.time;
|
||||
|
||||
import java.util.List;
|
||||
import net.minecraft.core.particles.ParticleTypes;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
public final class TimeDilationParticles {
|
||||
private static final int PARTICLE_INTERVAL_TICKS = 4;
|
||||
private static final int MAX_RING_POINTS = 96;
|
||||
|
||||
private TimeDilationParticles() {
|
||||
}
|
||||
|
||||
public static void spawnFieldBorders(ServerLevel level, List<TimeDilationFieldEntity> fields) {
|
||||
if (level.getGameTime() % PARTICLE_INTERVAL_TICKS != 0L) {
|
||||
return;
|
||||
}
|
||||
for (TimeDilationFieldEntity field : fields) {
|
||||
spawnBorder(level, field.position(), field.fieldRadius());
|
||||
}
|
||||
}
|
||||
|
||||
private static void spawnBorder(ServerLevel level, Vec3 center, double radius) {
|
||||
int points = Mth.clamp((int) Math.ceil(radius * 10.0D), 24, MAX_RING_POINTS);
|
||||
double phase = (level.getGameTime() % 80L) * 0.07853981633974483D;
|
||||
|
||||
for (int i = 0; i < points; i++) {
|
||||
double angle = phase + (Math.PI * 2.0D * i / points);
|
||||
double x = center.x + Math.cos(angle) * radius;
|
||||
double z = center.z + Math.sin(angle) * radius;
|
||||
spawnParticle(level, x, center.y, z);
|
||||
|
||||
if (i % 4 == 0) {
|
||||
spawnParticle(level, x, center.y + radius * 0.5D, z);
|
||||
spawnParticle(level, x, center.y - radius * 0.5D, z);
|
||||
}
|
||||
if (i % 8 == 0) {
|
||||
spawnParticle(level, center.x + Math.cos(angle) * radius * 0.7D, center.y + Math.sin(angle) * radius, center.z);
|
||||
spawnParticle(level, center.x, center.y + Math.sin(angle) * radius, center.z + Math.cos(angle) * radius * 0.7D);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void spawnParticle(ServerLevel level, double x, double y, double z) {
|
||||
level.sendParticles(ParticleTypes.REVERSE_PORTAL, x, y, z, 1, 0.02D, 0.02D, 0.02D, 0.0D);
|
||||
}
|
||||
}
|
||||
+5
-42
@@ -1,10 +1,8 @@
|
||||
package com.craisinlord.antarchy.content.worldgen.thoraxis;
|
||||
|
||||
import com.craisinlord.antarchy.config.AntarchySettings;
|
||||
import com.craisinlord.antarchy.content.entity.portal.DimensionalTearEntity;
|
||||
import com.craisinlord.antarchy.content.AntarchyObjects;
|
||||
import com.mojang.serialization.Codec;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.WorldGenRegion;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.util.RandomSource;
|
||||
@@ -13,15 +11,10 @@ import net.minecraft.world.level.WorldGenLevel;
|
||||
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 net.minecraft.world.phys.Vec3;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public final class DimensionalTearFeature extends Feature<NoneFeatureConfiguration> {
|
||||
private static final int MIN_LINK_RANGE = 50;
|
||||
private static final int MAX_LINK_RANGE = 500;
|
||||
private static final int LOCAL_SEARCH_ATTEMPTS = 18;
|
||||
private static final int PARTNER_ORIGIN_ATTEMPTS = 12;
|
||||
private static final int PARTNER_SEARCH_ATTEMPTS = 48;
|
||||
|
||||
public DimensionalTearFeature(Codec<NoneFeatureConfiguration> codec) {
|
||||
super(codec);
|
||||
@@ -30,46 +23,20 @@ public final class DimensionalTearFeature extends Feature<NoneFeatureConfigurati
|
||||
@Override
|
||||
public boolean place(FeaturePlaceContext<NoneFeatureConfiguration> context) {
|
||||
WorldGenLevel level = context.level();
|
||||
if (!(level instanceof ServerLevelAccessor serverLevelAccessor)) {
|
||||
if (!(level instanceof ServerLevelAccessor)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RandomSource random = context.random();
|
||||
BlockPos first = findOpenPocket(level, context.origin(), random, LOCAL_SEARCH_ATTEMPTS);
|
||||
if (first == null) {
|
||||
BlockPos anchor = findOpenPocket(level, context.origin(), random, LOCAL_SEARCH_ATTEMPTS);
|
||||
if (anchor == null || !canAccess(level, anchor)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
BlockPos second = null;
|
||||
for (int i = 0; i < PARTNER_ORIGIN_ATTEMPTS && second == null; i++) {
|
||||
second = findOpenPocket(level, linkedOrigin(first, random), random, PARTNER_SEARCH_ATTEMPTS);
|
||||
}
|
||||
if (second == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ServerLevel serverLevel = serverLevelAccessor.getLevel();
|
||||
int lifetime = Math.max(1200, AntarchySettings.dimensionalTearLifetimeTicks());
|
||||
float yawA = random.nextFloat() * 360.0F;
|
||||
float yawB = Mth.wrapDegrees(yawA + 140.0F + random.nextFloat() * 80.0F);
|
||||
DimensionalTearEntity tearA = DimensionalTearEntity.create(serverLevel, center(first), yawA, lifetime);
|
||||
DimensionalTearEntity tearB = DimensionalTearEntity.create(serverLevel, center(second), yawB, lifetime);
|
||||
tearA.linkTo(tearB);
|
||||
tearB.linkTo(tearA);
|
||||
serverLevel.addFreshEntity(tearA);
|
||||
serverLevel.addFreshEntity(tearB);
|
||||
level.setBlock(anchor, AntarchyObjects.DIMENSIONAL_TEAR_MARKER.get().defaultBlockState(), 2);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static BlockPos linkedOrigin(BlockPos first, RandomSource random) {
|
||||
double angle = random.nextDouble() * Math.PI * 2.0D;
|
||||
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);
|
||||
return first.offset(dx, dy, dz);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static BlockPos findOpenPocket(WorldGenLevel level, BlockPos origin, RandomSource random, int attempts) {
|
||||
int minY = level.getMinBuildHeight() + 8;
|
||||
@@ -105,8 +72,4 @@ public final class DimensionalTearFeature extends Feature<NoneFeatureConfigurati
|
||||
private static boolean canAccess(WorldGenLevel level, BlockPos pos) {
|
||||
return !(level instanceof WorldGenRegion region) || region.ensureCanWrite(pos);
|
||||
}
|
||||
|
||||
private static Vec3 center(BlockPos pos) {
|
||||
return new Vec3(pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D);
|
||||
}
|
||||
}
|
||||
+97
-44
@@ -48,9 +48,13 @@ public class NadirTreeFeature extends Feature<NadirTreeConfiguration> {
|
||||
Map<BlockPos, Direction.Axis> logs = new LinkedHashMap<>();
|
||||
Set<BlockPos> foliage = new LinkedHashSet<>();
|
||||
|
||||
placeTrunk(spine, bottomRadius, logs, random);
|
||||
int tierCount = Mth.clamp(2 + branchCount / 2, 3, 5);
|
||||
int outerRadius = 4 + bottomRadius + random.nextInt(3);
|
||||
|
||||
placeTrunk(spine, logs, random);
|
||||
placeRootWaves(spine, bottomRadius, logs, foliage, random, veilRadius);
|
||||
placeBranches(spine, branchCount, branchLength, logs, foliage, random, veilRadius);
|
||||
placeChandelierTiers(spine, tierCount, outerRadius, logs, foliage, random, veilRadius);
|
||||
placeBranches(spine, Math.max(1, branchCount / 2), branchLength, logs, foliage, random, veilRadius);
|
||||
placeVeilCurtains(spine, foliage, random, veilRadius);
|
||||
|
||||
if (logs.isEmpty()) {
|
||||
@@ -114,38 +118,27 @@ public class NadirTreeFeature extends Feature<NadirTreeConfiguration> {
|
||||
return spine;
|
||||
}
|
||||
|
||||
private void placeTrunk(List<BlockPos> spine, int bottomRadius, Map<BlockPos, Direction.Axis> logs, RandomSource random) {
|
||||
private void placeTrunk(List<BlockPos> spine, Map<BlockPos, Direction.Axis> logs, RandomSource random) {
|
||||
for (int index = 0; index < spine.size(); index++) {
|
||||
BlockPos center = spine.get(index);
|
||||
float progress = spine.size() == 1 ? 1.0F : index / (float) (spine.size() - 1);
|
||||
float wave = Mth.sin(progress * Mth.PI * 2.75F) * 0.55F + Mth.sin(progress * Mth.PI * 5.5F) * 0.25F;
|
||||
int radius = Mth.clamp(Math.round((0.45F + progress * 0.3F + wave * 0.22F) * bottomRadius), 0, Math.max(1, bottomRadius - 1));
|
||||
if (index < 2) {
|
||||
radius = Math.max(radius, 1);
|
||||
}
|
||||
fillTrunkLayer(center, radius, logs, random);
|
||||
fillTrunkLayer(spine.get(index), index < 2, logs, random);
|
||||
}
|
||||
}
|
||||
|
||||
private void fillTrunkLayer(BlockPos center, int radius, Map<BlockPos, Direction.Axis> logs, RandomSource random) {
|
||||
if (radius <= 0) {
|
||||
putLog(logs, center, Direction.Axis.Y);
|
||||
return;
|
||||
}
|
||||
|
||||
int radiusSq = radius * radius;
|
||||
for (int dx = -radius; dx <= radius; dx++) {
|
||||
for (int dz = -radius; dz <= radius; dz++) {
|
||||
int dist = dx * dx + dz * dz;
|
||||
if (dist > radiusSq + 1) {
|
||||
continue;
|
||||
}
|
||||
if (dist > radiusSq - radius && random.nextFloat() < 0.48F) {
|
||||
continue;
|
||||
}
|
||||
private void fillTrunkLayer(BlockPos center, boolean flare, Map<BlockPos, Direction.Axis> logs, RandomSource random) {
|
||||
for (int dx = 0; dx <= 1; dx++) {
|
||||
for (int dz = 0; dz <= 1; dz++) {
|
||||
putLog(logs, center.offset(dx, 0, dz), Direction.Axis.Y);
|
||||
}
|
||||
}
|
||||
if (flare) {
|
||||
for (int dx = -1; dx <= 2; dx++) {
|
||||
for (int dz = -1; dz <= 2; dz++) {
|
||||
if ((dx == -1 || dx == 2 || dz == -1 || dz == 2) && random.nextFloat() < 0.5F) {
|
||||
putLog(logs, center.offset(dx, 0, dz), Direction.Axis.Y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void placeRootWaves(
|
||||
@@ -231,6 +224,67 @@ public class NadirTreeFeature extends Feature<NadirTreeConfiguration> {
|
||||
}
|
||||
}
|
||||
|
||||
private void placeChandelierTiers(
|
||||
List<BlockPos> spine,
|
||||
int tierCount,
|
||||
int outerRadius,
|
||||
Map<BlockPos, Direction.Axis> logs,
|
||||
Set<BlockPos> foliage,
|
||||
RandomSource random,
|
||||
int veilRadius
|
||||
) {
|
||||
int top = Math.max(2, spine.size() / 5);
|
||||
int bottom = Math.max(top + 1, spine.size() - 2);
|
||||
for (int tier = 0; tier < tierCount; tier++) {
|
||||
float progress = tierCount == 1 ? 0.0F : tier / (float) (tierCount - 1);
|
||||
int index = Mth.clamp(Math.round(Mth.lerp(progress, top, bottom)), 0, spine.size() - 1);
|
||||
BlockPos hub = spine.get(index);
|
||||
int radius = Math.max(2, Math.round(Mth.lerp(progress, outerRadius, 2.0F)) + random.nextInt(2));
|
||||
int droop = 1 + random.nextInt(2);
|
||||
BlockPos ringCenter = hub.below(droop);
|
||||
|
||||
placeTierRing(ringCenter, radius, logs, random);
|
||||
placeTierSpokes(hub, ringCenter, radius, logs);
|
||||
|
||||
for (Direction spoke : Direction.Plane.HORIZONTAL) {
|
||||
BlockPos rim = ringCenter.relative(spoke, radius);
|
||||
Direction tangent = spoke.getClockWise();
|
||||
placeVeilRibbon(foliage, rim, tangent, Math.max(2, veilRadius + random.nextInt(2)), random);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void placeTierRing(BlockPos center, int radius, Map<BlockPos, Direction.Axis> logs, RandomSource random) {
|
||||
int outerSq = (radius + 1) * (radius + 1);
|
||||
int innerSq = (radius - 1) * (radius - 1);
|
||||
for (int dx = -radius - 1; dx <= radius + 1; dx++) {
|
||||
for (int dz = -radius - 1; dz <= radius + 1; dz++) {
|
||||
int distSq = dx * dx + dz * dz;
|
||||
if (distSq > outerSq || distSq < innerSq) {
|
||||
continue;
|
||||
}
|
||||
if (random.nextFloat() < 0.12F) {
|
||||
continue;
|
||||
}
|
||||
Direction.Axis axis = Math.abs(dx) >= Math.abs(dz) ? Direction.Axis.X : Direction.Axis.Z;
|
||||
putLog(logs, center.offset(dx, 0, dz), axis);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void placeTierSpokes(BlockPos hub, BlockPos ringCenter, int radius, Map<BlockPos, Direction.Axis> logs) {
|
||||
for (Direction spoke : Direction.Plane.HORIZONTAL) {
|
||||
BlockPos previous = hub;
|
||||
for (int step = 1; step <= radius; step++) {
|
||||
float t = step / (float) radius;
|
||||
int y = Math.round(Mth.lerp(t, hub.getY(), ringCenter.getY()));
|
||||
BlockPos next = new BlockPos(hub.getX() + spoke.getStepX() * step, y, hub.getZ() + spoke.getStepZ() * step);
|
||||
putLog(logs, next, axisForSegment(previous, next));
|
||||
previous = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void placeVeilCurtains(List<BlockPos> spine, Set<BlockPos> foliage, RandomSource random, int veilRadius) {
|
||||
for (int index = 1; index < spine.size(); index++) {
|
||||
if (random.nextFloat() > 0.24F) {
|
||||
@@ -244,23 +298,22 @@ public class NadirTreeFeature extends Feature<NadirTreeConfiguration> {
|
||||
|
||||
private void placeVeilRibbon(Set<BlockPos> foliage, BlockPos start, Direction flow, int length, RandomSource random) {
|
||||
Direction side = random.nextBoolean() ? flow.getClockWise() : flow.getCounterClockWise();
|
||||
BlockPos cursor = start;
|
||||
for (int step = 0; step < length; step++) {
|
||||
foliage.add(cursor);
|
||||
if (random.nextFloat() < 0.34F) {
|
||||
foliage.add(cursor.relative(side));
|
||||
}
|
||||
if (random.nextFloat() < 0.18F) {
|
||||
foliage.add(cursor.relative(side.getOpposite()));
|
||||
}
|
||||
cursor = cursor.below();
|
||||
if (step > 0 && random.nextFloat() < 0.4F) {
|
||||
cursor = cursor.relative(random.nextBoolean() ? side : side.getOpposite());
|
||||
}
|
||||
if (random.nextFloat() < 0.18F) {
|
||||
BlockPos drift = cursor.relative(flow);
|
||||
if (random.nextBoolean()) {
|
||||
foliage.add(drift);
|
||||
int span = Math.max(4, length + 2);
|
||||
int bands = 1 + random.nextInt(2);
|
||||
float phase = random.nextFloat() * Mth.PI * 2.0F;
|
||||
float frequency = 0.55F + random.nextFloat() * 0.45F;
|
||||
for (int band = 0; band < bands; band++) {
|
||||
BlockPos rowAnchor = start.relative(side, band);
|
||||
float bandPhase = phase + band * 1.7F;
|
||||
for (int step = 0; step < span; step++) {
|
||||
float wave = Mth.sin(step * frequency + bandPhase);
|
||||
int lift = Math.round(wave * 1.5F);
|
||||
BlockPos top = rowAnchor.relative(flow, step).offset(0, lift, 0);
|
||||
int strand = Math.max(2, 3 + Math.round((wave + 1.0F) * 2.0F) + random.nextInt(2) - (1 + random.nextInt(3)));
|
||||
BlockPos cursor = top;
|
||||
for (int drop = 0; drop < strand; drop++) {
|
||||
foliage.add(cursor);
|
||||
cursor = cursor.below();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+21
-4
@@ -26,18 +26,22 @@ public final class ThoraxisUndersideManager {
|
||||
private static final ResourceLocation THORAXIS_DIMENSION = ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "thoraxis");
|
||||
private static final AntarchyGravityTransition TRANSITION = new AntarchyGravityTransition(12);
|
||||
private static final int EFFECT_DURATION_TICKS = 40;
|
||||
private static final int MAX_FLIPS_PER_TICK = 8;
|
||||
private static final Set<UUID> UNDERSIDE_APPLIED_EFFECT = new HashSet<>();
|
||||
private static final Set<UUID> UNDERSIDE_FORCED_GRAVITY = new HashSet<>();
|
||||
private static final Set<UUID> ACTIVE_ITEMS_SCRATCH = new HashSet<>();
|
||||
|
||||
private ThoraxisUndersideManager() {
|
||||
}
|
||||
|
||||
public static void tick(ServerLevel level) {
|
||||
if (!isThoraxis(level)) {
|
||||
if (!isThoraxis(level) || level.players().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Set<UUID> activeThisTick = new HashSet<>();
|
||||
Set<UUID> activeThisTick = ACTIVE_ITEMS_SCRATCH;
|
||||
activeThisTick.clear();
|
||||
int flipsRemaining = MAX_FLIPS_PER_TICK;
|
||||
for (Entity entity : level.getAllEntities()) {
|
||||
if (!entity.isAlive() || entity.isSpectator()) {
|
||||
continue;
|
||||
@@ -48,6 +52,14 @@ public final class ThoraxisUndersideManager {
|
||||
if (entity instanceof LivingEntity living) {
|
||||
UUID uuid = entity.getUUID();
|
||||
if (inUnderside) {
|
||||
boolean needsFlip = AntarchyGravityApi.getGravityDirection(living) != AntarchyGravityDirection.UP
|
||||
|| !AntarchyGravityApi.isGravityForced(living);
|
||||
if (needsFlip && flipsRemaining <= 0) {
|
||||
continue;
|
||||
}
|
||||
if (needsFlip) {
|
||||
flipsRemaining--;
|
||||
}
|
||||
applyUndersideInversion(living);
|
||||
} else if (UNDERSIDE_APPLIED_EFFECT.remove(uuid)) {
|
||||
if (living.hasEffect(AntarchyObjects.INVERTED_EFFECT.get())) {
|
||||
@@ -67,10 +79,15 @@ public final class ThoraxisUndersideManager {
|
||||
|
||||
UUID uuid = entity.getUUID();
|
||||
if (inUnderside) {
|
||||
boolean needsFlip = AntarchyGravityApi.getGravityDirection(entity) != AntarchyGravityDirection.UP
|
||||
|| !AntarchyGravityApi.isGravityForced(entity);
|
||||
if (needsFlip && flipsRemaining <= 0) {
|
||||
continue;
|
||||
}
|
||||
activeThisTick.add(uuid);
|
||||
UNDERSIDE_FORCED_GRAVITY.add(uuid);
|
||||
if (AntarchyGravityApi.getGravityDirection(entity) != AntarchyGravityDirection.UP
|
||||
|| !AntarchyGravityApi.isGravityForced(entity)) {
|
||||
if (needsFlip) {
|
||||
flipsRemaining--;
|
||||
AntarchyGravityApi.setForcedGravityDirection(entity, AntarchyGravityDirection.UP, TRANSITION);
|
||||
}
|
||||
continue;
|
||||
|
||||
+1
-1
@@ -38,6 +38,6 @@ public abstract class CustomBossBarMixin {
|
||||
|
||||
@ModifyConstant(method = "render", constant = @Constant(intValue = 9, ordinal = 1))
|
||||
private int antarchy$doubleGapForCustomBossBar(int original, @Local LerpingBossEvent bossEvent) {
|
||||
return CustomBossBarRenderer.isCustomBoss(bossEvent) ? original + 19 : original;
|
||||
return CustomBossBarRenderer.isCustomBoss(bossEvent) ? original + 30 : original;
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package com.craisinlord.antarchy.mixins.time;
|
||||
|
||||
import com.craisinlord.antarchy.content.time.TimeDilationApi;
|
||||
import com.craisinlord.antarchy.content.time.TimeDilationEntityAccess;
|
||||
import com.craisinlord.antarchy.content.time.TimeDilationMath;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
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.ModifyVariable;
|
||||
|
||||
@Mixin(Entity.class)
|
||||
public abstract class EntityDeltaMovementTimeDilationMixin {
|
||||
@ModifyVariable(method = "setDeltaMovement(Lnet/minecraft/world/phys/Vec3;)V", at = @At("HEAD"), argsOnly = true, ordinal = 0)
|
||||
private Vec3 antarchy$scaleTimeDilatedVelocityChange(Vec3 nextMovement) {
|
||||
Entity entity = (Entity) (Object) this;
|
||||
if (!(entity instanceof TimeDilationEntityAccess access)
|
||||
|| access.antarchy$isInTimeDilationMove()) {
|
||||
return nextMovement;
|
||||
}
|
||||
|
||||
double rate = TimeDilationApi.getRate(entity);
|
||||
if (rate >= TimeDilationMath.NORMAL_RATE) {
|
||||
return nextMovement;
|
||||
}
|
||||
|
||||
Vec3 currentMovement = entity.getDeltaMovement();
|
||||
double y = currentMovement.y + (nextMovement.y - currentMovement.y) * rate;
|
||||
if (nextMovement.y > currentMovement.y && nextMovement.y > 0.0D && (entity.onGround() || currentMovement.y <= 0.0D)) {
|
||||
y = nextMovement.y;
|
||||
}
|
||||
return new Vec3(
|
||||
currentMovement.x + (nextMovement.x - currentMovement.x) * rate,
|
||||
y,
|
||||
currentMovement.z + (nextMovement.z - currentMovement.z) * rate
|
||||
);
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package com.craisinlord.antarchy.mixins.time;
|
||||
|
||||
import com.craisinlord.antarchy.content.time.TimeDilationApi;
|
||||
import com.craisinlord.antarchy.content.time.TimeDilationEntityAccess;
|
||||
import com.craisinlord.antarchy.content.time.TimeDilationMath;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.MoverType;
|
||||
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.ModifyVariable;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(Entity.class)
|
||||
public abstract class EntityMoveTimeDilationMixin {
|
||||
@Inject(method = "move", at = @At("HEAD"))
|
||||
private void antarchy$beginTimeDilationMove(MoverType moverType, Vec3 movement, CallbackInfo ci) {
|
||||
if (this instanceof TimeDilationEntityAccess access) {
|
||||
access.antarchy$setInTimeDilationMove(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "move", at = @At("RETURN"))
|
||||
private void antarchy$endTimeDilationMove(MoverType moverType, Vec3 movement, CallbackInfo ci) {
|
||||
if (this instanceof TimeDilationEntityAccess access) {
|
||||
access.antarchy$setInTimeDilationMove(false);
|
||||
}
|
||||
}
|
||||
|
||||
@ModifyVariable(method = "move", at = @At("HEAD"), argsOnly = true, ordinal = 0)
|
||||
private Vec3 antarchy$scaleTimeDilatedMovement(Vec3 movement) {
|
||||
Entity entity = (Entity) (Object) this;
|
||||
double rate = TimeDilationApi.getRate(entity);
|
||||
if (rate >= TimeDilationMath.NORMAL_RATE) {
|
||||
return movement;
|
||||
}
|
||||
return movement.scale(rate);
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package com.craisinlord.antarchy.mixins.time;
|
||||
|
||||
import com.craisinlord.antarchy.content.time.TimeDilationEntityAccess;
|
||||
import com.craisinlord.antarchy.content.time.TimeDilationMath;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
|
||||
@Mixin(Entity.class)
|
||||
public abstract class EntityTimeDilationStateMixin implements TimeDilationEntityAccess {
|
||||
@Unique
|
||||
private double antarchy$timeDilationRate = TimeDilationMath.NORMAL_RATE;
|
||||
@Unique
|
||||
private final Map<String, Double> antarchy$timeDilationTimerProgress = new HashMap<>();
|
||||
@Unique
|
||||
private boolean antarchy$inTimeDilationMove;
|
||||
|
||||
@Override
|
||||
public double antarchy$getTimeDilationRate() {
|
||||
return this.antarchy$timeDilationRate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void antarchy$setTimeDilationRate(double rate) {
|
||||
this.antarchy$timeDilationRate = TimeDilationMath.clampRate(rate);
|
||||
if (this.antarchy$timeDilationRate >= TimeDilationMath.NORMAL_RATE) {
|
||||
this.antarchy$timeDilationTimerProgress.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean antarchy$consumeTimeDilationTick(String timerKey, double rate) {
|
||||
double clampedRate = TimeDilationMath.clampRate(rate);
|
||||
if (clampedRate >= TimeDilationMath.NORMAL_RATE) {
|
||||
return true;
|
||||
}
|
||||
|
||||
double progress = this.antarchy$timeDilationTimerProgress.getOrDefault(timerKey, 0.0D) + clampedRate;
|
||||
int wholeTicks = (int) Math.floor(progress + 1.0E-9D);
|
||||
if (wholeTicks <= 0) {
|
||||
this.antarchy$timeDilationTimerProgress.put(timerKey, progress);
|
||||
return false;
|
||||
}
|
||||
|
||||
this.antarchy$timeDilationTimerProgress.put(timerKey, progress - wholeTicks);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean antarchy$isInTimeDilationMove() {
|
||||
return this.antarchy$inTimeDilationMove;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void antarchy$setInTimeDilationMove(boolean inMove) {
|
||||
this.antarchy$inTimeDilationMove = inMove;
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package com.craisinlord.antarchy.mixins.time;
|
||||
|
||||
import com.craisinlord.antarchy.content.client.ClientTimeDilationTicker;
|
||||
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
|
||||
import net.minecraft.client.renderer.entity.LivingEntityRenderer;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
||||
@Mixin(LivingEntityRenderer.class)
|
||||
public abstract class LivingEntityRendererAnimationTimeDilationMixin {
|
||||
@ModifyReturnValue(method = "getBob", at = @At("RETURN"))
|
||||
private float antarchy$slowIdleAnimation(float bob, LivingEntity entity, float partialTick) {
|
||||
return ClientTimeDilationTicker.dilateAnimationTime(entity, bob);
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package com.craisinlord.antarchy.mixins.time;
|
||||
|
||||
import com.craisinlord.antarchy.content.time.TimeDilationApi;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
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 LivingEntityTimeDilationMixin {
|
||||
@Inject(method = "updateSwingTime", at = @At("HEAD"), cancellable = true)
|
||||
private void antarchy$slowSwingTime(CallbackInfo ci) {
|
||||
Entity entity = (Entity) (Object) this;
|
||||
if (!TimeDilationApi.consumeTick(entity, "living_swing")) {
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "tickEffects", at = @At("HEAD"), cancellable = true)
|
||||
private void antarchy$slowEffectTicks(CallbackInfo ci) {
|
||||
Entity entity = (Entity) (Object) this;
|
||||
if (!TimeDilationApi.consumeTick(entity, "living_effects")) {
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "updatingUsingItem", at = @At("HEAD"), cancellable = true)
|
||||
private void antarchy$slowUsingItem(CallbackInfo ci) {
|
||||
Entity entity = (Entity) (Object) this;
|
||||
if (!TimeDilationApi.consumeTick(entity, "living_using_item")) {
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "swing(Lnet/minecraft/world/InteractionHand;Z)V", at = @At("HEAD"), cancellable = true)
|
||||
private void antarchy$slowSwingStart(InteractionHand hand, boolean updateSelf, CallbackInfo ci) {
|
||||
Entity entity = (Entity) (Object) this;
|
||||
if (!TimeDilationApi.consumeTick(entity, "living_swing_start")) {
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "updateUsingItem", at = @At("HEAD"), cancellable = true)
|
||||
private void antarchy$slowItemUseProgress(ItemStack stack, CallbackInfo ci) {
|
||||
Entity entity = (Entity) (Object) this;
|
||||
if (!TimeDilationApi.consumeTick(entity, "living_item_use_progress")) {
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package com.craisinlord.antarchy.mixins.time;
|
||||
|
||||
import com.craisinlord.antarchy.content.time.TimeDilationApi;
|
||||
import com.craisinlord.antarchy.content.time.TimeDilationMath;
|
||||
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.entity.Mob;
|
||||
import net.minecraft.world.entity.ai.control.LookControl;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
||||
@Mixin(LookControl.class)
|
||||
public abstract class LookControlTimeDilationMixin {
|
||||
@Shadow
|
||||
protected Mob mob;
|
||||
|
||||
@ModifyReturnValue(method = "rotateTowards", at = @At("RETURN"))
|
||||
private float antarchy$slowLookTurn(float result, float from, float to, float maxAngle) {
|
||||
double rate = TimeDilationApi.getRate(this.mob);
|
||||
if (rate >= TimeDilationMath.NORMAL_RATE) {
|
||||
return result;
|
||||
}
|
||||
return from + (float) (Mth.wrapDegrees(result - from) * rate);
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package com.craisinlord.antarchy.mixins.time;
|
||||
|
||||
import com.craisinlord.antarchy.content.time.TimeDilationApi;
|
||||
import net.minecraft.world.entity.PathfinderMob;
|
||||
import net.minecraft.world.entity.ai.goal.MeleeAttackGoal;
|
||||
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(MeleeAttackGoal.class)
|
||||
public abstract class MeleeAttackGoalTimeDilationMixin {
|
||||
@Shadow
|
||||
@Final
|
||||
protected PathfinderMob mob;
|
||||
|
||||
@Shadow
|
||||
private int ticksUntilNextAttack;
|
||||
|
||||
@Inject(method = "resetAttackCooldown", at = @At("RETURN"))
|
||||
private void antarchy$scaleMeleeCooldown(CallbackInfo ci) {
|
||||
this.ticksUntilNextAttack = TimeDilationApi.scaleCooldownTicks(this.mob, this.ticksUntilNextAttack);
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package com.craisinlord.antarchy.mixins.time;
|
||||
|
||||
import com.craisinlord.antarchy.content.time.TimeDilationApi;
|
||||
import com.craisinlord.antarchy.content.time.TimeDilationMath;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.Mob;
|
||||
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(Mob.class)
|
||||
public abstract class MobAttackTimeDilationMixin {
|
||||
@Unique
|
||||
private long antarchy$nextAttackTick = Long.MIN_VALUE;
|
||||
|
||||
@Inject(method = "doHurtTarget", at = @At("HEAD"), cancellable = true)
|
||||
private void antarchy$rateLimitAttack(Entity target, CallbackInfoReturnable<Boolean> cir) {
|
||||
Mob mob = (Mob) (Object) this;
|
||||
if (mob.level().isClientSide) {
|
||||
return;
|
||||
}
|
||||
double rate = TimeDilationApi.getRate(mob);
|
||||
if (rate >= TimeDilationMath.NORMAL_RATE) {
|
||||
return;
|
||||
}
|
||||
long now = mob.level().getGameTime();
|
||||
if (now < this.antarchy$nextAttackTick) {
|
||||
cir.setReturnValue(false);
|
||||
return;
|
||||
}
|
||||
this.antarchy$nextAttackTick = now + TimeDilationApi.scaleCooldownTicks(mob, 20);
|
||||
}
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
package com.craisinlord.antarchy.mixins.time;
|
||||
|
||||
import com.craisinlord.antarchy.content.client.ClientTimeDilationTicker;
|
||||
import com.craisinlord.antarchy.content.time.TimeDilationMath;
|
||||
import net.minecraft.client.particle.Particle;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
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.ModifyArgs;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.invoke.arg.Args;
|
||||
|
||||
@Mixin(Particle.class)
|
||||
public abstract class ParticleTimeDilationMixin {
|
||||
@Shadow
|
||||
protected int age;
|
||||
@Shadow
|
||||
protected double x;
|
||||
@Shadow
|
||||
protected double y;
|
||||
@Shadow
|
||||
protected double z;
|
||||
|
||||
@Unique
|
||||
private double antarchy$ageProgress;
|
||||
|
||||
@Unique
|
||||
private double antarchy$sampleRate() {
|
||||
return ClientTimeDilationTicker.rateAt(this.x, this.y, this.z);
|
||||
}
|
||||
|
||||
@ModifyArgs(
|
||||
method = "tick",
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/particle/Particle;move(DDD)V")
|
||||
)
|
||||
private void antarchy$slowParticleMovement(Args args) {
|
||||
double rate = this.antarchy$sampleRate();
|
||||
if (rate >= TimeDilationMath.NORMAL_RATE) {
|
||||
return;
|
||||
}
|
||||
args.set(0, (Double) args.get(0) * rate);
|
||||
args.set(1, (Double) args.get(1) * rate);
|
||||
args.set(2, (Double) args.get(2) * rate);
|
||||
}
|
||||
|
||||
@Redirect(
|
||||
method = "tick",
|
||||
at = @At(value = "FIELD", target = "Lnet/minecraft/client/particle/Particle;age:I", opcode = Opcodes.PUTFIELD)
|
||||
)
|
||||
private void antarchy$slowParticleAge(Particle self, int nextAge) {
|
||||
double rate = this.antarchy$sampleRate();
|
||||
if (rate >= TimeDilationMath.NORMAL_RATE) {
|
||||
this.age = nextAge;
|
||||
return;
|
||||
}
|
||||
this.antarchy$ageProgress += rate;
|
||||
if (this.antarchy$ageProgress >= 1.0D) {
|
||||
this.antarchy$ageProgress -= 1.0D;
|
||||
this.age = nextAge;
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package com.craisinlord.antarchy.mixins.time;
|
||||
|
||||
import com.craisinlord.antarchy.content.time.TimeDilationApi;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import net.minecraft.world.entity.item.PrimedTnt;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
||||
@Mixin(PrimedTnt.class)
|
||||
public abstract class PrimedTntTimeDilationMixin {
|
||||
@WrapOperation(
|
||||
method = "tick",
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/item/PrimedTnt;setFuse(I)V")
|
||||
)
|
||||
private void antarchy$slowFuse(PrimedTnt tnt, int fuse, Operation<Void> original) {
|
||||
if (TimeDilationApi.consumeTick(tnt, "primed_tnt_fuse")) {
|
||||
original.call(tnt, fuse);
|
||||
}
|
||||
}
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
package com.craisinlord.antarchy.mixins.time;
|
||||
|
||||
import com.craisinlord.antarchy.content.time.TimeDilationApi;
|
||||
import com.craisinlord.antarchy.content.time.TimeDilationMath;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.projectile.AbstractArrow;
|
||||
import net.minecraft.world.entity.projectile.AbstractHurtingProjectile;
|
||||
import net.minecraft.world.entity.projectile.ThrowableProjectile;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin({AbstractArrow.class, ThrowableProjectile.class, AbstractHurtingProjectile.class})
|
||||
public abstract class ProjectileMovementTimeDilationMixin {
|
||||
@Unique
|
||||
private Vec3 antarchy$preTickPos;
|
||||
@Unique
|
||||
private Vec3 antarchy$preTickDelta;
|
||||
|
||||
@Inject(method = "tick", at = @At("HEAD"))
|
||||
private void antarchy$recordPreTickMotion(CallbackInfo ci) {
|
||||
Entity entity = (Entity) (Object) this;
|
||||
if (TimeDilationApi.getRate(entity) < TimeDilationMath.NORMAL_RATE) {
|
||||
this.antarchy$preTickPos = entity.position();
|
||||
this.antarchy$preTickDelta = entity.getDeltaMovement();
|
||||
} else {
|
||||
this.antarchy$preTickPos = null;
|
||||
this.antarchy$preTickDelta = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "tick", at = @At("RETURN"))
|
||||
private void antarchy$scaleTickDisplacement(CallbackInfo ci) {
|
||||
Vec3 pre = this.antarchy$preTickPos;
|
||||
Vec3 preDelta = this.antarchy$preTickDelta;
|
||||
this.antarchy$preTickPos = null;
|
||||
this.antarchy$preTickDelta = null;
|
||||
if (pre == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Entity entity = (Entity) (Object) this;
|
||||
if (entity.isRemoved()) {
|
||||
return;
|
||||
}
|
||||
double rate = TimeDilationApi.getRate(entity);
|
||||
if (rate >= TimeDilationMath.NORMAL_RATE) {
|
||||
return;
|
||||
}
|
||||
|
||||
Vec3 actual = entity.position().subtract(pre);
|
||||
if (actual.lengthSqr() < 1.0E-12D) {
|
||||
return;
|
||||
}
|
||||
if (preDelta != null && actual.lengthSqr() + 1.0E-9D < preDelta.lengthSqr()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Vec3 lerped = pre.add(actual.scale(rate));
|
||||
entity.setPos(lerped.x, lerped.y, lerped.z);
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package com.craisinlord.antarchy.mixins.time;
|
||||
|
||||
import com.craisinlord.antarchy.content.time.TimeDilationApi;
|
||||
import net.minecraft.world.entity.Mob;
|
||||
import net.minecraft.world.entity.ai.goal.RangedAttackGoal;
|
||||
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(RangedAttackGoal.class)
|
||||
public abstract class RangedAttackGoalTimeDilationMixin {
|
||||
@Shadow
|
||||
@Final
|
||||
private Mob mob;
|
||||
|
||||
@Inject(method = "tick", at = @At("HEAD"), cancellable = true)
|
||||
private void antarchy$slowRangedGoalTick(CallbackInfo ci) {
|
||||
if (!TimeDilationApi.consumeTick(this.mob, "ranged_goal_tick")) {
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package com.craisinlord.antarchy.mixins.time;
|
||||
|
||||
import com.craisinlord.antarchy.content.time.TimeDilationApi;
|
||||
import net.minecraft.world.entity.Mob;
|
||||
import net.minecraft.world.entity.ai.goal.RangedBowAttackGoal;
|
||||
import net.minecraft.world.entity.monster.Monster;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(RangedBowAttackGoal.class)
|
||||
public abstract class RangedBowAttackGoalTimeDilationMixin {
|
||||
@Unique
|
||||
private Mob antarchy$timeDilationMob;
|
||||
|
||||
@Inject(method = "<init>(Lnet/minecraft/world/entity/monster/Monster;DIF)V", at = @At("RETURN"))
|
||||
private void antarchy$captureMob(Monster mob, double speedModifier, int attackIntervalMin, float attackRadius, CallbackInfo ci) {
|
||||
this.antarchy$timeDilationMob = mob;
|
||||
}
|
||||
|
||||
@Inject(method = "tick", at = @At("HEAD"), cancellable = true)
|
||||
private void antarchy$slowRangedBowGoalTick(CallbackInfo ci) {
|
||||
if (this.antarchy$timeDilationMob != null && !TimeDilationApi.consumeTick(this.antarchy$timeDilationMob, "ranged_bow_goal_tick")) {
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package com.craisinlord.antarchy.mixins.time;
|
||||
|
||||
import com.craisinlord.antarchy.content.client.ClientTimeDilationTicker;
|
||||
import com.craisinlord.antarchy.content.time.TimeDilationMath;
|
||||
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
|
||||
import net.minecraft.client.resources.sounds.SoundInstance;
|
||||
import net.minecraft.client.sounds.SoundEngine;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
||||
@Mixin(SoundEngine.class)
|
||||
public abstract class SoundEngineTimeDilationMixin {
|
||||
@ModifyReturnValue(method = "calculatePitch", at = @At("RETURN"))
|
||||
private float antarchy$dilatePitch(float pitch, SoundInstance sound) {
|
||||
double rate = ClientTimeDilationTicker.rateAt(sound.getX(), sound.getY(), sound.getZ());
|
||||
if (rate >= TimeDilationMath.NORMAL_RATE) {
|
||||
return pitch;
|
||||
}
|
||||
return (float) (pitch * Math.max(TimeDilationMath.MIN_RATE, rate));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user