Merge pull request #6 from craisinlord/beta6

Beta V6 release
This commit is contained in:
craisinlord authored and GitHub committed 2026-07-06 10:00:22 -05:00
commit 66e5a77adb
598 files changed
+74634 -18076

No files matched your search

+13 -12
View File
@@ -7,26 +7,27 @@ dependencies {
// modImplementation("curse.maven:create-${create_curseforge_id}:${create_curseforge_file_id}")
// modImplementation("curse.maven:selene-499980:4754433")
compileOnly("curse.maven:intapi-${project.intapi_curseforge_id}:${project.intapi_curseforge_file_id_neoforge}")
compileOnly("curse.maven:create-${project.create_curseforge_id_neoforge}:${project.create_curseforge_file_id_neoforge}")
compileOnly("curse.maven:marvel-${project.marvel_curseforge_id}:${project.marvel_curseforge_file_id}")
compileOnly("curse.maven:stellaris-${project.stellaris_curseforge_id}:${project.stellaris_curseforge_file_id}")
compileOnly("curse.maven:aperture-${project.aperture_curseforge_id}:${project.aperture_curseforge_file_id}")
compileOnly("curse.maven:inventory_pets-${project.inventory_pets_curseforge_id}:${project.inventory_pets_curseforge_file_id}")
compileOnly("curse.maven:cataclysm-${project.cataclysm_curseforge_id}:${project.cataclysm_curseforge_file_id}")
compileOnly("curse.maven:iaf-${project.iaf_curseforge_id}:${project.iaf_curseforge_file_id}")
compileOnly("curse.maven:backpack-${project.backpack_curseforge_id}:${project.backpack_curseforge_file_id}")
// compileOnly("curse.maven:create-${project.create_curseforge_id_neoforge}:${project.create_curseforge_file_id_neoforge}")
// compileOnly("curse.maven:marvel-${project.marvel_curseforge_id}:${project.marvel_curseforge_file_id}")
// compileOnly("curse.maven:stellaris-${project.stellaris_curseforge_id}:${project.stellaris_curseforge_file_id}")
// compileOnly("curse.maven:aperture-${project.aperture_curseforge_id}:${project.aperture_curseforge_file_id}")
// compileOnly("curse.maven:inventory_pets-${project.inventory_pets_curseforge_id}:${project.inventory_pets_curseforge_file_id}")
// compileOnly("curse.maven:cataclysm-${project.cataclysm_curseforge_id}:${project.cataclysm_curseforge_file_id}")
// compileOnly("curse.maven:iaf-${project.iaf_curseforge_id}:${project.iaf_curseforge_file_id}")
// compileOnly("curse.maven:backpack-${project.backpack_curseforge_id}:${project.backpack_curseforge_file_id}")
// compileOnly("curse.maven:regions_unexplored-${project.regions_unexplored_curseforge_id}:${project.regions_unexplored_curseforge_file_id}")
// compileOnly("curse.maven:geckolib-${project.geckolib_curseforge_id}:${project.geckolib_curseforge_file_id}")
compileOnly(files("$rootDir/infinite_dimensions_jars/infinity-2.7.1+1.21.1-neoforge.jar"))
compileOnly(files("$rootDir/infinite_dimensions_jars/infinity-2.7.1+1.21.1-fabric.jar"))
compileOnly(files("$rootDir/infinite_dimensions_jars/infinity-2.7.1+1.21.1-neoforge-sources.jar"))
compileOnly(files("$rootDir/infinite_dimensions_jars/gravity-api-1.0.7-1.20.1.jar"))
compileOnly(files("$rootDir/infinite_dimensions_jars/Cloro's Gravity Gun Mod (Release 1.3.1).jar"))
// compileOnly(files("$rootDir/infinite_dimensions_jars/Cloro's Gravity Gun Mod (Release 1.3.1).jar"))
// compileOnly(files("$rootDir/infinite_dimensions_jars/antarchy-1.0.0+1.21.1-neoforge.jar"))
compileOnly(files("$rootDir/infinite_dimensions_jars/VanillaBackport-neoforge-1.21.1-1.1.7.6.jar"))
compileOnly(files("$rootDir/infinite_dimensions_jars/soul-fire-d-neoforge-1.21-6.1.0.jar"))
compileOnly(files("$rootDir/infinite_dimensions_jars/prometheus-neoforge-1.21-1.2.5.jar"))
// compileOnly(files("$rootDir/infinite_dimensions_jars/VanillaBackport-neoforge-1.21.1-1.1.7.6.jar"))
// compileOnly(files("$rootDir/infinite_dimensions_jars/soul-fire-d-neoforge-1.21-6.1.0.jar"))
// compileOnly(files("$rootDir/infinite_dimensions_jars/prometheus-neoforge-1.21-1.2.5.jar"))
compileOnly(files("$rootDir/infinite_dimensions_jars/sable-neoforge-1.21.1-2.0.3.jar"))
compileOnly(files("$rootDir/infinite_dimensions_jars/antarchy-1.0.0+1.21.1-neoforge.jar"))
compileOnly("software.bernie.geckolib:geckolib-neoforge-${minecraft_version}:${geckolib_version}")
//
// compileOnly group: 'org.ow2.asm', name: 'asm-tree', version: '9.4'
@@ -1,5 +1,6 @@
package com.craisinlord.antarchy;
import com.craisinlord.antarchy.content.AttributeRangeInit;
import com.craisinlord.integrated_api.events.lifecycle.ServerGoingToStartEvent;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -10,6 +11,7 @@ public class Antarchy {
public static final Logger LOGGER = LogManager.getLogger();
public static void init() {
AttributeRangeInit.apply();
ServerGoingToStartEvent.EVENT.addListener(Antarchy::serverAboutToStart);
}
private static void serverAboutToStart(final ServerGoingToStartEvent event) {
@@ -5,7 +5,7 @@ import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.Level;
public final class AntarchySettings {
private static boolean disableInfinityBookPortalCreation = false;
private static boolean disableInfinityBookPortalCreation = true;
private static boolean easterBunnyEnabled = true;
private static int easterBunnyNaturalSpawnChancePercent = 1;
private static boolean rainbowAntsLeadToInfinityDimensions = true;
@@ -31,7 +31,7 @@ public final class AntarchySettings {
private static double waspHealth = 16.0D;
private static double waspAttackDamage = 4.0D;
private static double waspMovementSpeed = 0.39D;
private static double bomberHealth = 10.0D;
private static double bomberHealth = 15.0D;
private static double bomberAttackDamage = 4.0D;
private static double bomberExplosionDamage = 8.0D;
private static double bomberExplosionRadius = 4.0D;
@@ -90,6 +90,8 @@ public final class AntarchySettings {
private static ResourceKey<Level> rainbowAntNonInfinityFallbackDimension = dimensionKey("antarchy:elythia");
private static double krakenHealth = 1500.0D;
private static double krakenAttackDamage = 45.0D;
private static double octopusBombHealth = 100.0D;
private static double octopusBombAttackDamage = 16.0D;
private static double brutalflyHealth = 400.0D;
private static double brutalflySwipeDamage = 15.0D;
private static double brutalflySpitDamage = 5.0D;
@@ -98,6 +100,12 @@ public final class AntarchySettings {
private static double mantisMovementSpeed = 0.42D;
private static double mantisFlyingSpeed = 0.77D;
private static boolean mantisIgnoreLightLevel = false;
private static double alphaMantisHealth = 500.0D;
private static double alphaMantisAttackDamage = 18.0D;
private static double alphaMantisMovementSpeed = 0.42D;
private static double alphaMantisFlyingSpeed = 0.77D;
private static int alphaMantisSummonIntervalTicks = 300;
private static int alphaMantisMaxMinions = 4;
private static double krakenProjectileDamageTakenMultiplier = 0.5D;
private static boolean krakenSquidSpawnEnabled = true;
private static boolean krakenMassSpawnEnabled = true;
@@ -143,12 +151,12 @@ public final class AntarchySettings {
private static double nightmareSwordBaseDamage = 15.0D;
private static double nightmareSwordScalingFactor = 1.0D;
private static double nightmareSwordScalingFactor = 1.5D;
private static boolean basiliskPetrifyingGazeEnabled = true;
private static int basiliskSpawnMaxLightLevel = 5;
private static double basiliskHealth = 150.0D;
private static double basiliskAttackDamage = 36.0D;
private static double basiliskMovementSpeed = 0.30D;
private static double basiliskMovementSpeed = 0.225D;
private static double basiliskArmor = 6.0D;
private static double basiliskKnockbackResistance = 0.5D;
private static double basiliskFollowRange = 28.0D;
@@ -225,23 +233,23 @@ public final class AntarchySettings {
private static final int reverieAmbientTargetMaxAgeTicks = 40;
private static final int reverieAmbientPulseIntervalMinTicks = 12;
private static final int reverieAmbientPulseIntervalMaxTicks = 19;
private static double emperorScorpionHealth = 100.0D;
private static double emperorScorpionAttackDamage = 12.0D;
private static double emperorScorpionHealth = 400.0D;
private static double emperorScorpionAttackDamage = 28.0D;
private static double emperorScorpionMovementSpeed = 0.32D;
private static double emperorScorpionArmor = 6.0D;
private static double emperorScorpionKnockbackResistance = 0.6D;
private static double emperorScorpionFollowRange = 32.0D;
private static double emperorScorpionArmor = 25.0D;
private static double emperorScorpionKnockbackResistance = 0.8D;
private static double emperorScorpionFollowRange = 40.0D;
private static int emperorScorpionXpReward = 25;
private static int emperorScorpionClawAnimTicks = 12;
private static int emperorScorpionClawHitTick = 6;
private static int emperorScorpionClawCooldownTicks = 16;
private static int emperorScorpionStingAnimTicks = 18;
private static int emperorScorpionStingHitTick = 9;
private static int emperorScorpionStingCooldownTicks = 42;
private static int emperorScorpionPoisonTicks = 100;
private static int emperorScorpionWeaknessTicks = 80;
private static int emperorScorpionSummonIntervalTicks = 180;
private static int emperorScorpionMaxSummonedScorpions = 4;
private static int emperorScorpionClawAnimTicks = 23;
private static int emperorScorpionClawHitTick = 12;
private static int emperorScorpionClawCooldownTicks = 28;
private static int emperorScorpionStingAnimTicks = 25;
private static int emperorScorpionStingHitTick = 13;
private static int emperorScorpionStingCooldownTicks = 60;
private static int emperorScorpionPoisonTicks = 300;
private static int emperorScorpionWeaknessTicks = 120;
private static int emperorScorpionSummonIntervalTicks = 140;
private static int emperorScorpionMaxSummonedScorpions = 8;
private static double scorpionHealth = 30.0D;
private static double scorpionAttackDamage = 5.0D;
private static double scorpionMovementSpeed = 0.29D;
@@ -271,6 +279,10 @@ public final class AntarchySettings {
private static double bedBugAttackLungeHorizontalSpeed = 0.42D;
private static double bedBugAttackLungeVerticalSpeed = 0.1D;
private static float bedBugHealAmount = 2.0F;
private static double jumpyBugHealth = 100.0D;
private static double jumpyBugPounceDamage = 12.0D;
private static double jumpyBugLatchDamage = 1.0D;
private static double jumpyBugCamouflageAlpha = 0.18D;
private static double brutalflyArmor = 10.0D;
private static double brutalflyArmorToughness = 4.0D;
private static double brutalflyKnockbackResistance = 0.7D;
@@ -426,6 +438,7 @@ public final class AntarchySettings {
private static double ductTapeRepairPercentPerUse = 0.25D;
private static double ultimateArmorKnockbackResistance = 0.1D;
private static double nightmareArmorKnockbackResistance = 0.1D;
private static double primordialArmorKnockbackPerPiece = 0.5D;
private static int fallenKingCrownArmorValue = 2;
private static double fallenKingCrownArmorToughness = 0.0D;
private static boolean elythiaFireflyParticlesEnabled = true;
@@ -447,14 +460,25 @@ public final class AntarchySettings {
private static double toreterrorHealth = 300.0D;
private static double toreterrorJumpAttackDamage = 14.0D;
private static double toreterrorJumpAttackKnockback = 2.5D;
private static double toreterrorSpinDamage = 8.0D;
private static double toreterrorSpinKnockback = 1.5D;
private static double toreterrorSpinDamage = 6.0D;
private static double toreterrorSpinKnockback = 0.9D;
private static double toreterrorRangedWaterBombChance = 0.5D;
private static double toreterrorProjectileDamageMultiplier = 0.5D;
private static double waterBombDamage = 6.0D;
private static int waterBombLifetimeTicks = 120;
private static double waterBombGravity = 0.12D;
private static double waterBombKnockback = 1.2D;
private static double waterCannonCooldownSeconds = 1.5D;
private static double creepingHorrorHealth = 15.0D;
private static double creepingHorrorAttackDamage = 6.0D;
private static double lurkingTerrorHealth = 15.0D;
private static double lurkingTerrorAttackDamage = 6.0D;
private static double cheepHealth = 8.0D;
private static double cheepAttackDamage = 3.0D;
private static double dorrieHealth = 60.0D;
private static double herculesBeetleHealth = 500.0D;
private static double herculesBeetleAttackDamage = 30.0D;
private static double herculesBeetleChargeDamage = 40.0D;
private AntarchySettings() {
}
@@ -723,6 +747,18 @@ public final class AntarchySettings {
return krakenAttackDamage;
}
public static double octopusBombHealth() {
return octopusBombHealth;
}
public static void setOctopusBombHealth(double value) { octopusBombHealth = value; }
public static double octopusBombAttackDamage() {
return octopusBombAttackDamage;
}
public static void setOctopusBombAttackDamage(double value) { octopusBombAttackDamage = value; }
public static double brutalflyHealth() {
return brutalflyHealth;
}
@@ -759,6 +795,30 @@ public final class AntarchySettings {
mantisIgnoreLightLevel = value;
}
public static double alphaMantisHealth() {
return alphaMantisHealth;
}
public static double alphaMantisAttackDamage() {
return alphaMantisAttackDamage;
}
public static double alphaMantisMovementSpeed() {
return alphaMantisMovementSpeed;
}
public static double alphaMantisFlyingSpeed() {
return alphaMantisFlyingSpeed;
}
public static int alphaMantisSummonIntervalTicks() {
return alphaMantisSummonIntervalTicks;
}
public static int alphaMantisMaxMinions() {
return alphaMantisMaxMinions;
}
public static double waspHealth() {
return waspHealth;
}
@@ -1163,6 +1223,30 @@ public final class AntarchySettings {
mantisFlyingSpeed = value;
}
public static void setAlphaMantisHealth(double value) {
alphaMantisHealth = value;
}
public static void setAlphaMantisAttackDamage(double value) {
alphaMantisAttackDamage = value;
}
public static void setAlphaMantisMovementSpeed(double value) {
alphaMantisMovementSpeed = value;
}
public static void setAlphaMantisFlyingSpeed(double value) {
alphaMantisFlyingSpeed = value;
}
public static void setAlphaMantisSummonIntervalTicks(int value) {
alphaMantisSummonIntervalTicks = value;
}
public static void setAlphaMantisMaxMinions(int value) {
alphaMantisMaxMinions = value;
}
public static void setKrakenProjectileDamageTakenMultiplier(double value) {
krakenProjectileDamageTakenMultiplier = value;
}
@@ -1810,6 +1894,18 @@ public final class AntarchySettings {
public static float bedBugHealAmount() { return bedBugHealAmount; }
public static void setBedBugHealAmount(float value) { bedBugHealAmount = value; }
public static double jumpyBugHealth() { return jumpyBugHealth; }
public static void setJumpyBugHealth(double value) { jumpyBugHealth = value; }
public static double jumpyBugPounceDamage() { return jumpyBugPounceDamage; }
public static void setJumpyBugPounceDamage(double value) { jumpyBugPounceDamage = value; }
public static double jumpyBugLatchDamage() { return jumpyBugLatchDamage; }
public static void setJumpyBugLatchDamage(double value) { jumpyBugLatchDamage = value; }
public static double jumpyBugCamouflageAlpha() { return jumpyBugCamouflageAlpha; }
public static void setJumpyBugCamouflageAlpha(double value) { jumpyBugCamouflageAlpha = value; }
public static double brutalflyArmor() { return brutalflyArmor; }
public static void setBrutalflyArmor(double value) { brutalflyArmor = value; }
@@ -2275,6 +2371,9 @@ public final class AntarchySettings {
public static double nightmareArmorKnockbackResistance() { return nightmareArmorKnockbackResistance; }
public static void setNightmareArmorKnockbackResistance(double value) { nightmareArmorKnockbackResistance = value; }
public static double primordialArmorKnockbackPerPiece() { return primordialArmorKnockbackPerPiece; }
public static void setPrimordialArmorKnockbackPerPiece(double value) { primordialArmorKnockbackPerPiece = value; }
public static int fallenKingCrownArmorValue() { return fallenKingCrownArmorValue; }
public static void setFallenKingCrownArmorValue(int value) { fallenKingCrownArmorValue = value; }
@@ -2330,6 +2429,8 @@ public final class AntarchySettings {
public static void setToreterrorSpinKnockback(double v) { toreterrorSpinKnockback = v; }
public static double toreterrorRangedWaterBombChance() { return toreterrorRangedWaterBombChance; }
public static void setToreterrorRangedWaterBombChance(double v) { toreterrorRangedWaterBombChance = v; }
public static double toreterrorProjectileDamageMultiplier() { return toreterrorProjectileDamageMultiplier; }
public static void setToreterrorProjectileDamageMultiplier(double v) { toreterrorProjectileDamageMultiplier = v; }
public static double waterBombDamage() { return waterBombDamage; }
public static void setWaterBombDamage(double v) { waterBombDamage = v; }
public static int waterBombLifetimeTicks() { return waterBombLifetimeTicks; }
@@ -2340,4 +2441,25 @@ public final class AntarchySettings {
public static void setWaterBombKnockback(double v) { waterBombKnockback = v; }
public static double waterCannonCooldownSeconds() { return waterCannonCooldownSeconds; }
public static void setWaterCannonCooldownSeconds(double v) { waterCannonCooldownSeconds = v; }
public static double creepingHorrorHealth() { return creepingHorrorHealth; }
public static void setCreepingHorrorHealth(double v) { creepingHorrorHealth = v; }
public static double creepingHorrorAttackDamage() { return creepingHorrorAttackDamage; }
public static void setCreepingHorrorAttackDamage(double v) { creepingHorrorAttackDamage = v; }
public static double lurkingTerrorHealth() { return lurkingTerrorHealth; }
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 cheepHealth() { return cheepHealth; }
public static void setCheepHealth(double v) { cheepHealth = v; }
public static double cheepAttackDamage() { return cheepAttackDamage; }
public static void setCheepAttackDamage(double v) { cheepAttackDamage = v; }
public static double dorrieHealth() { return dorrieHealth; }
public static double herculesBeetleHealth() { return herculesBeetleHealth; }
public static void setHerculesBeetleHealth(double v) { herculesBeetleHealth = v; }
public static double herculesBeetleAttackDamage() { return herculesBeetleAttackDamage; }
public static void setHerculesBeetleAttackDamage(double v) { herculesBeetleAttackDamage = v; }
public static double herculesBeetleChargeDamage() { return herculesBeetleChargeDamage; }
public static void setHerculesBeetleChargeDamage(double v) { herculesBeetleChargeDamage = v; }
public static void setDorrieHealth(double v) { dorrieHealth = v; }
}
@@ -0,0 +1,54 @@
package com.craisinlord.antarchy.config;
import com.craisinlord.antarchy.Antarchy;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public final class ConfigResetGuard {
public static final int RESET_GENERATION = 1;
private static final String MARKER_FILE_NAME = ".config_generation";
private ConfigResetGuard() {
}
public static void wipeIfNeeded(Path configDir, Path... configFiles) {
Path markerPath = configDir.resolve(MARKER_FILE_NAME);
if (readGeneration(markerPath) >= RESET_GENERATION) {
return;
}
for (Path configFile : configFiles) {
try {
if (Files.deleteIfExists(configFile)) {
Antarchy.LOGGER.info("Reset config file {} to defaults (config generation {})", configFile, RESET_GENERATION);
}
} catch (IOException e) {
Antarchy.LOGGER.warn("Failed to delete config file {} during config reset", configFile, e);
}
}
writeGeneration(markerPath);
}
private static int readGeneration(Path markerPath) {
if (!Files.exists(markerPath)) {
return 0;
}
try {
return Integer.parseInt(Files.readString(markerPath).trim());
} catch (IOException | NumberFormatException e) {
return 0;
}
}
private static void writeGeneration(Path markerPath) {
try {
Files.createDirectories(markerPath.getParent());
Files.writeString(markerPath, Integer.toString(RESET_GENERATION));
} catch (IOException e) {
Antarchy.LOGGER.warn("Failed to write config generation marker at {}", markerPath, e);
}
}
}
@@ -5,9 +5,13 @@ import com.craisinlord.antarchy.content.entity.BomberEntity;
import com.craisinlord.antarchy.content.entity.ButterflyEntity;
import com.craisinlord.antarchy.content.entity.CaterpillarEntity;
import com.craisinlord.antarchy.content.entity.EasterBunnyEntity;
import com.craisinlord.antarchy.content.entity.HerculesBeetleEntity;
import com.craisinlord.antarchy.content.entity.HushProjectileEntity;
import com.craisinlord.antarchy.content.entity.StinkBugEntity;
import com.craisinlord.antarchy.content.entity.WaterBombEntity;
import com.craisinlord.antarchy.content.entity.TriffidEntity;
import com.craisinlord.antarchy.content.entity.CreepingHorrorEntity;
import com.craisinlord.antarchy.content.entity.LurkingTerrorEntity;
import com.craisinlord.antarchy.content.entity.ToreterrorEntity;
import com.craisinlord.antarchy.content.entity.WaspEntity;
import com.craisinlord.antarchy.content.entity.brutalfly.BrutalflyEntity;
@@ -15,6 +19,7 @@ import com.craisinlord.antarchy.content.entity.brutalfly.BrutalflyOrbEntity;
import com.craisinlord.antarchy.content.block.entity.HushweedBlockEntity;
import com.craisinlord.antarchy.content.entity.flying_squirrel.FlyingSquirrelEntity;
import com.craisinlord.antarchy.content.entity.MissileSquidEntity;
import com.craisinlord.antarchy.content.entity.OctopusBombEntity;
import com.craisinlord.antarchy.content.entity.kraken.KrakenEntity;
import com.craisinlord.antarchy.content.entity.MolewormEntity;
import com.craisinlord.antarchy.content.entity.MantisEntity;
@@ -25,6 +30,7 @@ import com.craisinlord.antarchy.content.block.entity.DreamCampfireBlockEntity;
import com.craisinlord.antarchy.content.block.entity.WaspNestBlockEntity;
import java.util.function.Supplier;
import net.minecraft.core.Holder;
import net.minecraft.core.particles.SimpleParticleType;
import net.minecraft.world.effect.MobEffect;
import net.minecraft.world.entity.ai.attributes.Attribute;
import net.minecraft.world.entity.EntityType;
@@ -49,6 +55,8 @@ public final class AntarchyObjects {
@SuppressWarnings("unchecked")
public static Supplier<EntityType<MissileSquidEntity>> MISSILE_SQUID = (Supplier<EntityType<MissileSquidEntity>>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<EntityType<OctopusBombEntity>> OCTOPUS_BOMB = (Supplier<EntityType<OctopusBombEntity>>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<EntityType<MolewormEntity>> MOLEWORM = (Supplier<EntityType<MolewormEntity>>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<EntityType<MantisEntity>> MANTIS = (Supplier<EntityType<MantisEntity>>) UNBOUND;
@@ -79,6 +87,20 @@ public final class AntarchyObjects {
@SuppressWarnings("unchecked")
public static Supplier<EntityType<WaterBombEntity>> WATER_BOMB = (Supplier<EntityType<WaterBombEntity>>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<EntityType<CreepingHorrorEntity>> CREEPING_HORROR = (Supplier<EntityType<CreepingHorrorEntity>>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<EntityType<LurkingTerrorEntity>> LURKING_TERROR = (Supplier<EntityType<LurkingTerrorEntity>>) 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.CheepEntity>> CHEEP = (Supplier<EntityType<com.craisinlord.antarchy.content.entity.CheepEntity>>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<EntityType<com.craisinlord.antarchy.content.entity.DorrieEntity>> DORRIE = (Supplier<EntityType<com.craisinlord.antarchy.content.entity.DorrieEntity>>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<EntityType<HerculesBeetleEntity>> HERCULES_BEETLE = (Supplier<EntityType<HerculesBeetleEntity>>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<net.minecraft.world.item.Item> CHEEP_ITEM = (Supplier<net.minecraft.world.item.Item>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Block> DUPLICATOR_LOG = (Supplier<Block>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Block> DUPLICATOR_SAPLING = (Supplier<Block>) UNBOUND;
@@ -103,6 +125,12 @@ public final class AntarchyObjects {
@SuppressWarnings("unchecked")
public static Supplier<Block> CRACKED_SHELLSTONE_BRICKS = (Supplier<Block>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Block> MOSSY_SHELLSTONE_BRICK_STAIRS = (Supplier<Block>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Block> MOSSY_SHELLSTONE_BRICK_SLAB = (Supplier<Block>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Block> MOSSY_SHELLSTONE_BRICK_WALL = (Supplier<Block>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Block> SHELLSTONE_STAIRS = (Supplier<Block>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Block> SHELLSTONE_SLAB = (Supplier<Block>) UNBOUND;
@@ -129,6 +157,10 @@ public final class AntarchyObjects {
@SuppressWarnings("unchecked")
public static Supplier<Block> NYXITE_SPIKE = (Supplier<Block>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Block> CHITEN_BLOCK = (Supplier<Block>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Block> CHITEN_SPIKE = (Supplier<Block>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Block> POTENT_NYXITE = (Supplier<Block>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Block> ANTIMETAL = (Supplier<Block>) UNBOUND;
@@ -153,6 +185,8 @@ public final class AntarchyObjects {
@SuppressWarnings("unchecked")
public static Supplier<Item> REVERIE_BOTTLE = (Supplier<Item>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Item> STINK_BUG_ITEM = (Supplier<Item>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Holder<MobEffect>> DREAD = (Supplier<Holder<MobEffect>>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Holder<Attribute>> DOUBLE_DAMAGE_CHANCE = (Supplier<Holder<Attribute>>) UNBOUND;
@@ -165,6 +199,8 @@ public final class AntarchyObjects {
@SuppressWarnings("unchecked")
public static Supplier<Holder<MobEffect>> INVERTED_EFFECT = (Supplier<Holder<MobEffect>>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Holder<MobEffect>> STINKY_EFFECT = (Supplier<Holder<MobEffect>>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Block> OURANWOOD_ACORN_BLOCK = (Supplier<Block>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Block> MOSSY_OURANWOOD_LOG = (Supplier<Block>) UNBOUND;
@@ -179,12 +215,18 @@ public final class AntarchyObjects {
@SuppressWarnings("unchecked")
public static Supplier<Block> BED_BUG_EGG = (Supplier<Block>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Block> CREEPING_HORROR_EGGS = (Supplier<Block>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Block> LURKING_TERROR_EGGS = (Supplier<Block>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Block> WASP_NEST = (Supplier<Block>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Block> HUSHWEED = (Supplier<Block>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Block> SQUIRREL_NEST_BLOCK = (Supplier<Block>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Block> BRUTALFLY_COCOON_SPAWNER = (Supplier<Block>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<BlockEntityType<AntNestBlockEntity>> ANT_NEST_BLOCK_ENTITY = (Supplier<BlockEntityType<AntNestBlockEntity>>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<BlockEntityType<DreamCampfireBlockEntity>> DREAM_CAMPFIRE_BLOCK_ENTITY = (Supplier<BlockEntityType<DreamCampfireBlockEntity>>) UNBOUND;
@@ -192,6 +234,10 @@ public final class AntarchyObjects {
public static Supplier<BlockEntityType<WaspNestBlockEntity>> WASP_NEST_BLOCK_ENTITY = (Supplier<BlockEntityType<WaspNestBlockEntity>>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<BlockEntityType<HushweedBlockEntity>> HUSHWEED_BLOCK_ENTITY = (Supplier<BlockEntityType<HushweedBlockEntity>>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<SimpleParticleType> STINKY_GAS = (Supplier<SimpleParticleType>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<SimpleParticleType> STINKY_FLY = (Supplier<SimpleParticleType>) UNBOUND;
private AntarchyObjects() {
}
@@ -216,6 +262,7 @@ public final class AntarchyObjects {
Supplier<EntityType<HushProjectileEntity>> hushProjectile,
Supplier<EntityType<ToreterrorEntity>> toreterror,
Supplier<EntityType<WaterBombEntity>> waterBomb,
Supplier<EntityType<com.craisinlord.antarchy.content.entity.CheepEntity>> cheep,
Supplier<Block> duplicatorLog,
Supplier<Block> duplicatorSapling,
Supplier<Block> ductTape,
@@ -228,6 +275,9 @@ public final class AntarchyObjects {
Supplier<Block> chiseledShellstone,
Supplier<Block> mossyShellstoneBricks,
Supplier<Block> crackedShellstoneBricks,
Supplier<Block> mossyShellstoneBrickStairs,
Supplier<Block> mossyShellstoneBrickSlab,
Supplier<Block> mossyShellstoneBrickWall,
Supplier<Block> shellstoneStairs,
Supplier<Block> shellstoneSlab,
Supplier<Block> shellstoneWall,
@@ -256,6 +306,7 @@ public final class AntarchyObjects {
Supplier<Holder<MobEffect>> dread,
Supplier<Holder<MobEffect>> paralyzedEffect,
Supplier<Holder<MobEffect>> invertedEffect,
Supplier<Holder<MobEffect>> stinkyEffect,
Supplier<Block> ouranwoodAcornBlock,
Supplier<Block> mossyOuranwoodLog,
Supplier<Block> mossyOuranwoodWood,
@@ -266,10 +317,13 @@ public final class AntarchyObjects {
Supplier<Block> waspNest,
Supplier<Block> hushweed,
Supplier<Block> squirrelNestBlock,
Supplier<Block> brutalflyCocoonSpawner,
Supplier<BlockEntityType<AntNestBlockEntity>> antNestBlockEntity,
Supplier<BlockEntityType<DreamCampfireBlockEntity>> dreamCampfireBlockEntity,
Supplier<BlockEntityType<WaspNestBlockEntity>> waspNestBlockEntity,
Supplier<BlockEntityType<HushweedBlockEntity>> hushweedBlockEntity,
Supplier<SimpleParticleType> stinkyGas,
Supplier<SimpleParticleType> stinkyFly,
Supplier<Holder<Attribute>> doubleDamageChance,
Supplier<Holder<Attribute>> bloodglassMaxHearts,
Supplier<Holder<MobEffect>> bloodglassWard
@@ -293,6 +347,7 @@ public final class AntarchyObjects {
HUSH_PROJECTILE = hushProjectile;
TORETERROR = toreterror;
WATER_BOMB = waterBomb;
CHEEP = cheep;
DUPLICATOR_LOG = duplicatorLog;
DUPLICATOR_SAPLING = duplicatorSapling;
DUCT_TAPE = ductTape;
@@ -305,6 +360,9 @@ public final class AntarchyObjects {
CHISELED_SHELLSTONE = chiseledShellstone;
MOSSY_SHELLSTONE_BRICKS = mossyShellstoneBricks;
CRACKED_SHELLSTONE_BRICKS = crackedShellstoneBricks;
MOSSY_SHELLSTONE_BRICK_STAIRS = mossyShellstoneBrickStairs;
MOSSY_SHELLSTONE_BRICK_SLAB = mossyShellstoneBrickSlab;
MOSSY_SHELLSTONE_BRICK_WALL = mossyShellstoneBrickWall;
SHELLSTONE_STAIRS = shellstoneStairs;
SHELLSTONE_SLAB = shellstoneSlab;
SHELLSTONE_WALL = shellstoneWall;
@@ -333,6 +391,7 @@ public final class AntarchyObjects {
DREAD = dread;
PARALYZED_EFFECT = paralyzedEffect;
INVERTED_EFFECT = invertedEffect;
STINKY_EFFECT = stinkyEffect;
OURANWOOD_ACORN_BLOCK = ouranwoodAcornBlock;
MOSSY_OURANWOOD_LOG = mossyOuranwoodLog;
MOSSY_OURANWOOD_WOOD = mossyOuranwoodWood;
@@ -343,10 +402,13 @@ public final class AntarchyObjects {
WASP_NEST = waspNest;
HUSHWEED = hushweed;
SQUIRREL_NEST_BLOCK = squirrelNestBlock;
BRUTALFLY_COCOON_SPAWNER = brutalflyCocoonSpawner;
ANT_NEST_BLOCK_ENTITY = antNestBlockEntity;
DREAM_CAMPFIRE_BLOCK_ENTITY = dreamCampfireBlockEntity;
WASP_NEST_BLOCK_ENTITY = waspNestBlockEntity;
HUSHWEED_BLOCK_ENTITY = hushweedBlockEntity;
STINKY_GAS = stinkyGas;
STINKY_FLY = stinkyFly;
DOUBLE_DAMAGE_CHANCE = doubleDamageChance;
BLOODGLASS_MAX_HEARTS = bloodglassMaxHearts;
BLOODGLASS_WARD = bloodglassWard;
@@ -356,6 +418,14 @@ public final class AntarchyObjects {
return state.is(DUPLICATOR_LOG.get()) || state.is(DUPLICATOR_SAPLING.get());
}
public static void setOctopusBomb(Supplier<EntityType<OctopusBombEntity>> supplier) {
OCTOPUS_BOMB = supplier;
}
public static void setHerculesBeetle(Supplier<EntityType<HerculesBeetleEntity>> supplier) {
HERCULES_BEETLE = supplier;
}
public static BlockBehaviour.Properties shellstoneProperties() {
return BlockBehaviour.Properties.ofFullCopy(Blocks.STONE).requiresCorrectToolForDrops();
}
@@ -119,6 +119,23 @@ public final class AntarchySoundEvents {
public static Supplier<SoundEvent> MOLEVORE_HURT = (Supplier<SoundEvent>) UNBOUND;
public static Supplier<SoundEvent> MOLEVORE_ATTACK = (Supplier<SoundEvent>) UNBOUND;
public static Supplier<SoundEvent> MOLEVORE_DIG = (Supplier<SoundEvent>) UNBOUND;
public static Supplier<SoundEvent> BOMBER_WALK = (Supplier<SoundEvent>) UNBOUND;
public static Supplier<SoundEvent> BOMBER_KNOCK = (Supplier<SoundEvent>) UNBOUND;
public static Supplier<SoundEvent> BOMBER_EXPLODE = (Supplier<SoundEvent>) UNBOUND;
public static Supplier<SoundEvent> TORETERROR_IDLE = (Supplier<SoundEvent>) UNBOUND;
public static Supplier<SoundEvent> TORETERROR_HURT = (Supplier<SoundEvent>) UNBOUND;
public static Supplier<SoundEvent> TORETERROR_DEATH = (Supplier<SoundEvent>) UNBOUND;
public static Supplier<SoundEvent> TORETERROR_BOMBER_FIRE = (Supplier<SoundEvent>) UNBOUND;
public static Supplier<SoundEvent> TORETERROR_JUMP_UP = (Supplier<SoundEvent>) UNBOUND;
public static Supplier<SoundEvent> TORETERROR_JUMP_LAND = (Supplier<SoundEvent>) UNBOUND;
public static Supplier<SoundEvent> STINKY_FLY = (Supplier<SoundEvent>) UNBOUND;
public static Supplier<SoundEvent> CREEPING_HORROR_GROWL = (Supplier<SoundEvent>) UNBOUND;
public static Supplier<SoundEvent> CREEPING_HORROR_HURT = (Supplier<SoundEvent>) UNBOUND;
public static Supplier<SoundEvent> CREEPING_HORROR_BITE = (Supplier<SoundEvent>) UNBOUND;
public static Supplier<SoundEvent> LURKING_TERROR_SNARL = (Supplier<SoundEvent>) UNBOUND;
public static Supplier<SoundEvent> LURKING_TERROR_HURT = (Supplier<SoundEvent>) UNBOUND;
public static Supplier<SoundEvent> LURKING_TERROR_BITE = (Supplier<SoundEvent>) UNBOUND;
public static Supplier<SoundEvent> LURKING_TERROR_FLY_LOOP = (Supplier<SoundEvent>) UNBOUND;
public static Supplier<SoundEvent> DUCT_TAPE_USE = (Supplier<SoundEvent>) UNBOUND;
private AntarchySoundEvents() {
@@ -235,6 +252,23 @@ public final class AntarchySoundEvents {
Supplier<SoundEvent> molevoreHurt,
Supplier<SoundEvent> molevoreAttack,
Supplier<SoundEvent> molevoreDig,
Supplier<SoundEvent> bomberWalk,
Supplier<SoundEvent> bomberKnock,
Supplier<SoundEvent> bomberExplode,
Supplier<SoundEvent> toreterrorIdle,
Supplier<SoundEvent> toreterrorHurt,
Supplier<SoundEvent> toreterrorDeath,
Supplier<SoundEvent> toreterrorBomberFire,
Supplier<SoundEvent> toreterrorJumpUp,
Supplier<SoundEvent> toreterrorJumpLand,
Supplier<SoundEvent> stinkyFly,
Supplier<SoundEvent> creepingHorrorGrowl,
Supplier<SoundEvent> creepingHorrorHurt,
Supplier<SoundEvent> creepingHorrorBite,
Supplier<SoundEvent> lurkingTerrorSnarl,
Supplier<SoundEvent> lurkingTerrorHurt,
Supplier<SoundEvent> lurkingTerrorBite,
Supplier<SoundEvent> lurkingTerrorFlyLoop,
Supplier<SoundEvent> ductTapeUse
) {
SQUIDZOOKA_FIRE = squidzookaFire;
@@ -347,6 +381,23 @@ public final class AntarchySoundEvents {
MOLEVORE_HURT = molevoreHurt;
MOLEVORE_ATTACK = molevoreAttack;
MOLEVORE_DIG = molevoreDig;
BOMBER_WALK = bomberWalk;
BOMBER_KNOCK = bomberKnock;
BOMBER_EXPLODE = bomberExplode;
TORETERROR_IDLE = toreterrorIdle;
TORETERROR_HURT = toreterrorHurt;
TORETERROR_DEATH = toreterrorDeath;
TORETERROR_BOMBER_FIRE = toreterrorBomberFire;
TORETERROR_JUMP_UP = toreterrorJumpUp;
TORETERROR_JUMP_LAND = toreterrorJumpLand;
STINKY_FLY = stinkyFly;
CREEPING_HORROR_GROWL = creepingHorrorGrowl;
CREEPING_HORROR_HURT = creepingHorrorHurt;
CREEPING_HORROR_BITE = creepingHorrorBite;
LURKING_TERROR_SNARL = lurkingTerrorSnarl;
LURKING_TERROR_HURT = lurkingTerrorHurt;
LURKING_TERROR_BITE = lurkingTerrorBite;
LURKING_TERROR_FLY_LOOP = lurkingTerrorFlyLoop;
DUCT_TAPE_USE = ductTapeUse;
}
}
@@ -30,6 +30,7 @@ public final class AntarchyTags {
public static final TagKey<Block> MOLEVORE_SEE_THROUGH_BLOCKS = TagKey.create(Registries.BLOCK, id("molevore_see_through_blocks"));
public static final TagKey<Block> TERMITE_FOODS = TagKey.create(Registries.BLOCK, id("termite_foods"));
public static final TagKey<Block> UMBRAL_MOSS_REPLACEABLE = TagKey.create(Registries.BLOCK, id("umbral_moss_replaceable"));
public static final TagKey<Block> HERCULES_BEETLE_CHARGE_IMMUNE_BLOCKS = TagKey.create(Registries.BLOCK, id("hercules_beetle_charge_immune_blocks"));
private Blocks() {
}
@@ -55,6 +56,7 @@ public final class AntarchyTags {
public static final TagKey<EntityType<?>> PARALYSIS_IMMUNE = TagKey.create(Registries.ENTITY_TYPE, id("paralysis_immune"));
public static final TagKey<EntityType<?>> HUSHWEED_TARGETS = TagKey.create(Registries.ENTITY_TYPE, id("hushweed_targets"));
public static final TagKey<EntityType<?>> TRIFFID_PREY = TagKey.create(Registries.ENTITY_TYPE, id("triffid_prey"));
public static final TagKey<EntityType<?>> STINKY_REPELLED_HOSTILES = TagKey.create(Registries.ENTITY_TYPE, id("stinky_repelled_hostiles"));
public static final TagKey<EntityType<?>> SCORPION_WHIP_IMMUNE = TagKey.create(Registries.ENTITY_TYPE, id("scorpion_whip_immune"));
public static final TagKey<EntityType<?>> SCORPION_WHIP_BLACKLIST = TagKey.create(Registries.ENTITY_TYPE, id("scorpion_whip_blacklist"));
@@ -75,6 +77,7 @@ public final class AntarchyTags {
public static final TagKey<Item> DUCT_TAPE_BLACKLIST = TagKey.create(Registries.ITEM, id("duct_tape_blacklist"));
public static final TagKey<Item> BROWN_ANT_ACTIVATION_ITEMS = TagKey.create(Registries.ITEM, id("brown_ant_activation_items"));
public static final TagKey<Item> BROWN_ANT_BREEDING_FOODS = TagKey.create(Registries.ITEM, id("brown_ant_breeding_foods"));
public static final TagKey<Item> MULTISHOT_ENCHANTABLE = TagKey.create(Registries.ITEM, id("multishot_enchantable"));
public static final TagKey<Item> RED_ANT_ACTIVATION_ITEMS = TagKey.create(Registries.ITEM, id("red_ant_activation_items"));
public static final TagKey<Item> RED_ANT_BREEDING_FOODS = TagKey.create(Registries.ITEM, id("red_ant_breeding_foods"));
public static final TagKey<Item> RAINBOW_ANT_ACTIVATION_ITEMS = TagKey.create(Registries.ITEM, id("rainbow_ant_activation_items"));
@@ -86,6 +89,7 @@ public final class AntarchyTags {
public static final TagKey<Item> CATERPILLAR_FOODS = TagKey.create(Registries.ITEM, id("caterpillar_foods"));
public static final TagKey<Item> BUTTERFLY_BREEDING_FOODS = TagKey.create(Registries.ITEM, id("butterfly_breeding_foods"));
public static final TagKey<Item> REVERIE_DUPLICATION_ITEMS = TagKey.create(Registries.ITEM, id("reverie_duplication_items"));
public static final TagKey<Item> HERCULES_BEETLE_FOOD = TagKey.create(Registries.ITEM, id("hercules_beetle_food"));
private Items() {
}
@@ -94,6 +98,7 @@ public final class AntarchyTags {
public static final class Biomes {
public static final TagKey<Biome> MANTIS_SPAWN_BIOMES = TagKey.create(Registries.BIOME, id("mantis_spawn_biomes"));
public static final TagKey<Biome> MANTIS_OVERWORLD_SPAWN_BIOMES = TagKey.create(Registries.BIOME, id("mantis_overworld_spawn_biomes"));
public static final TagKey<Biome> ELYTHIA_FIREFLY_PARTICLE_BIOMES = TagKey.create(Registries.BIOME, id("elythia_firefly_particle_biomes"));
private Biomes() {
}
@@ -0,0 +1,34 @@
package com.craisinlord.antarchy.content;
import com.craisinlord.antarchy.mixins.AccessorRangedAttribute;
import com.google.common.collect.ImmutableSet;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.ai.attributes.Attribute;
import net.minecraft.world.entity.ai.attributes.RangedAttribute;
import java.util.Set;
public class AttributeRangeInit {
private static final double MAX = 11_000_000D;
private static final Set<ResourceLocation> TARGETS = ImmutableSet.of(
ResourceLocation.fromNamespaceAndPath("minecraft", "generic.max_health"),
ResourceLocation.fromNamespaceAndPath("minecraft", "generic.armor"),
ResourceLocation.fromNamespaceAndPath("minecraft", "generic.armor_toughness"),
ResourceLocation.fromNamespaceAndPath("minecraft", "generic.attack_damage"),
ResourceLocation.fromNamespaceAndPath("minecraft", "generic.attack_knockback")
);
public static void apply() {
for (ResourceLocation id : TARGETS) {
Attribute attribute = BuiltInRegistries.ATTRIBUTE.get(id);
if (attribute instanceof RangedAttribute ranged && ranged instanceof AccessorRangedAttribute accessor) {
if (ranged.getMaxValue() < MAX) {
accessor.antarchyAttributefix$setMaxValue(MAX);
}
}
}
}
}
@@ -0,0 +1,221 @@
package com.craisinlord.antarchy.content;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.tags.EntityTypeTags;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.PathfinderMob;
import net.minecraft.world.entity.monster.Enemy;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.phys.Vec3;
public final class StinkyBehavior {
public static final double STINK_BUG_AURA_RADIUS = 5.0D;
public static final double STINKY_NAUSEA_RADIUS = 4.0D;
private static final int TRAIL_INTERVAL_TICKS = 4;
private static final int REACTION_INTERVAL_TICKS = 10;
private static final double ARTHROPOD_ALERT_RADIUS = 28.0D;
private static final double FLEE_RADIUS = 10.0D;
private static final double FLEE_DISTANCE = 8.0D;
private static final int STINKY_NAUSEA_DURATION_TICKS = 60;
private static final int STINK_BUG_BURST_NAUSEA_DURATION_TICKS = 100;
private StinkyBehavior() {
}
public static void tickStinkyTrail(LivingEntity entity) {
if (!(entity.level() instanceof ServerLevel serverLevel)) {
return;
}
if (!entity.isAlive() || entity.tickCount % TRAIL_INTERVAL_TICKS != 0 || !entity.hasEffect(AntarchyObjects.STINKY_EFFECT.get())) {
return;
}
Vec3 motion = entity.getDeltaMovement();
Vec3 horizontalMotion = new Vec3(motion.x, 0.0D, motion.z);
Vec3 direction = horizontalMotion.lengthSqr() > 1.0E-4D
? horizontalMotion.normalize()
: entity.getLookAngle().multiply(1.0D, 0.0D, 1.0D);
if (direction.lengthSqr() <= 1.0E-4D) {
direction = new Vec3(0.0D, 0.0D, 1.0D);
} else {
direction = direction.normalize();
}
Vec3 origin = entity.position()
.subtract(direction.scale(0.35D))
.add(0.0D, Math.max(0.15D, entity.getBbHeight() * 0.2D), 0.0D);
serverLevel.sendParticles(
AntarchyObjects.STINKY_GAS.get(),
origin.x,
origin.y,
origin.z,
2,
entity.getBbWidth() * 0.12D,
0.04D,
entity.getBbWidth() * 0.12D,
0.005D
);
serverLevel.sendParticles(
AntarchyObjects.STINKY_FLY.get(),
entity.getX(),
entity.getY(0.45D),
entity.getZ(),
1,
entity.getBbWidth() * 0.35D,
entity.getBbHeight() * 0.2D,
entity.getBbWidth() * 0.35D,
0.0D
);
applyNearbyNausea(entity, STINKY_NAUSEA_RADIUS, STINKY_NAUSEA_DURATION_TICKS);
if (entity.tickCount % REACTION_INTERVAL_TICKS == 0) {
reactToStinkySource(entity);
}
}
public static void emitBurst(LivingEntity entity, int count) {
if (!(entity.level() instanceof ServerLevel serverLevel)) {
return;
}
serverLevel.sendParticles(
AntarchyObjects.STINKY_GAS.get(),
entity.getX(),
entity.getY(0.45D),
entity.getZ(),
count,
entity.getBbWidth() * 0.35D,
entity.getBbHeight() * 0.25D,
entity.getBbWidth() * 0.35D,
0.045D
);
serverLevel.sendParticles(
AntarchyObjects.STINKY_FLY.get(),
entity.getX(),
entity.getY(0.45D),
entity.getZ(),
Math.max(2, count / 6),
entity.getBbWidth() * 0.45D,
entity.getBbHeight() * 0.25D,
entity.getBbWidth() * 0.45D,
0.0D
);
applyNearbyNausea(entity, STINK_BUG_AURA_RADIUS, STINK_BUG_BURST_NAUSEA_DURATION_TICKS);
}
public static void applyBurstStinkyEffect(LivingEntity source, double radius, int durationTicks) {
if (source.level().isClientSide()) {
return;
}
for (LivingEntity entity : source.level().getEntitiesOfClass(
LivingEntity.class,
source.getBoundingBox().inflate(radius),
e -> e.isAlive() && e != source
)) {
entity.addEffect(new MobEffectInstance(AntarchyObjects.STINKY_EFFECT.get(), durationTicks, 0, false, true, true));
}
}
public static void applyNearbyNausea(LivingEntity source, double radius, int durationTicks) {
if (source.level().isClientSide()) {
return;
}
for (Player player : source.level().getEntitiesOfClass(
Player.class,
source.getBoundingBox().inflate(radius),
player -> player.isAlive() && !player.isSpectator() && player != source
)) {
player.addEffect(new MobEffectInstance(MobEffects.CONFUSION, durationTicks, 0, false, true, true));
}
}
private static void reactToStinkySource(LivingEntity source) {
if (source.level().isClientSide() || !source.isAlive()) {
return;
}
for (Mob mob : source.level().getEntitiesOfClass(
Mob.class,
source.getBoundingBox().inflate(ARTHROPOD_ALERT_RADIUS),
mob -> mob.isAlive() && mob != source
)) {
if (mob.getType().is(EntityTypeTags.SENSITIVE_TO_BANE_OF_ARTHROPODS)) {
if (source.getType().is(EntityTypeTags.SENSITIVE_TO_BANE_OF_ARTHROPODS)) {
continue;
}
attractArthropod(mob, source);
continue;
}
if (shouldRepelMob(mob, source)) {
repelPassiveOrNeutralMob(mob, source);
}
}
}
private static void attractArthropod(Mob mob, LivingEntity source) {
if (!isValidStinkyAttackSourceForMob(mob, source)) {
return;
}
LivingEntity currentTarget = mob.getTarget();
if (currentTarget != null && currentTarget.isAlive() && currentTarget.hasEffect(AntarchyObjects.STINKY_EFFECT.get())
&& mob.distanceToSqr(currentTarget) <= ARTHROPOD_ALERT_RADIUS * ARTHROPOD_ALERT_RADIUS) {
return;
}
mob.setTarget(source);
}
private static void repelPassiveOrNeutralMob(Mob mob, LivingEntity source) {
if (!(mob instanceof PathfinderMob pathfinderMob) || !isValidStinkyRepelSourceForMob(mob, source)) {
return;
}
Vec3 away = mob.position().subtract(source.position());
if (away.lengthSqr() < 1.0E-4D) {
away = new Vec3(mob.getRandom().nextDouble() - 0.5D, 0.0D, mob.getRandom().nextDouble() - 0.5D);
}
Vec3 escapeTarget = mob.position().add(away.normalize().scale(FLEE_DISTANCE));
pathfinderMob.setTarget(null);
pathfinderMob.getNavigation().moveTo(escapeTarget.x, escapeTarget.y, escapeTarget.z, 1.2D);
}
private static boolean shouldRepelMob(Mob mob, LivingEntity source) {
if (mob.hasEffect(AntarchyObjects.STINKY_EFFECT.get()) || mob.distanceToSqr(source) > FLEE_RADIUS * FLEE_RADIUS) {
return false;
}
if (mob.getType().is(AntarchyTags.Entities.STINKY_REPELLED_HOSTILES)) {
return true;
}
return !(mob instanceof Enemy);
}
private static boolean isValidStinkyAttackSourceForMob(Mob mob, LivingEntity source) {
if (!isValidStinkyRepelSourceForMob(mob, source)) {
return false;
}
if (source == mob || !source.isAlive() || !source.hasEffect(AntarchyObjects.STINKY_EFFECT.get())) {
return false;
}
if (source instanceof Player player && (player.isCreative() || player.isSpectator())) {
return false;
}
return !mob.isAlliedTo(source);
}
private static boolean isValidStinkyRepelSourceForMob(Mob mob, LivingEntity source) {
if (source == mob || !source.isAlive() || !source.hasEffect(AntarchyObjects.STINKY_EFFECT.get())) {
return false;
}
if (source instanceof Player player && player.isSpectator()) {
return false;
}
return !mob.isAlliedTo(source);
}
}
@@ -0,0 +1,53 @@
package com.craisinlord.antarchy.content.block;
import com.craisinlord.antarchy.Antarchy;
import com.mojang.serialization.MapCodec;
import net.minecraft.core.Holder;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.core.BlockPos;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.BonemealableBlock;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.feature.ConfiguredFeature;
public class AmberMossBlock extends Block implements BonemealableBlock {
public static final MapCodec<AmberMossBlock> CODEC = Block.simpleCodec(AmberMossBlock::new);
private static final ResourceKey<ConfiguredFeature<?, ?>> AMBER_MOSS_PATCH_BONEMEAL = ResourceKey.create(
Registries.CONFIGURED_FEATURE,
ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "amber_moss_patch_bonemeal")
);
public AmberMossBlock(BlockBehaviour.Properties properties) {
super(properties);
}
@Override
public MapCodec<AmberMossBlock> codec() {
return CODEC;
}
@Override
public boolean isValidBonemealTarget(LevelReader level, BlockPos pos, BlockState state) {
return level.getBlockState(pos.above()).isAir();
}
@Override
public boolean isBonemealSuccess(Level level, RandomSource random, BlockPos pos, BlockState state) {
return true;
}
@Override
public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) {
Holder.Reference<ConfiguredFeature<?, ?>> feature = level.registryAccess()
.lookupOrThrow(Registries.CONFIGURED_FEATURE)
.getOrThrow(AMBER_MOSS_PATCH_BONEMEAL);
feature.value().place(level, level.getChunkSource().getGenerator(), random, pos);
}
}
@@ -96,7 +96,7 @@ public class BedBugEggBlock extends Block {
@Override
protected void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
if (!this.shouldUpdateHatchLevel(level) || !hasHatchSupport(level, pos)) {
if (!this.shouldUpdateHatchLevel(level)) {
return;
}
@@ -0,0 +1,46 @@
package com.craisinlord.antarchy.content.block;
import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.content.entity.brutalfly.BrutalflyEntity;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.RenderShape;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
public class BrutalflyCocoonSpawnerBlock extends Block {
public BrutalflyCocoonSpawnerBlock(BlockBehaviour.Properties properties) {
super(properties);
}
@Override
protected RenderShape getRenderShape(BlockState state) {
return RenderShape.INVISIBLE;
}
@Override
protected void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
level.setBlock(pos, Blocks.AIR.defaultBlockState(), Block.UPDATE_ALL);
BrutalflyEntity brutalfly = AntarchyObjects.BRUTALFLY.get().create(level);
if (brutalfly == null) {
return;
}
BlockPos anchor = pos.above();
brutalfly.moveTo(
anchor.getX() + 0.5D,
anchor.getY() - 1.5D,
anchor.getZ() + 0.5D,
random.nextFloat() * 360.0F,
0.0F
);
brutalfly.setCocooned(true, anchor);
brutalfly.setHealth(brutalfly.getMaxHealth());
level.addFreshEntity(brutalfly);
}
}
@@ -0,0 +1,166 @@
package com.craisinlord.antarchy.content.block;
import com.craisinlord.antarchy.content.AntarchyObjects;
import com.mojang.serialization.MapCodec;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.RandomSource;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.PointedDripstoneBlock;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.DripstoneThickness;
import net.minecraft.world.level.material.Fluids;
public class ChitenSpikeBlock extends PointedDripstoneBlock {
@SuppressWarnings("rawtypes")
public static final MapCodec<PointedDripstoneBlock> CODEC = (MapCodec) Block.simpleCodec(ChitenSpikeBlock::new);
public ChitenSpikeBlock(BlockBehaviour.Properties properties) {
super(properties);
}
@Override
public MapCodec<PointedDripstoneBlock> codec() {
return CODEC;
}
@Override
public BlockState getStateForPlacement(BlockPlaceContext context) {
LevelAccessor level = context.getLevel();
BlockPos pos = context.getClickedPos();
Direction direction = calculateTipDirection(level, pos, context.getNearestLookingVerticalDirection().getOpposite());
if (direction == null) {
return null;
}
boolean mergeTips = !context.isSecondaryUseActive();
DripstoneThickness thickness = calculateDripstoneThickness(level, pos, direction, mergeTips);
if (thickness == null) {
return null;
}
return this.defaultBlockState()
.setValue(TIP_DIRECTION, direction)
.setValue(THICKNESS, thickness)
.setValue(WATERLOGGED, level.getFluidState(pos).getType() == Fluids.WATER);
}
@Override
protected BlockState updateShape(
BlockState state,
Direction direction,
BlockState neighborState,
LevelAccessor level,
BlockPos pos,
BlockPos neighborPos
) {
if (state.getValue(WATERLOGGED)) {
level.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(level));
}
if (direction != Direction.UP && direction != Direction.DOWN) {
return state;
}
Direction tipDirection = state.getValue(TIP_DIRECTION);
if (tipDirection == Direction.DOWN && level.getBlockTicks().hasScheduledTick(pos, this)) {
return state;
}
if (direction == tipDirection.getOpposite() && !this.canSurvive(state, level, pos)) {
if (tipDirection == Direction.DOWN) {
level.scheduleTick(pos, this, 2);
} else {
level.scheduleTick(pos, this, 1);
}
return state;
}
boolean mergeTips = state.getValue(THICKNESS) == DripstoneThickness.TIP_MERGE;
return state.setValue(THICKNESS, calculateDripstoneThickness(level, pos, tipDirection, mergeTips));
}
@Override
public boolean canSurvive(BlockState state, LevelReader level, BlockPos pos) {
return isValidPointedDripstonePlacement(level, pos, state.getValue(TIP_DIRECTION));
}
@Override
protected void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
if (this.canSurvive(state, level, pos)) {
return;
}
if (state.getValue(TIP_DIRECTION) == Direction.DOWN) {
BlockPos.MutableBlockPos root = pos.mutable();
while (isChitenSpikeWithDirection(level.getBlockState(root.above()), Direction.DOWN)) {
root.move(Direction.UP);
}
BlockPos.MutableBlockPos cursor = root.mutable();
while (isChitenSpikeWithDirection(level.getBlockState(cursor), Direction.DOWN)) {
BlockState fallingState = level.getBlockState(cursor);
if (isChitenSpikeWithDirection(fallingState, Direction.DOWN)) {
net.minecraft.world.entity.item.FallingBlockEntity.fall(level, cursor, fallingState);
}
cursor.move(Direction.DOWN);
}
return;
}
BlockPos.MutableBlockPos cursor = pos.mutable();
while (isChitenSpikeWithDirection(level.getBlockState(cursor), Direction.UP)) {
BlockState spikeState = level.getBlockState(cursor);
dropResources(spikeState, level, cursor);
level.removeBlock(cursor, false);
cursor.move(Direction.UP);
}
}
private static Direction calculateTipDirection(LevelReader level, BlockPos pos, Direction preferredDirection) {
if (isValidPointedDripstonePlacement(level, pos, preferredDirection)) {
return preferredDirection;
}
Direction opposite = preferredDirection.getOpposite();
return isValidPointedDripstonePlacement(level, pos, opposite) ? opposite : null;
}
private static DripstoneThickness calculateDripstoneThickness(LevelReader level, BlockPos pos, Direction direction, boolean mergeTips) {
Direction opposite = direction.getOpposite();
BlockState forwardState = level.getBlockState(pos.relative(direction));
if (isChitenSpikeWithDirection(forwardState, opposite)) {
return !mergeTips && forwardState.getValue(THICKNESS) != DripstoneThickness.TIP_MERGE
? DripstoneThickness.TIP
: DripstoneThickness.TIP_MERGE;
}
if (!isChitenSpikeWithDirection(forwardState, direction)) {
return DripstoneThickness.TIP;
}
DripstoneThickness forwardThickness = forwardState.getValue(THICKNESS);
if (forwardThickness != DripstoneThickness.TIP && forwardThickness != DripstoneThickness.TIP_MERGE) {
BlockState backState = level.getBlockState(pos.relative(opposite));
return !isChitenSpikeWithDirection(backState, direction) ? DripstoneThickness.BASE : DripstoneThickness.MIDDLE;
}
return DripstoneThickness.FRUSTUM;
}
private static boolean isValidPointedDripstonePlacement(LevelReader level, BlockPos pos, Direction direction) {
BlockPos supportPos = pos.relative(direction.getOpposite());
BlockState supportState = level.getBlockState(supportPos);
return supportState.isFaceSturdy(level, supportPos, direction) || isChitenSpikeWithDirection(supportState, direction);
}
private static boolean isChitenSpikeWithDirection(BlockState state, Direction direction) {
return state.is(AntarchyObjects.CHITEN_SPIKE.get()) && state.getValue(TIP_DIRECTION) == direction;
}
}
@@ -0,0 +1,240 @@
package com.craisinlord.antarchy.content.block;
import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.content.entity.CreepingHorrorEntity;
import com.craisinlord.antarchy.content.block.AmberMossBlock;
import com.craisinlord.antarchy.content.block.LurkingTerrorEggBlock;
import com.mojang.serialization.MapCodec;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.tags.BlockTags;
import net.minecraft.util.RandomSource;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.ambient.Bat;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.GameRules;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockBehaviour;
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.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.VoxelShape;
public class CreepingHorrorEggBlock extends Block {
public static final MapCodec<CreepingHorrorEggBlock> CODEC = Block.simpleCodec(CreepingHorrorEggBlock::new);
public static final IntegerProperty HATCH = BlockStateProperties.HATCH;
public static final IntegerProperty EGGS = BlockStateProperties.EGGS;
private static final float PANIC_HATCH_CHANCE = 0.50F;
private static final int MAX_HATCH = 2;
private static final int MAX_EGGS = 4;
private static final VoxelShape SINGLE_SHAPE = Block.box(3.0D, 0.0D, 3.0D, 13.0D, 7.0D, 13.0D);
private static final VoxelShape MULTI_SHAPE = Block.box(1.0D, 0.0D, 1.0D, 15.0D, 7.0D, 15.0D);
public CreepingHorrorEggBlock(BlockBehaviour.Properties properties) {
super(properties);
this.registerDefaultState(this.stateDefinition.any().setValue(HATCH, 0).setValue(EGGS, 1));
}
@Override
public MapCodec<CreepingHorrorEggBlock> codec() {
return CODEC;
}
@Override
protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
return state.getValue(EGGS) > 1 ? MULTI_SHAPE : SINGLE_SHAPE;
}
@Override
protected boolean canSurvive(BlockState state, LevelReader level, BlockPos pos) {
return level.getBlockState(pos.below()).isFaceSturdy(level, pos.below(), Direction.UP);
}
@Override
protected BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor level, BlockPos pos, BlockPos neighborPos) {
return state.canSurvive(level, pos) ? super.updateShape(state, direction, neighborState, level, pos, neighborPos) : Blocks.AIR.defaultBlockState();
}
@Override
protected boolean canBeReplaced(BlockState state, BlockPlaceContext context) {
return !context.isSecondaryUseActive()
&& context.getItemInHand().is(this.asItem())
&& state.getValue(EGGS) < MAX_EGGS;
}
@Override
public BlockState getStateForPlacement(BlockPlaceContext context) {
BlockState existingState = context.getLevel().getBlockState(context.getClickedPos());
if (existingState.is(this)) {
return existingState.setValue(EGGS, Math.min(MAX_EGGS, existingState.getValue(EGGS) + 1));
}
return this.defaultBlockState();
}
@Override
protected boolean isRandomlyTicking(BlockState state) {
return true;
}
@Override
protected void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
if (!this.shouldUpdateHatchLevel(level, pos)) {
return;
}
int hatch = state.getValue(HATCH);
if (hatch < MAX_HATCH) {
level.setBlock(pos, state.setValue(HATCH, hatch + 1), 2);
level.playSound(null, pos, SoundEvents.TURTLE_EGG_CRACK, SoundSource.BLOCKS, 0.7F, 0.95F + random.nextFloat() * 0.1F);
return;
}
this.hatchEggs(level, pos, state, random, state.getValue(EGGS));
}
@Override
public void stepOn(Level level, BlockPos pos, BlockState state, Entity entity) {
if (entity instanceof Player && !entity.isSteppingCarefully() && this.destroyEgg(level, state, pos, entity, 100)) {
if (level instanceof ServerLevel serverLevel) {
panicHatchNearby(serverLevel, pos);
}
}
super.stepOn(level, pos, state, entity);
}
@Override
public void fallOn(Level level, BlockState state, BlockPos pos, Entity entity, float fallDistance) {
this.destroyEgg(level, state, pos, entity, 3);
super.fallOn(level, state, pos, entity, fallDistance);
}
@Override
protected void onPlace(BlockState state, Level level, BlockPos pos, BlockState oldState, boolean isMoving) {
if (hasHatchSupport(level, pos) && !level.isClientSide) {
level.levelEvent(2012, pos, 15);
}
}
@Override
public void playerDestroy(Level level, Player player, BlockPos pos, BlockState state, BlockEntity blockEntity, net.minecraft.world.item.ItemStack stack) {
super.playerDestroy(level, player, pos, state, blockEntity, stack);
this.decreaseEggs(level, pos, state);
}
private static void panicHatchNearby(ServerLevel level, BlockPos origin) {
int radius = 6;
BlockPos.MutableBlockPos mutable = new BlockPos.MutableBlockPos();
for (int dx = -radius; dx <= radius; dx++) {
for (int dy = -radius; dy <= radius; dy++) {
for (int dz = -radius; dz <= radius; dz++) {
if (dx == 0 && dy == 0 && dz == 0) continue;
mutable.set(origin.getX() + dx, origin.getY() + dy, origin.getZ() + dz);
BlockState nearby = level.getBlockState(mutable);
BlockPos immutable = mutable.immutable();
if (nearby.getBlock() instanceof CreepingHorrorEggBlock b) {
b.hatchPanickedEggs(level, immutable, nearby, level.random);
} else if (nearby.getBlock() instanceof LurkingTerrorEggBlock b) {
b.hatchPanickedEggs(level, immutable, nearby, level.random);
}
}
}
}
}
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
builder.add(HATCH, EGGS);
}
public static boolean hasHatchSupport(BlockGetter level, BlockPos pos) {
return isSupportedGround(level, pos.below());
}
public static boolean isSupportedGround(BlockGetter level, BlockPos pos) {
return level.getBlockState(pos).is(BlockTags.SAND) || level.getBlockState(pos).blocksMotion();
}
private boolean destroyEgg(Level level, BlockState state, BlockPos pos, Entity entity, int chance) {
if (level.isClientSide || !this.canDestroyEgg(level, entity) || level.random.nextInt(chance) != 0) {
return false;
}
level.playSound(null, pos, SoundEvents.TURTLE_EGG_BREAK, SoundSource.BLOCKS, 0.7F, 0.9F + level.random.nextFloat() * 0.2F);
this.decreaseEggs(level, pos, state);
return true;
}
void hatchPanickedEggs(ServerLevel level, BlockPos pos, BlockState state, RandomSource random) {
int hatchedEggs = rollPanickedEggs(state.getValue(EGGS), random);
if (hatchedEggs <= 0) {
return;
}
this.hatchEggs(level, pos, state, random, hatchedEggs);
}
void hatchEggs(ServerLevel level, BlockPos pos, BlockState state, RandomSource random, int eggsToHatch) {
if (eggsToHatch >= state.getValue(EGGS)) {
level.removeBlock(pos, false);
} else {
level.setBlock(pos, state.setValue(EGGS, state.getValue(EGGS) - eggsToHatch), 2);
}
level.playSound(null, pos, SoundEvents.TURTLE_EGG_HATCH, SoundSource.BLOCKS, 0.75F, 0.95F + random.nextFloat() * 0.1F);
for (int i = 0; i < eggsToHatch; i++) {
CreepingHorrorEntity entity = AntarchyObjects.CREEPING_HORROR.get().create(level);
if (entity == null) {
continue;
}
double spawnX = pos.getX() + 0.25D + random.nextDouble() * 0.5D;
double spawnZ = pos.getZ() + 0.25D + random.nextDouble() * 0.5D;
entity.moveTo(spawnX, pos.getY() + 0.1D, spawnZ, random.nextFloat() * 360.0F, 0.0F);
level.addFreshEntity(entity);
}
}
private static int rollPanickedEggs(int eggs, RandomSource random) {
int hatchedEggs = 0;
for (int i = 0; i < eggs; i++) {
if (random.nextFloat() < PANIC_HATCH_CHANCE) {
hatchedEggs++;
}
}
return hatchedEggs;
}
private void decreaseEggs(Level level, BlockPos pos, BlockState state) {
int eggs = state.getValue(EGGS);
if (eggs <= 1) {
level.destroyBlock(pos, false);
return;
}
level.setBlock(pos, state.setValue(EGGS, eggs - 1), 2);
}
private boolean shouldUpdateHatchLevel(Level level, BlockPos pos) {
boolean onAmberMoss = level.getBlockState(pos.below()).getBlock() instanceof AmberMossBlock;
return level.random.nextInt(onAmberMoss ? 150 : 500) == 0;
}
private boolean canDestroyEgg(Level level, Entity entity) {
if (entity instanceof CreepingHorrorEntity || entity instanceof Bat) {
return false;
}
if (entity instanceof net.minecraft.world.entity.LivingEntity) {
return entity instanceof Player || level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING);
}
return false;
}
}
@@ -0,0 +1,240 @@
package com.craisinlord.antarchy.content.block;
import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.content.entity.LurkingTerrorEntity;
import com.craisinlord.antarchy.content.block.AmberMossBlock;
import com.craisinlord.antarchy.content.block.CreepingHorrorEggBlock;
import com.mojang.serialization.MapCodec;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.tags.BlockTags;
import net.minecraft.util.RandomSource;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.ambient.Bat;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.GameRules;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockBehaviour;
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.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.VoxelShape;
public class LurkingTerrorEggBlock extends Block {
public static final MapCodec<LurkingTerrorEggBlock> CODEC = Block.simpleCodec(LurkingTerrorEggBlock::new);
public static final IntegerProperty HATCH = BlockStateProperties.HATCH;
public static final IntegerProperty EGGS = BlockStateProperties.EGGS;
private static final float PANIC_HATCH_CHANCE = 0.50F;
private static final int MAX_HATCH = 2;
private static final int MAX_EGGS = 4;
private static final VoxelShape SINGLE_SHAPE = Block.box(3.0D, 0.0D, 3.0D, 13.0D, 7.0D, 13.0D);
private static final VoxelShape MULTI_SHAPE = Block.box(1.0D, 0.0D, 1.0D, 15.0D, 7.0D, 15.0D);
public LurkingTerrorEggBlock(BlockBehaviour.Properties properties) {
super(properties);
this.registerDefaultState(this.stateDefinition.any().setValue(HATCH, 0).setValue(EGGS, 1));
}
@Override
public MapCodec<LurkingTerrorEggBlock> codec() {
return CODEC;
}
@Override
protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
return state.getValue(EGGS) > 1 ? MULTI_SHAPE : SINGLE_SHAPE;
}
@Override
protected boolean canSurvive(BlockState state, LevelReader level, BlockPos pos) {
return level.getBlockState(pos.below()).isFaceSturdy(level, pos.below(), Direction.UP);
}
@Override
protected BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor level, BlockPos pos, BlockPos neighborPos) {
return state.canSurvive(level, pos) ? super.updateShape(state, direction, neighborState, level, pos, neighborPos) : Blocks.AIR.defaultBlockState();
}
@Override
protected boolean canBeReplaced(BlockState state, BlockPlaceContext context) {
return !context.isSecondaryUseActive()
&& context.getItemInHand().is(this.asItem())
&& state.getValue(EGGS) < MAX_EGGS;
}
@Override
public BlockState getStateForPlacement(BlockPlaceContext context) {
BlockState existingState = context.getLevel().getBlockState(context.getClickedPos());
if (existingState.is(this)) {
return existingState.setValue(EGGS, Math.min(MAX_EGGS, existingState.getValue(EGGS) + 1));
}
return this.defaultBlockState();
}
@Override
protected boolean isRandomlyTicking(BlockState state) {
return true;
}
@Override
protected void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
if (!this.shouldUpdateHatchLevel(level, pos)) {
return;
}
int hatch = state.getValue(HATCH);
if (hatch < MAX_HATCH) {
level.setBlock(pos, state.setValue(HATCH, hatch + 1), 2);
level.playSound(null, pos, SoundEvents.TURTLE_EGG_CRACK, SoundSource.BLOCKS, 0.7F, 0.95F + random.nextFloat() * 0.1F);
return;
}
this.hatchEggs(level, pos, state, random, state.getValue(EGGS));
}
@Override
public void stepOn(Level level, BlockPos pos, BlockState state, Entity entity) {
if (entity instanceof Player && !entity.isSteppingCarefully() && this.destroyEgg(level, state, pos, entity, 100)) {
if (level instanceof ServerLevel serverLevel) {
panicHatchNearby(serverLevel, pos);
}
}
super.stepOn(level, pos, state, entity);
}
@Override
public void fallOn(Level level, BlockState state, BlockPos pos, Entity entity, float fallDistance) {
this.destroyEgg(level, state, pos, entity, 3);
super.fallOn(level, state, pos, entity, fallDistance);
}
@Override
protected void onPlace(BlockState state, Level level, BlockPos pos, BlockState oldState, boolean isMoving) {
if (hasHatchSupport(level, pos) && !level.isClientSide) {
level.levelEvent(2012, pos, 15);
}
}
@Override
public void playerDestroy(Level level, Player player, BlockPos pos, BlockState state, BlockEntity blockEntity, net.minecraft.world.item.ItemStack stack) {
super.playerDestroy(level, player, pos, state, blockEntity, stack);
this.decreaseEggs(level, pos, state);
}
private static void panicHatchNearby(ServerLevel level, BlockPos origin) {
int radius = 6;
BlockPos.MutableBlockPos mutable = new BlockPos.MutableBlockPos();
for (int dx = -radius; dx <= radius; dx++) {
for (int dy = -radius; dy <= radius; dy++) {
for (int dz = -radius; dz <= radius; dz++) {
if (dx == 0 && dy == 0 && dz == 0) continue;
mutable.set(origin.getX() + dx, origin.getY() + dy, origin.getZ() + dz);
BlockState nearby = level.getBlockState(mutable);
BlockPos immutable = mutable.immutable();
if (nearby.getBlock() instanceof LurkingTerrorEggBlock b) {
b.hatchPanickedEggs(level, immutable, nearby, level.random);
} else if (nearby.getBlock() instanceof CreepingHorrorEggBlock b) {
b.hatchPanickedEggs(level, immutable, nearby, level.random);
}
}
}
}
}
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
builder.add(HATCH, EGGS);
}
public static boolean hasHatchSupport(BlockGetter level, BlockPos pos) {
return isSupportedGround(level, pos.below());
}
public static boolean isSupportedGround(BlockGetter level, BlockPos pos) {
return level.getBlockState(pos).is(BlockTags.SAND) || level.getBlockState(pos).blocksMotion();
}
private boolean destroyEgg(Level level, BlockState state, BlockPos pos, Entity entity, int chance) {
if (level.isClientSide || !this.canDestroyEgg(level, entity) || level.random.nextInt(chance) != 0) {
return false;
}
level.playSound(null, pos, SoundEvents.TURTLE_EGG_BREAK, SoundSource.BLOCKS, 0.7F, 0.9F + level.random.nextFloat() * 0.2F);
this.decreaseEggs(level, pos, state);
return true;
}
void hatchPanickedEggs(ServerLevel level, BlockPos pos, BlockState state, RandomSource random) {
int hatchedEggs = rollPanickedEggs(state.getValue(EGGS), random);
if (hatchedEggs <= 0) {
return;
}
this.hatchEggs(level, pos, state, random, hatchedEggs);
}
void hatchEggs(ServerLevel level, BlockPos pos, BlockState state, RandomSource random, int eggsToHatch) {
if (eggsToHatch >= state.getValue(EGGS)) {
level.removeBlock(pos, false);
} else {
level.setBlock(pos, state.setValue(EGGS, state.getValue(EGGS) - eggsToHatch), 2);
}
level.playSound(null, pos, SoundEvents.TURTLE_EGG_HATCH, SoundSource.BLOCKS, 0.75F, 0.95F + random.nextFloat() * 0.1F);
for (int i = 0; i < eggsToHatch; i++) {
LurkingTerrorEntity entity = AntarchyObjects.LURKING_TERROR.get().create(level);
if (entity == null) {
continue;
}
double spawnX = pos.getX() + 0.25D + random.nextDouble() * 0.5D;
double spawnZ = pos.getZ() + 0.25D + random.nextDouble() * 0.5D;
entity.moveTo(spawnX, pos.getY() + 0.1D, spawnZ, random.nextFloat() * 360.0F, 0.0F);
level.addFreshEntity(entity);
}
}
private static int rollPanickedEggs(int eggs, RandomSource random) {
int hatchedEggs = 0;
for (int i = 0; i < eggs; i++) {
if (random.nextFloat() < PANIC_HATCH_CHANCE) {
hatchedEggs++;
}
}
return hatchedEggs;
}
private void decreaseEggs(Level level, BlockPos pos, BlockState state) {
int eggs = state.getValue(EGGS);
if (eggs <= 1) {
level.destroyBlock(pos, false);
return;
}
level.setBlock(pos, state.setValue(EGGS, eggs - 1), 2);
}
private boolean shouldUpdateHatchLevel(Level level, BlockPos pos) {
boolean onAmberMoss = level.getBlockState(pos.below()).getBlock() instanceof AmberMossBlock;
return level.random.nextInt(onAmberMoss ? 150 : 500) == 0;
}
private boolean canDestroyEgg(Level level, Entity entity) {
if (entity instanceof LurkingTerrorEntity || entity instanceof Bat) {
return false;
}
if (entity instanceof net.minecraft.world.entity.LivingEntity) {
return entity instanceof Player || level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING);
}
return false;
}
}
@@ -0,0 +1,58 @@
package com.craisinlord.antarchy.content.client;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.function.BooleanSupplier;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.phys.Vec3;
public final class CameraShakeClientState {
public static final double TORETERROR_RANGE = 48.0D;
public static final float TORETERROR_STRENGTH = 1.0F;
public static final double NIGHTMARE_RANGE = 32.0D;
public static final float NIGHTMARE_STRENGTH = 1.35F;
private record Source(double range, float strength, BooleanSupplier active) {
}
private static final Map<LivingEntity, Source> SOURCES = new HashMap<>();
private CameraShakeClientState() {
}
public static void register(LivingEntity entity, double range, float strength, BooleanSupplier active) {
SOURCES.putIfAbsent(entity, new Source(range, strength, active));
}
public static float getStrength(Vec3 cameraPos) {
if (SOURCES.isEmpty()) {
return 0.0F;
}
float total = 0.0F;
Iterator<Map.Entry<LivingEntity, Source>> iterator = SOURCES.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<LivingEntity, Source> entry = iterator.next();
LivingEntity entity = entry.getKey();
Source source = entry.getValue();
if (entity.isRemoved() || !entity.isAlive() || !source.active().getAsBoolean()) {
iterator.remove();
continue;
}
double distance = Math.sqrt(cameraPos.distanceToSqr(entity.position().add(0.0D, entity.getBbHeight() * 0.5D, 0.0D)));
if (distance > source.range()) {
continue;
}
total += (float) ((1.0D - distance / source.range()) * source.strength());
}
return total;
}
public static void clear() {
SOURCES.clear();
}
}
@@ -0,0 +1,26 @@
package com.craisinlord.antarchy.content.client;
public final class HerculesBeetleImpactShakeClientState {
private static int shakeTicks;
private HerculesBeetleImpactShakeClientState() {
}
public static void trigger(int ticks) {
shakeTicks = Math.max(shakeTicks, Math.max(0, ticks));
}
public static void tick() {
if (shakeTicks > 0) {
shakeTicks--;
}
}
public static int getTicks() {
return shakeTicks;
}
public static void clear() {
shakeTicks = 0;
}
}
@@ -0,0 +1,29 @@
package com.craisinlord.antarchy.content.client;
import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.item.NightmareSwordItem;
import net.minecraft.ChatFormatting;
import net.minecraft.client.Minecraft;
import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.player.Player;
public final class NightmareSwordTooltipHelper {
private NightmareSwordTooltipHelper() {
}
public static Component damageLine() {
Player player = Minecraft.getInstance().player;
float damage = player != null
? NightmareSwordItem.calculateDamage(player)
: (float) AntarchySettings.nightmareSwordBaseDamage();
return Component.translatable("tooltip.antarchy.nightmare_sword_damage", formatDamage(damage)).withStyle(ChatFormatting.RED);
}
private static String formatDamage(float damage) {
float rounded = Math.round(damage * 10.0F) / 10.0F;
if (rounded == (int) rounded) {
return String.valueOf((int) rounded);
}
return String.valueOf(rounded);
}
}
@@ -0,0 +1,23 @@
package com.craisinlord.antarchy.content.client.model;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.entity.AlphaMantisEntity;
import net.minecraft.resources.ResourceLocation;
import software.bernie.geckolib.model.GeoModel;
public class AlphaMantisModel extends GeoModel<AlphaMantisEntity> {
@Override
public ResourceLocation getModelResource(AlphaMantisEntity animatable) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "geo/mantis.geo.json");
}
@Override
public ResourceLocation getTextureResource(AlphaMantisEntity animatable) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "textures/entity/mantis_alpha.png");
}
@Override
public ResourceLocation getAnimationResource(AlphaMantisEntity animatable) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "animations/mantis.animation.json");
}
}
@@ -0,0 +1,23 @@
package com.craisinlord.antarchy.content.client.model;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.entity.CheepEntity;
import net.minecraft.resources.ResourceLocation;
import software.bernie.geckolib.model.GeoModel;
public class CheepModel extends GeoModel<CheepEntity> {
@Override
public ResourceLocation getModelResource(CheepEntity animatable) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "geo/cheep.geo.json");
}
@Override
public ResourceLocation getTextureResource(CheepEntity animatable) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "textures/entity/cheep/cheep_magenta.png");
}
@Override
public ResourceLocation getAnimationResource(CheepEntity animatable) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "animations/cheep.animation.json");
}
}
@@ -6,6 +6,11 @@ import net.minecraft.resources.ResourceLocation;
import software.bernie.geckolib.model.GeoModel;
public class CloudSharkModel extends GeoModel<CloudSharkEntity> {
public static final ResourceLocation BASE_TEXTURE =
ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "textures/entity/cloud_shark/cloud_shark.png");
public static final ResourceLocation EMISSIVE_TEXTURE =
ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "textures/entity/cloud_shark/cloud_shark_emissive.png");
@Override
public ResourceLocation getModelResource(CloudSharkEntity animatable) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "geo/cloud_shark.geo.json");
@@ -13,7 +18,7 @@ public class CloudSharkModel extends GeoModel<CloudSharkEntity> {
@Override
public ResourceLocation getTextureResource(CloudSharkEntity animatable) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "textures/entity/cloud_shark.png");
return BASE_TEXTURE;
}
@Override
@@ -0,0 +1,23 @@
package com.craisinlord.antarchy.content.client.model;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.entity.CreepingHorrorEntity;
import net.minecraft.resources.ResourceLocation;
import software.bernie.geckolib.model.GeoModel;
public class CreepingHorrorModel extends GeoModel<CreepingHorrorEntity> {
@Override
public ResourceLocation getModelResource(CreepingHorrorEntity animatable) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "geo/creeping_horror.geo.json");
}
@Override
public ResourceLocation getTextureResource(CreepingHorrorEntity animatable) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "textures/entity/creeping_horror.png");
}
@Override
public ResourceLocation getAnimationResource(CreepingHorrorEntity animatable) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "animations/creeping_horror.animation.json");
}
}
@@ -0,0 +1,28 @@
package com.craisinlord.antarchy.content.client.model;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.entity.DorrieEntity;
import net.minecraft.resources.ResourceLocation;
import software.bernie.geckolib.model.GeoModel;
public class DorrieModel extends GeoModel<DorrieEntity> {
private static final ResourceLocation TEXTURE =
ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "textures/entity/dorrie.png");
private static final ResourceLocation TEXTURE_SADDLED =
ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "textures/entity/dorrie_saddle.png");
@Override
public ResourceLocation getModelResource(DorrieEntity animatable) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "geo/dorrie.geo.json");
}
@Override
public ResourceLocation getTextureResource(DorrieEntity animatable) {
return animatable.hasSaddle() ? TEXTURE_SADDLED : TEXTURE;
}
@Override
public ResourceLocation getAnimationResource(DorrieEntity animatable) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "animations/dorrie.animation.json");
}
}
@@ -7,6 +7,9 @@ import software.bernie.geckolib.model.GeoModel;
public class EmperorScorpionModel extends GeoModel<EmperorScorpionEntity> {
public static final ResourceLocation EMISSIVE_TEXTURE =
ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "textures/entity/emperor_scorpion/emperor_scorpion_emissive.png");
@Override
public ResourceLocation getModelResource(EmperorScorpionEntity animatable) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "geo/emperor_scorpion.geo.json");
@@ -14,7 +17,7 @@ public class EmperorScorpionModel extends GeoModel<EmperorScorpionEntity> {
@Override
public ResourceLocation getTextureResource(EmperorScorpionEntity animatable) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "textures/entity/emperor_scorpion.png");
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "textures/entity/emperor_scorpion/emperor_scorpion.png");
}
@Override
@@ -0,0 +1,28 @@
package com.craisinlord.antarchy.content.client.model;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.entity.HerculesBeetleEntity;
import net.minecraft.resources.ResourceLocation;
import software.bernie.geckolib.model.GeoModel;
public class HerculesBeetleModel extends GeoModel<HerculesBeetleEntity> {
private static final ResourceLocation TEXTURE =
ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "textures/entity/hercules_beetle/hercules_beetle.png");
private static final ResourceLocation TEXTURE_SADDLED =
ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "textures/entity/hercules_beetle/hercules_beetle_saddle.png");
@Override
public ResourceLocation getModelResource(HerculesBeetleEntity animatable) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "geo/hercules_beetle.geo.json");
}
@Override
public ResourceLocation getTextureResource(HerculesBeetleEntity animatable) {
return animatable.hasSaddle() ? TEXTURE_SADDLED : TEXTURE;
}
@Override
public ResourceLocation getAnimationResource(HerculesBeetleEntity animatable) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "animations/hercules_beetle.animation.json");
}
}
@@ -0,0 +1,23 @@
package com.craisinlord.antarchy.content.client.model;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.entity.JumpyBugEntity;
import net.minecraft.resources.ResourceLocation;
import software.bernie.geckolib.model.GeoModel;
public class JumpyBugModel extends GeoModel<JumpyBugEntity> {
@Override
public ResourceLocation getModelResource(JumpyBugEntity animatable) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "geo/jumpy_bug.geo.json");
}
@Override
public ResourceLocation getTextureResource(JumpyBugEntity animatable) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "textures/entity/jumpy_bug.png");
}
@Override
public ResourceLocation getAnimationResource(JumpyBugEntity animatable) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "animations/jumpy_bug.animation.json");
}
}
@@ -12,9 +12,7 @@ public class KrakenModel extends GeoModel<KrakenEntity> {
@Override
public ResourceLocation getTextureResource(KrakenEntity animatable) {
return animatable.isPhaseTwo()
? ResourceLocation.fromNamespaceAndPath("antarchy", "textures/entity/kraken_purple.png")
: ResourceLocation.fromNamespaceAndPath("antarchy", "textures/entity/kraken.png");
return ResourceLocation.fromNamespaceAndPath("antarchy", "textures/entity/kraken.png");
}
@Override
@@ -0,0 +1,23 @@
package com.craisinlord.antarchy.content.client.model;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.entity.LurkingTerrorEntity;
import net.minecraft.resources.ResourceLocation;
import software.bernie.geckolib.model.GeoModel;
public class LurkingTerrorModel extends GeoModel<LurkingTerrorEntity> {
@Override
public ResourceLocation getModelResource(LurkingTerrorEntity animatable) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "geo/lurking_terror.geo.json");
}
@Override
public ResourceLocation getTextureResource(LurkingTerrorEntity animatable) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "textures/entity/lurking_terror.png");
}
@Override
public ResourceLocation getAnimationResource(LurkingTerrorEntity animatable) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "animations/lurking_terror.animation.json");
}
}
@@ -0,0 +1,22 @@
package com.craisinlord.antarchy.content.client.model;
import com.craisinlord.antarchy.content.entity.OctopusBombEntity;
import net.minecraft.resources.ResourceLocation;
import software.bernie.geckolib.model.GeoModel;
public class OctopusBombModel extends GeoModel<OctopusBombEntity> {
@Override
public ResourceLocation getModelResource(OctopusBombEntity animatable) {
return ResourceLocation.fromNamespaceAndPath("antarchy", "geo/octopus_bomb.geo.json");
}
@Override
public ResourceLocation getTextureResource(OctopusBombEntity animatable) {
return ResourceLocation.fromNamespaceAndPath("antarchy", "textures/entity/octopus_bomb.png");
}
@Override
public ResourceLocation getAnimationResource(OctopusBombEntity animatable) {
return ResourceLocation.fromNamespaceAndPath("antarchy", "animations/octopus_bomb.animation.json");
}
}
@@ -6,19 +6,39 @@ import net.minecraft.resources.ResourceLocation;
import software.bernie.geckolib.model.GeoModel;
public class ScorpionModel extends GeoModel<ScorpionEntity> {
private static final ResourceLocation MODEL =
ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "geo/scorpion.geo.json");
private static final ResourceLocation BLUE_TEXTURE =
ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "textures/entity/scorpion/scorpion_blue.png");
private static final ResourceLocation GREEN_TEXTURE =
ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "textures/entity/scorpion/scorpion_green.png");
private static final ResourceLocation BLUE_EMISSIVE_TEXTURE =
ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "textures/entity/scorpion/scorpion_blue_emissive.png");
private static final ResourceLocation GREEN_EMISSIVE_TEXTURE =
ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "textures/entity/scorpion/scorpion_green_emissive.png");
private static final ResourceLocation ANIMATION =
ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "animations/scorpion.animation.json");
@Override
public ResourceLocation getModelResource(ScorpionEntity animatable) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "geo/scorpion.geo.json");
return MODEL;
}
@Override
public ResourceLocation getTextureResource(ScorpionEntity animatable) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "textures/entity/scorpion.png");
return textureFor(animatable);
}
@Override
public ResourceLocation getAnimationResource(ScorpionEntity animatable) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "animations/scorpion.animation.json");
return ANIMATION;
}
public static ResourceLocation textureFor(ScorpionEntity animatable) {
return animatable.getTextureVariant() == ScorpionEntity.GREEN_VARIANT ? GREEN_TEXTURE : BLUE_TEXTURE;
}
public static ResourceLocation emissiveTextureFor(ScorpionEntity animatable) {
return animatable.getTextureVariant() == ScorpionEntity.GREEN_VARIANT ? GREEN_EMISSIVE_TEXTURE : BLUE_EMISSIVE_TEXTURE;
}
}
@@ -0,0 +1,23 @@
package com.craisinlord.antarchy.content.client.model;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.entity.SpitBugEntity;
import net.minecraft.resources.ResourceLocation;
import software.bernie.geckolib.model.GeoModel;
public class SpitBugModel extends GeoModel<SpitBugEntity> {
@Override
public ResourceLocation getModelResource(SpitBugEntity animatable) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "geo/spit_bug.geo.json");
}
@Override
public ResourceLocation getTextureResource(SpitBugEntity animatable) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "textures/entity/spit_bug.png");
}
@Override
public ResourceLocation getAnimationResource(SpitBugEntity animatable) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "animations/spit_bug.animation.json");
}
}
@@ -0,0 +1,23 @@
package com.craisinlord.antarchy.content.client.model;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.entity.StinkBugEntity;
import net.minecraft.resources.ResourceLocation;
import software.bernie.geckolib.model.GeoModel;
public class StinkBugModel extends GeoModel<StinkBugEntity> {
@Override
public ResourceLocation getModelResource(StinkBugEntity animatable) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "geo/stink_bug.geo.json");
}
@Override
public ResourceLocation getTextureResource(StinkBugEntity animatable) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "textures/entity/stink_bug.png");
}
@Override
public ResourceLocation getAnimationResource(StinkBugEntity animatable) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "animations/stink_bug.animation.json");
}
}
@@ -0,0 +1,27 @@
package com.craisinlord.antarchy.content.client.model;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.entity.ant.TermiteEntity;
import net.minecraft.resources.ResourceLocation;
import software.bernie.geckolib.model.GeoModel;
public class TermiteModel extends GeoModel<TermiteEntity> {
private static final ResourceLocation MODEL = ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "geo/termite.geo.json");
private static final ResourceLocation TEXTURE = ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "textures/entity/termite.png");
private static final ResourceLocation ANIMATIONS = ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "animations/termite.animation.json");
@Override
public ResourceLocation getModelResource(TermiteEntity animatable) {
return MODEL;
}
@Override
public ResourceLocation getTextureResource(TermiteEntity animatable) {
return TEXTURE;
}
@Override
public ResourceLocation getAnimationResource(TermiteEntity animatable) {
return ANIMATIONS;
}
}
@@ -0,0 +1,54 @@
package com.craisinlord.antarchy.content.client.particle;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.particle.Particle;
import net.minecraft.client.particle.ParticleProvider;
import net.minecraft.client.particle.ParticleRenderType;
import net.minecraft.client.particle.SpriteSet;
import net.minecraft.client.particle.TextureSheetParticle;
import net.minecraft.core.particles.SimpleParticleType;
public final class OrangeAshParticle extends TextureSheetParticle {
private final SpriteSet sprites;
private OrangeAshParticle(ClientLevel level, double x, double y, double z, double xd, double yd, double zd, SpriteSet sprites) {
super(level, x, y, z, xd, yd, zd);
this.sprites = sprites;
this.friction = 0.98F;
this.gravity = 0.0025F;
this.speedUpWhenYMotionIsBlocked = false;
this.hasPhysics = false;
this.xd = xd * 0.08D + (this.random.nextDouble() - 0.5D) * 0.01D;
this.yd = yd * 0.02D - 0.01D - this.random.nextDouble() * 0.01D;
this.zd = zd * 0.08D + (this.random.nextDouble() - 0.5D) * 0.01D;
this.quadSize *= 0.75F + this.random.nextFloat() * 0.5F;
this.lifetime = 40 + this.random.nextInt(28);
this.setColor(0.96F, 0.46F, 0.12F);
this.setAlpha(0.72F);
this.pickSprite(sprites);
}
@Override
public void tick() {
super.tick();
this.pickSprite(this.sprites);
}
@Override
public ParticleRenderType getRenderType() {
return ParticleRenderType.PARTICLE_SHEET_OPAQUE;
}
public static final class Provider implements ParticleProvider<SimpleParticleType> {
private final SpriteSet sprites;
public Provider(SpriteSet sprites) {
this.sprites = sprites;
}
@Override
public Particle createParticle(SimpleParticleType type, ClientLevel level, double x, double y, double z, double xd, double yd, double zd) {
return new OrangeAshParticle(level, x, y, z, xd, yd, zd, this.sprites);
}
}
}
@@ -0,0 +1,15 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.content.client.model.AlphaMantisModel;
import com.craisinlord.antarchy.content.entity.AlphaMantisEntity;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import software.bernie.geckolib.renderer.GeoEntityRenderer;
public class AlphaMantisRenderer extends GeoEntityRenderer<AlphaMantisEntity> {
public AlphaMantisRenderer(EntityRendererProvider.Context context) {
super(context, new AlphaMantisModel());
this.shadowRadius = 1.5F;
// Mantis renders at 1.25x; the alpha is 1.5x bigger overall
this.withScale(1.875F);
}
}
@@ -35,11 +35,10 @@ public class AntCarriedFoodGeoLayer extends BlockAndItemGeoLayer<BaseAntEntity>
@Override
protected void renderStackForBone(PoseStack poseStack, GeoBone bone, ItemStack stack, BaseAntEntity animatable, MultiBufferSource bufferSource, float partialTick, int packedLight, int packedOverlay) {
double yOffset = animatable.isDancing() ? -0.055D : -0.04D;
poseStack.translate(0.0D, yOffset, -0.42D);
poseStack.mulPose(Axis.XP.rotationDegrees(90.0F));
poseStack.mulPose(Axis.ZP.rotationDegrees(180.0F));
poseStack.scale(0.4F, 0.4F, 0.4F);
double yOffset = animatable.isDancing() ? 0.58D : 0.15D;
poseStack.translate(0.0D, yOffset, -0.50D);
poseStack.mulPose(Axis.YP.rotationDegrees(90.0F));
poseStack.scale(0.5F, 0.5F, 0.5F);
super.renderStackForBone(poseStack, bone, stack, animatable, bufferSource, partialTick, packedLight, packedOverlay);
}
}
@@ -1,60 +0,0 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.content.entity.ant.BaseAntEntity;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.math.Axis;
import net.minecraft.client.model.SilverfishModel;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.entity.ItemRenderer;
import net.minecraft.client.renderer.entity.RenderLayerParent;
import net.minecraft.client.renderer.entity.layers.RenderLayer;
import net.minecraft.client.renderer.texture.OverlayTexture;
import net.minecraft.world.item.ItemDisplayContext;
import net.minecraft.world.item.ItemStack;
public class AntCarriedFoodLayer<T extends BaseAntEntity> extends RenderLayer<T, SilverfishModel<T>> {
private final ItemRenderer itemRenderer;
public AntCarriedFoodLayer(RenderLayerParent<T, SilverfishModel<T>> renderer, ItemRenderer itemRenderer) {
super(renderer);
this.itemRenderer = itemRenderer;
}
@Override
public void render(
PoseStack poseStack,
MultiBufferSource buffer,
int packedLight,
T ant,
float limbSwing,
float limbSwingAmount,
float partialTick,
float ageInTicks,
float netHeadYaw,
float headPitch
) {
ItemStack carriedFood = ant.getCarriedFood();
if (carriedFood.isEmpty()) {
return;
}
poseStack.pushPose();
poseStack.translate(0.0F, 0.08F, -0.06F);
poseStack.mulPose(Axis.XP.rotationDegrees(90.0F));
poseStack.mulPose(Axis.ZP.rotationDegrees(180.0F));
poseStack.scale(0.22F, 0.22F, 0.22F);
this.itemRenderer.renderStatic(
ant,
carriedFood,
ItemDisplayContext.FIXED,
false,
poseStack,
buffer,
ant.level(),
packedLight,
OverlayTexture.NO_OVERLAY,
ant.getId()
);
poseStack.popPose();
}
}
@@ -0,0 +1,155 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.Antarchy;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.BufferBuilder;
import com.mojang.blaze3d.vertex.BufferUploader;
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
import com.mojang.blaze3d.vertex.MeshData;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.Tesselator;
import com.mojang.blaze3d.vertex.VertexBuffer;
import com.mojang.blaze3d.vertex.VertexFormat;
import java.util.Random;
import net.minecraft.client.Camera;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.Level;
public final class CavarynSkyRenderer {
public static final ResourceKey<Level> CAVARYN = ResourceKey.create(
Registries.DIMENSION,
ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "cavaryn")
);
@org.jetbrains.annotations.Nullable
private static VertexBuffer orangeStarBuffer;
private CavarynSkyRenderer() {
}
public static boolean shouldRender(ClientLevel level) {
return level.dimension().equals(CAVARYN);
}
public static void render(
Minecraft minecraft,
ClientLevel level,
PoseStack poseStack,
org.joml.Matrix4f projectionMatrix,
float partialTick,
Camera camera,
boolean isFoggy,
Runnable setupFog,
VertexBuffer skyBuffer,
VertexBuffer darkBuffer
) {
setupFog.run();
if (isFoggy || minecraft.gameRenderer.getMainCamera() != camera) {
return;
}
float red = 0.015F;
float green = 0.085F;
float blue = 0.035F;
RenderSystem.depthMask(false);
RenderSystem.setShaderColor(red, green, blue, 1.0F);
skyBuffer.bind();
skyBuffer.drawWithShader(poseStack.last().pose(), projectionMatrix, GameRenderer.getPositionShader());
VertexBuffer.unbind();
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
float starBrightness = 0.8F;
if (orangeStarBuffer == null) {
orangeStarBuffer = buildOrangeStarBuffer();
}
RenderSystem.enableBlend();
RenderSystem.defaultBlendFunc();
RenderSystem.setShaderColor(starBrightness, starBrightness, starBrightness, starBrightness);
orangeStarBuffer.bind();
orangeStarBuffer.drawWithShader(poseStack.last().pose(), projectionMatrix, GameRenderer.getPositionColorShader());
VertexBuffer.unbind();
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
RenderSystem.disableBlend();
if (camera.getEntity() != null) {
double horizon = camera.getEntity().getEyePosition(partialTick).y - level.getLevelData().getHorizonHeight(level);
if (horizon < 0.0D) {
poseStack.pushPose();
poseStack.translate(0.0F, 12.0F, 0.0F);
darkBuffer.bind();
darkBuffer.drawWithShader(poseStack.last().pose(), projectionMatrix, GameRenderer.getPositionShader());
VertexBuffer.unbind();
poseStack.popPose();
}
}
RenderSystem.depthMask(true);
}
private static VertexBuffer buildOrangeStarBuffer() {
Random random = new Random(548721L);
BufferBuilder builder = Tesselator.getInstance().begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR);
for (int i = 0; i < 1800; i++) {
double x = random.nextFloat() * 2.0F - 1.0F;
double y = random.nextFloat() * 2.0F - 1.0F;
double z = random.nextFloat() * 2.0F - 1.0F;
double size = 0.12F + random.nextFloat() * 0.12F;
double lenSq = x * x + y * y + z * z;
if (lenSq < 1.0D && lenSq > 0.01D) {
double len = 1.0D / Math.sqrt(lenSq);
x *= len;
y *= len;
z *= len;
double sx = x * 100.0D;
double sy = y * 100.0D;
double sz = z * 100.0D;
double yaw = Math.atan2(x, z);
double sinYaw = Math.sin(yaw);
double cosYaw = Math.cos(yaw);
double pitch = Math.atan2(Math.sqrt(x * x + z * z), y);
double sinPitch = Math.sin(pitch);
double cosPitch = Math.cos(pitch);
double rot = random.nextDouble() * Math.PI * 2.0D;
double sinRot = Math.sin(rot);
double cosRot = Math.cos(rot);
float[] color = pickStarColor(random);
for (int j = 0; j < 4; j++) {
double u = (j & 2) - 1;
double v = (j + 1 & 2) - 1;
double px = u * cosRot - v * sinRot;
double py = v * cosRot + u * sinRot;
double qx = px * sinPitch;
double qy = -px * cosPitch;
double rx = qy * sinYaw - py * cosYaw;
double ry = py * sinYaw + qy * cosYaw;
builder.addVertex((float) (sx + rx * size), (float) (sy + qx * size), (float) (sz + ry * size))
.setColor(color[0], color[1], color[2], 1.0F);
}
}
}
MeshData mesh = builder.buildOrThrow();
VertexBuffer buffer = new VertexBuffer(VertexBuffer.Usage.STATIC);
buffer.bind();
buffer.upload(mesh);
VertexBuffer.unbind();
return buffer;
}
private static float[] pickStarColor(Random random) {
float roll = random.nextFloat();
if (roll < 0.70F) return new float[]{1.00F, 0.55F, 0.14F};
if (roll < 0.90F) return new float[]{1.00F, 0.70F, 0.24F};
return new float[]{1.00F, 0.84F, 0.40F};
}
}
@@ -0,0 +1,12 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.content.client.model.CheepModel;
import com.craisinlord.antarchy.content.entity.CheepEntity;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import software.bernie.geckolib.renderer.GeoEntityRenderer;
public class CheepRenderer extends GeoEntityRenderer<CheepEntity> {
public CheepRenderer(EntityRendererProvider.Context context) {
super(context, new CheepModel());
}
}
@@ -2,8 +2,17 @@ package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.content.client.model.CloudSharkModel;
import com.craisinlord.antarchy.content.entity.cloud_shark.CloudSharkEntity;
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.client.renderer.texture.OverlayTexture;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.Nullable;
import software.bernie.geckolib.cache.object.BakedGeoModel;
import software.bernie.geckolib.renderer.GeoEntityRenderer;
import software.bernie.geckolib.renderer.layer.GeoRenderLayer;
public class CloudSharkRenderer extends GeoEntityRenderer<CloudSharkEntity> {
private static final float SHADOW_RADIUS = 0.6F;
@@ -11,5 +20,37 @@ public class CloudSharkRenderer extends GeoEntityRenderer<CloudSharkEntity> {
public CloudSharkRenderer(EntityRendererProvider.Context context) {
super(context, new CloudSharkModel());
this.shadowRadius = SHADOW_RADIUS;
this.addRenderLayer(new CloudSharkEmissiveLayer(this));
}
@Override
public RenderType getRenderType(CloudSharkEntity animatable, ResourceLocation texture, @Nullable MultiBufferSource bufferSource, float partialTick) {
return RenderType.entityCutoutNoCull(texture);
}
private static final class CloudSharkEmissiveLayer extends GeoRenderLayer<CloudSharkEntity> {
private CloudSharkEmissiveLayer(GeoEntityRenderer<CloudSharkEntity> renderer) {
super(renderer);
}
@Override
public void render(PoseStack poseStack, CloudSharkEntity animatable, BakedGeoModel bakedModel, @Nullable RenderType renderType,
MultiBufferSource bufferSource, @Nullable VertexConsumer buffer, float partialTick,
int packedLight, int packedOverlay) {
RenderType emissiveType = RenderType.eyes(CloudSharkModel.EMISSIVE_TEXTURE);
VertexConsumer emissiveBuffer = bufferSource.getBuffer(emissiveType);
this.getRenderer().reRender(
bakedModel,
poseStack,
bufferSource,
animatable,
emissiveType,
emissiveBuffer,
partialTick,
0xF000F0,
OverlayTexture.NO_OVERLAY,
0xFFFFFFFF
);
}
}
}
@@ -0,0 +1,29 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.content.client.model.CreepingHorrorModel;
import com.craisinlord.antarchy.content.entity.CreepingHorrorEntity;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import com.mojang.math.Axis;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import org.jetbrains.annotations.Nullable;
import software.bernie.geckolib.cache.object.BakedGeoModel;
import software.bernie.geckolib.renderer.GeoEntityRenderer;
public class CreepingHorrorRenderer extends GeoEntityRenderer<CreepingHorrorEntity> {
public CreepingHorrorRenderer(EntityRendererProvider.Context context) {
super(context, new CreepingHorrorModel());
this.shadowRadius = 0.5F;
this.withScale(0.85F);
}
@Override
public void preRender(PoseStack poseStack, CreepingHorrorEntity 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);
if (animatable.isWallClimbing()) {
poseStack.mulPose(Axis.XP.rotationDegrees(-90.0F));
poseStack.translate(0, -0.5, 0.5);
}
}
}
@@ -0,0 +1,12 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.content.client.model.DorrieModel;
import com.craisinlord.antarchy.content.entity.DorrieEntity;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import software.bernie.geckolib.renderer.GeoEntityRenderer;
public class DorrieRenderer extends GeoEntityRenderer<DorrieEntity> {
public DorrieRenderer(EntityRendererProvider.Context context) {
super(context, new DorrieModel());
}
}
@@ -2,14 +2,59 @@ package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.content.client.model.EmperorScorpionModel;
import com.craisinlord.antarchy.content.entity.EmperorScorpionEntity;
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.client.renderer.texture.OverlayTexture;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.Nullable;
import software.bernie.geckolib.cache.object.BakedGeoModel;
import software.bernie.geckolib.renderer.GeoEntityRenderer;
import software.bernie.geckolib.renderer.layer.GeoRenderLayer;
public class EmperorScorpionRenderer extends GeoEntityRenderer<EmperorScorpionEntity> {
public EmperorScorpionRenderer(EntityRendererProvider.Context context) {
super(context, new EmperorScorpionModel());
this.shadowRadius = 3.0F;
this.withScale(1.5F);
this.addRenderLayer(new EmperorScorpionEmissiveLayer(this));
}
@Override
public RenderType getRenderType(EmperorScorpionEntity animatable, ResourceLocation texture, @Nullable MultiBufferSource bufferSource, float partialTick) {
return RenderType.entityCutoutNoCull(texture);
}
@Override
protected float getDeathMaxRotation(EmperorScorpionEntity animatable) {
return 0.0F;
}
private static final class EmperorScorpionEmissiveLayer extends GeoRenderLayer<EmperorScorpionEntity> {
private EmperorScorpionEmissiveLayer(GeoEntityRenderer<EmperorScorpionEntity> renderer) {
super(renderer);
}
@Override
public void render(PoseStack poseStack, EmperorScorpionEntity animatable, BakedGeoModel bakedModel, @Nullable RenderType renderType,
MultiBufferSource bufferSource, @Nullable VertexConsumer buffer, float partialTick,
int packedLight, int packedOverlay) {
RenderType emissiveType = RenderType.eyes(EmperorScorpionModel.EMISSIVE_TEXTURE);
VertexConsumer emissiveBuffer = bufferSource.getBuffer(emissiveType);
this.getRenderer().reRender(
bakedModel,
poseStack,
bufferSource,
animatable,
emissiveType,
emissiveBuffer,
partialTick,
0xF000F0,
OverlayTexture.NO_OVERLAY,
0xFFFFFFFF
);
}
}
}
@@ -0,0 +1,12 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.content.client.model.HerculesBeetleModel;
import com.craisinlord.antarchy.content.entity.HerculesBeetleEntity;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import software.bernie.geckolib.renderer.GeoEntityRenderer;
public class HerculesBeetleRenderer extends GeoEntityRenderer<HerculesBeetleEntity> {
public HerculesBeetleRenderer(EntityRendererProvider.Context context) {
super(context, new HerculesBeetleModel());
}
}
@@ -0,0 +1,44 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.content.client.model.JumpyBugModel;
import com.craisinlord.antarchy.content.entity.JumpyBugEntity;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import com.mojang.math.Axis;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.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;
import software.bernie.geckolib.util.Color;
public class JumpyBugRenderer extends GeoEntityRenderer<JumpyBugEntity> {
public JumpyBugRenderer(EntityRendererProvider.Context context) {
super(context, new JumpyBugModel());
this.shadowRadius = 6.0F;
}
@Override
public RenderType getRenderType(JumpyBugEntity animatable, ResourceLocation texture, @Nullable MultiBufferSource bufferSource, float partialTick) {
return animatable.isCamouflaged() ? RenderType.entityTranslucent(texture) : RenderType.entityCutoutNoCull(texture);
}
@Override
public Color getRenderColor(JumpyBugEntity animatable, float partialTick, int packedLight) {
int alpha = Math.max(10, Math.min(255, Math.round(animatable.getVisualAlpha(partialTick) * 255.0F)));
return Color.ofRGBA(255, 255, 255, alpha);
}
@Override
public void preRender(PoseStack poseStack, JumpyBugEntity 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);
this.shadowRadius = animatable.isCamouflaged() ? 1.0F : 6.0F;
if (animatable.isClingingToCeiling()) {
poseStack.translate(0.0F, animatable.getBbHeight() + 0.05F, 0.0F);
poseStack.mulPose(Axis.ZP.rotationDegrees(180.0F));
}
}
}
@@ -8,7 +8,7 @@ import software.bernie.geckolib.renderer.GeoEntityRenderer;
public class KrakenRenderer extends GeoEntityRenderer<KrakenEntity> {
public KrakenRenderer(EntityRendererProvider.Context context) {
super(context, new KrakenModel());
this.shadowRadius = 3.6F;
this.withScale(3.0F);
this.shadowRadius = 2.0F;
this.withScale(1.5F);
}
}
@@ -0,0 +1,14 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.content.client.model.LurkingTerrorModel;
import com.craisinlord.antarchy.content.entity.LurkingTerrorEntity;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import software.bernie.geckolib.renderer.GeoEntityRenderer;
public class LurkingTerrorRenderer extends GeoEntityRenderer<LurkingTerrorEntity> {
public LurkingTerrorRenderer(EntityRendererProvider.Context context) {
super(context, new LurkingTerrorModel());
this.shadowRadius = 0.5F;
this.withScale(0.85F);
}
}
@@ -0,0 +1,13 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.content.client.model.OctopusBombModel;
import com.craisinlord.antarchy.content.entity.OctopusBombEntity;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import software.bernie.geckolib.renderer.GeoEntityRenderer;
public class OctopusBombRenderer extends GeoEntityRenderer<OctopusBombEntity> {
public OctopusBombRenderer(EntityRendererProvider.Context context) {
super(context, new OctopusBombModel());
this.shadowRadius = 1.2F;
}
}
@@ -2,13 +2,53 @@ package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.content.client.model.ScorpionModel;
import com.craisinlord.antarchy.content.entity.ScorpionEntity;
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.client.renderer.texture.OverlayTexture;
import org.jetbrains.annotations.Nullable;
import software.bernie.geckolib.cache.object.BakedGeoModel;
import software.bernie.geckolib.renderer.GeoEntityRenderer;
import software.bernie.geckolib.renderer.layer.GeoRenderLayer;
public class ScorpionRenderer extends GeoEntityRenderer<ScorpionEntity> {
public ScorpionRenderer(EntityRendererProvider.Context context) {
super(context, new ScorpionModel());
this.shadowRadius = 0.6F;
this.addRenderLayer(new ScorpionEmissiveLayer(this));
}
@Override
public RenderType getRenderType(ScorpionEntity animatable, net.minecraft.resources.ResourceLocation texture, @Nullable MultiBufferSource bufferSource, float partialTick) {
return RenderType.entityCutoutNoCull(texture);
}
private static final class ScorpionEmissiveLayer extends GeoRenderLayer<ScorpionEntity> {
private ScorpionEmissiveLayer(GeoEntityRenderer<ScorpionEntity> renderer) {
super(renderer);
}
@Override
public void render(PoseStack poseStack, ScorpionEntity animatable, BakedGeoModel bakedModel, @Nullable RenderType renderType,
MultiBufferSource bufferSource, @Nullable VertexConsumer buffer, float partialTick,
int packedLight, int packedOverlay) {
RenderType emissiveType = RenderType.eyes(ScorpionModel.emissiveTextureFor(animatable));
VertexConsumer emissiveBuffer = bufferSource.getBuffer(emissiveType);
this.getRenderer().reRender(
bakedModel,
poseStack,
bufferSource,
animatable,
emissiveType,
emissiveBuffer,
partialTick,
0xF000F0,
OverlayTexture.NO_OVERLAY,
0xFFFFFFFF
);
}
}
}
@@ -0,0 +1,73 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.content.entity.SpitBugProjectileEntity;
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.EntityRenderer;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.client.renderer.texture.OverlayTexture;
import net.minecraft.core.BlockPos;
import net.minecraft.resources.ResourceLocation;
import org.joml.Matrix4f;
public class SpitBugProjectileRenderer extends EntityRenderer<SpitBugProjectileEntity> {
private static final ResourceLocation PROJECTILE_TEXTURE =
ResourceLocation.withDefaultNamespace("textures/entity/enderdragon/dragon_fireball.png");
public SpitBugProjectileRenderer(EntityRendererProvider.Context context) {
super(context);
}
@Override
protected int getBlockLightLevel(SpitBugProjectileEntity entity, BlockPos pos) {
return 15;
}
@Override
public void render(SpitBugProjectileEntity entity, float entityYaw, float partialTick, PoseStack poseStack, MultiBufferSource buffer, int packedLight) {
poseStack.pushPose();
poseStack.mulPose(this.entityRenderDispatcher.cameraOrientation());
float scale = entity.getRenderScale(partialTick);
poseStack.scale(scale, scale, scale);
VertexConsumer vertexConsumer = buffer.getBuffer(RenderType.entityTranslucent(PROJECTILE_TEXTURE));
PoseStack.Pose pose = poseStack.last();
Matrix4f matrix4f = pose.pose();
float alpha = entity.isLingering() ? 0.78F : 0.95F;
addVertex(vertexConsumer, pose, matrix4f, -0.5F, -0.5F, 0.0F, 1.0F, alpha, packedLight);
addVertex(vertexConsumer, pose, matrix4f, 0.5F, -0.5F, 1.0F, 1.0F, alpha, packedLight);
addVertex(vertexConsumer, pose, matrix4f, 0.5F, 0.5F, 1.0F, 0.0F, alpha, packedLight);
addVertex(vertexConsumer, pose, matrix4f, -0.5F, 0.5F, 0.0F, 0.0F, alpha, packedLight);
poseStack.popPose();
super.render(entity, entityYaw, partialTick, poseStack, buffer, packedLight);
}
private static void addVertex(
VertexConsumer vertexConsumer,
PoseStack.Pose pose,
Matrix4f matrix4f,
float x,
float y,
float u,
float v,
float alpha,
int packedLight
) {
vertexConsumer.addVertex(matrix4f, x, y, 0.0F)
.setColor(0.55F, 1.0F, 0.35F, alpha)
.setUv(u, v)
.setOverlay(OverlayTexture.NO_OVERLAY)
.setLight(packedLight)
.setNormal(pose, 0.0F, 0.0F, 1.0F);
}
@Override
public ResourceLocation getTextureLocation(SpitBugProjectileEntity entity) {
return PROJECTILE_TEXTURE;
}
}
@@ -0,0 +1,14 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.content.client.model.SpitBugModel;
import com.craisinlord.antarchy.content.entity.SpitBugEntity;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import software.bernie.geckolib.renderer.GeoEntityRenderer;
public class SpitBugRenderer extends GeoEntityRenderer<SpitBugEntity> {
public SpitBugRenderer(EntityRendererProvider.Context context) {
super(context, new SpitBugModel());
this.shadowRadius = 1.6F;
this.withScale(0.85F);
}
}
@@ -0,0 +1,17 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.content.client.model.StinkBugModel;
import com.craisinlord.antarchy.content.entity.StinkBugEntity;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import software.bernie.geckolib.renderer.GeoEntityRenderer;
public class StinkBugRenderer extends GeoEntityRenderer<StinkBugEntity> {
private static final float ADULT_SCALE = 0.95F;
private static final float ADULT_SHADOW_RADIUS = 0.2F;
public StinkBugRenderer(EntityRendererProvider.Context context) {
super(context, new StinkBugModel());
this.shadowRadius = ADULT_SHADOW_RADIUS;
this.withScale(ADULT_SCALE);
}
}
@@ -0,0 +1,35 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.content.client.model.TermiteModel;
import com.craisinlord.antarchy.content.entity.ant.TermiteEntity;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import org.jetbrains.annotations.Nullable;
import software.bernie.geckolib.cache.object.BakedGeoModel;
import software.bernie.geckolib.renderer.GeoEntityRenderer;
public class TermiteRenderer extends GeoEntityRenderer<TermiteEntity> {
private static final float ADULT_SHADOW_RADIUS = 0.3F;
private static final float ADULT_SCALE = 0.85F;
private static final float BABY_SCALE = 0.5F;
public TermiteRenderer(EntityRendererProvider.Context context) {
super(context, new TermiteModel());
this.shadowRadius = ADULT_SHADOW_RADIUS;
}
@Override
public void preRender(PoseStack poseStack, TermiteEntity 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);
if (animatable.isBaby()) {
poseStack.scale(ADULT_SCALE * BABY_SCALE, ADULT_SCALE * BABY_SCALE, ADULT_SCALE * BABY_SCALE);
this.shadowRadius = ADULT_SHADOW_RADIUS * BABY_SCALE;
} else {
poseStack.scale(ADULT_SCALE, ADULT_SCALE, ADULT_SCALE);
this.shadowRadius = ADULT_SHADOW_RADIUS;
}
}
}
@@ -9,7 +9,7 @@ public class ToreterrorRenderer extends GeoEntityRenderer<ToreterrorEntity> {
public ToreterrorRenderer(EntityRendererProvider.Context context) {
super(context, new ToreterrorModel());
this.shadowRadius = 2.0F;
this.shadowRadius = 1.6F;
}
@Override
@@ -24,7 +24,6 @@ public class UpwardFallingBlockRenderer extends EntityRenderer<UpwardFallingBloc
if (blockState.isAir()) return;
poseStack.pushPose();
// Center the block model on the entity origin
poseStack.translate(-0.5, 0.0, -0.5);
Minecraft.getInstance().getBlockRenderer().renderSingleBlock(
blockState, poseStack, bufferSource, light, OverlayTexture.NO_OVERLAY
@@ -29,7 +29,8 @@ public class WaterBombRenderer extends EntityRenderer<WaterBombEntity> {
public void render(WaterBombEntity entity, float entityYaw, float partialTick, PoseStack poseStack, MultiBufferSource buffer, int packedLight) {
poseStack.pushPose();
poseStack.mulPose(this.entityRenderDispatcher.cameraOrientation());
poseStack.scale(0.5F, 0.5F, 0.5F);
float scale = entity.isHuge() ? 1.5F : 0.5F;
poseStack.scale(scale, scale, scale);
VertexConsumer vertexConsumer = buffer.getBuffer(RenderType.entityTranslucent(WATER_TEXTURE));
PoseStack.Pose pose = poseStack.last();
@@ -5,6 +5,7 @@ import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.Level;
import org.jetbrains.annotations.Nullable;
public final class AntarchyDamageSources {
public static DamageSource krakenMauling(ServerLevel level, Entity attacker) {
@@ -87,6 +88,34 @@ public final class AntarchyDamageSources {
);
}
public static DamageSource toreterrorSpin(ServerLevel level, Entity attacker) {
return new DamageSource(
level.registryAccess()
.registryOrThrow(Registries.DAMAGE_TYPE)
.getHolderOrThrow(AntarchyDamageTypes.TORETERROR_SPIN),
attacker
);
}
public static DamageSource toreterrorJump(ServerLevel level, Entity attacker) {
return new DamageSource(
level.registryAccess()
.registryOrThrow(Registries.DAMAGE_TYPE)
.getHolderOrThrow(AntarchyDamageTypes.TORETERROR_JUMP),
attacker
);
}
public static DamageSource waterSoaked(Level level, @Nullable Entity directEntity, @Nullable Entity causingEntity) {
return new DamageSource(
level.registryAccess()
.registryOrThrow(Registries.DAMAGE_TYPE)
.getHolderOrThrow(AntarchyDamageTypes.WATER_SOAKED),
directEntity,
causingEntity
);
}
private AntarchyDamageSources() {
}
}
@@ -61,6 +61,24 @@ public final class AntarchyDamageTypes {
ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "cornea_stalk_prick")
);
public static final ResourceKey<DamageType> TORETERROR_SPIN =
ResourceKey.create(
Registries.DAMAGE_TYPE,
ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "toreterror_spin")
);
public static final ResourceKey<DamageType> TORETERROR_JUMP =
ResourceKey.create(
Registries.DAMAGE_TYPE,
ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "toreterror_jump")
);
public static final ResourceKey<DamageType> WATER_SOAKED =
ResourceKey.create(
Registries.DAMAGE_TYPE,
ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "water_soaked")
);
private AntarchyDamageTypes() {
}
}
@@ -0,0 +1,23 @@
package com.craisinlord.antarchy.content.effect;
import com.craisinlord.antarchy.content.StinkyBehavior;
import net.minecraft.world.effect.MobEffect;
import net.minecraft.world.effect.MobEffectCategory;
import net.minecraft.world.entity.LivingEntity;
public final class StinkyMobEffect extends MobEffect {
public StinkyMobEffect() {
super(MobEffectCategory.HARMFUL, 0x66773A);
}
@Override
public boolean applyEffectTick(LivingEntity entity, int amplifier) {
StinkyBehavior.tickStinkyTrail(entity);
return true;
}
@Override
public boolean shouldApplyEffectTickThisTick(int duration, int amplifier) {
return true;
}
}
@@ -0,0 +1,184 @@
package com.craisinlord.antarchy.content.entity;
import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.content.AntarchyTags;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerBossEvent;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.RandomSource;
import net.minecraft.world.BossEvent;
import net.minecraft.world.Difficulty;
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.ai.attributes.AttributeSupplier;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.monster.Monster;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.ServerLevelAccessor;
import net.minecraft.world.level.block.LeavesBlock;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.phys.AABB;
import org.jetbrains.annotations.Nullable;
public class AlphaMantisEntity extends MantisEntity {
private final ServerBossEvent bossEvent =
new ServerBossEvent(Component.translatable("entity.antarchy.alpha_mantis"), BossEvent.BossBarColor.GREEN, BossEvent.BossBarOverlay.PROGRESS);
private int summonCooldownTicks;
public AlphaMantisEntity(EntityType<? extends AlphaMantisEntity> entityType, Level level) {
super(entityType, level);
this.xpReward = 60;
this.summonCooldownTicks = AntarchySettings.alphaMantisSummonIntervalTicks() / 2;
}
public static AttributeSupplier.Builder createAttributes() {
return Mob.createMobAttributes()
.add(Attributes.MAX_HEALTH, AntarchySettings.alphaMantisHealth())
.add(Attributes.ATTACK_DAMAGE, AntarchySettings.alphaMantisAttackDamage())
.add(Attributes.MOVEMENT_SPEED, AntarchySettings.alphaMantisMovementSpeed())
.add(Attributes.FLYING_SPEED, AntarchySettings.alphaMantisFlyingSpeed())
.add(Attributes.FOLLOW_RANGE, 48.0D)
.add(Attributes.ARMOR, 8.0D)
.add(Attributes.KNOCKBACK_RESISTANCE, 0.8D);
}
public static boolean checkAlphaMantisSpawnRules(EntityType<AlphaMantisEntity> entityType, ServerLevelAccessor level, MobSpawnType spawnReason, BlockPos pos, RandomSource random) {
if (spawnReason == MobSpawnType.SPAWN_EGG || spawnReason == MobSpawnType.SPAWNER || spawnReason == MobSpawnType.COMMAND) {
return true;
}
if (level.getDifficulty() == Difficulty.PEACEFUL) {
return false;
}
if (spawnReason != MobSpawnType.NATURAL) {
return false;
}
boolean atSurface = level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, pos).getY() <= pos.getY();
boolean posOpen = level.isEmptyBlock(pos) && level.isEmptyBlock(pos.above()) && level.getFluidState(pos).isEmpty();
boolean onSolidGround = level.getBlockState(pos.below()).isFaceSturdy(level, pos.below(), Direction.UP)
&& !(level.getBlockState(pos.below()).getBlock() instanceof LeavesBlock);
if (!posOpen || !onSolidGround || !atSurface) {
return false;
}
// Only one alpha at a time in the area
if (!level.getLevel().getEntitiesOfClass(AlphaMantisEntity.class, new AABB(pos).inflate(128.0D), Entity::isAlive).isEmpty()) {
return false;
}
boolean ignoreLightLevel = AntarchySettings.mantisIgnoreLightLevel();
// Daytime overworld spawns can't pass the vanilla darkness check, so no light gate here
if (level.getLevel().isDay() && level.getBiome(pos).is(AntarchyTags.Biomes.MANTIS_OVERWORLD_SPAWN_BIOMES)) {
return level.canSeeSky(pos);
}
if (level.getLevel().isNight() && level.getBiome(pos).is(AntarchyTags.Biomes.MANTIS_SPAWN_BIOMES)) {
return ignoreLightLevel || Monster.checkMonsterSpawnRules(entityType, level, spawnReason, pos, random);
}
return false;
}
@Override
public void tick() {
super.tick();
if (this.level().isClientSide) {
return;
}
if (this.isDeadOrDying()) {
this.bossEvent.setProgress(0.0F);
return;
}
this.bossEvent.setProgress(this.getHealth() / this.getMaxHealth());
this.tickMinionSummon();
}
private void tickMinionSummon() {
LivingEntity target = this.getTarget();
if (target == null || !target.isAlive()) {
return;
}
if (this.summonCooldownTicks > 0) {
this.summonCooldownTicks--;
return;
}
this.summonCooldownTicks = AntarchySettings.alphaMantisSummonIntervalTicks();
if (!(this.level() instanceof ServerLevel serverLevel)) {
return;
}
int nearbyMinions = serverLevel.getEntitiesOfClass(
MantisEntity.class,
this.getBoundingBox().inflate(32.0D),
mantis -> mantis.isAlive() && !(mantis instanceof AlphaMantisEntity)
).size();
int maxMinions = AntarchySettings.alphaMantisMaxMinions();
if (nearbyMinions >= maxMinions) {
return;
}
int toSummon = Math.min(1 + this.random.nextInt(2), maxMinions - nearbyMinions);
for (int i = 0; i < toSummon; i++) {
MantisEntity minion = AntarchyObjects.MANTIS.get().create(serverLevel);
if (minion == null) {
continue;
}
double angle = this.random.nextDouble() * Math.PI * 2.0D;
double distance = 3.0D + this.random.nextDouble() * 3.0D;
minion.moveTo(
this.getX() + Math.cos(angle) * distance,
this.getY() + 0.5D,
this.getZ() + Math.sin(angle) * distance,
this.random.nextFloat() * 360.0F,
0.0F
);
minion.finalizeSpawn(serverLevel, serverLevel.getCurrentDifficultyAt(minion.blockPosition()), MobSpawnType.MOB_SUMMONED, null);
minion.setTarget(target);
serverLevel.addFreshEntity(minion);
}
this.playSound(com.craisinlord.antarchy.content.AntarchySoundEvents.MANTIS_AMBIENT.get(), 2.0F, 0.6F);
}
@Override
public void setTarget(@Nullable LivingEntity target) {
super.setTarget(target);
// A boss that despawns mid-fight is no boss — stay loaded once engaged
if (target != null) {
this.setPersistenceRequired();
}
}
@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 void setCustomName(@Nullable Component name) {
super.setCustomName(name);
this.bossEvent.setName(this.getDisplayName());
}
}
@@ -13,7 +13,6 @@ import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.ai.goal.Goal;
import net.minecraft.world.entity.animal.Cow;
import net.minecraft.world.entity.Shearable;
import net.minecraft.core.BlockPos;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.entity.player.Player;
@@ -21,7 +20,6 @@ import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.phys.Vec3;
import software.bernie.geckolib.animatable.GeoEntity;
import software.bernie.geckolib.animatable.instance.AnimatableInstanceCache;
@@ -47,7 +45,7 @@ public abstract class AppleCowEntity extends Cow implements GeoEntity, Shearable
private static final String NAP_COOLDOWN_KEY = "NapCooldown";
private static final String QUIRK_TICKS_KEY = "QuirkTicks";
private static final String SHEARED_KEY = "Sheared";
private static final String GRASS_EAT_TICKS_KEY = "GrassEatTicks";
private static final String REGROW_TICKS_KEY = "RegrowTicks";
private static final String MAIN_CONTROLLER = "main_controller";
private static final String SLEEP_TRANSITION_CONTROLLER = "sleep_transition_controller";
private static final String START_SLEEP_TRIGGER = "start_sleep";
@@ -60,7 +58,7 @@ public abstract class AppleCowEntity extends Cow implements GeoEntity, Shearable
private static final int DAY_NAP_MAX_TICKS = 20 * 60 * 2;
private static final int DAY_NAP_COOLDOWN_TICKS = 20 * 60 * 10;
private static final int NIGHT_NAP_COOLDOWN_TICKS = 20 * 60 * 8;
private static final int GRASS_REGROW_TICKS = 20 * 60 * 3;
private static final int APPLE_REGROW_TICKS = 20 * 60 * 3;
private static final RawAnimation WALK_ANIM = RawAnimation.begin().thenLoop("walk");
private static final RawAnimation IDLE_ANIM = RawAnimation.begin().thenLoop("idle");
@@ -73,7 +71,7 @@ public abstract class AppleCowEntity extends Cow implements GeoEntity, Shearable
private int sleepTicksRemaining;
private int napCooldownTicks;
private int idleAnimationCycleTicks;
private int grassEatTicks;
private int regrowTicks;
protected AppleCowEntity(EntityType<? extends Cow> entityType, Level level) {
super(entityType, level);
@@ -92,7 +90,6 @@ public abstract class AppleCowEntity extends Cow implements GeoEntity, Shearable
protected void registerGoals() {
super.registerGoals();
this.goalSelector.addGoal(0, new AppleCowSleepGoal());
this.goalSelector.addGoal(1, new AppleCowGrazeGoal());
}
@Override
@@ -148,8 +145,12 @@ public abstract class AppleCowEntity extends Cow implements GeoEntity, Shearable
return;
}
if (!this.isSheared()) {
this.grassEatTicks = 0;
if (this.isSheared()) {
this.regrowTicks++;
if (this.regrowTicks >= APPLE_REGROW_TICKS) {
this.setSheared(false);
this.regrowTicks = 0;
}
}
if (this.shouldStartNap()) {
@@ -190,7 +191,7 @@ public abstract class AppleCowEntity extends Cow implements GeoEntity, Shearable
tag.putInt(NAP_COOLDOWN_KEY, this.napCooldownTicks);
tag.putInt(QUIRK_TICKS_KEY, this.getQuirkTicks());
tag.putBoolean(SHEARED_KEY, this.isSheared());
tag.putInt(GRASS_EAT_TICKS_KEY, this.grassEatTicks);
tag.putInt(REGROW_TICKS_KEY, this.regrowTicks);
}
@Override
@@ -201,7 +202,7 @@ public abstract class AppleCowEntity extends Cow implements GeoEntity, Shearable
this.napCooldownTicks = tag.contains(NAP_COOLDOWN_KEY) ? tag.getInt(NAP_COOLDOWN_KEY) : randomNapCooldown();
this.setQuirkTicks(tag.getInt(QUIRK_TICKS_KEY));
this.setSheared(tag.getBoolean(SHEARED_KEY));
this.grassEatTicks = tag.getInt(GRASS_EAT_TICKS_KEY);
this.regrowTicks = tag.getInt(REGROW_TICKS_KEY);
}
public boolean isSleeping() {
@@ -346,12 +347,24 @@ public abstract class AppleCowEntity extends Cow implements GeoEntity, Shearable
}
this.setSheared(true);
this.grassEatTicks = 0;
this.regrowTicks = 0;
this.stopCowSleeping();
this.playSound(SoundEvents.SHEEP_SHEAR, 1.0F, 1.0F);
this.spawnAtLocation(new ItemStack(this.shearDropItem(), 2), this.getBbHeight() * 0.66F);
}
@Override
protected void dropCustomDeathLoot(ServerLevel level, net.minecraft.world.damagesource.DamageSource damageSource, boolean recentlyHit) {
super.dropCustomDeathLoot(level, damageSource, recentlyHit);
if (this.isBaby() || this.isSheared()) {
return;
}
int count = this instanceof AppleCowEntityVariants.EnchantedGoldenAppleCow ? 1 : 1 + this.random.nextInt(2);
this.spawnAtLocation(new ItemStack(this.shearDropItem(), count));
}
private net.minecraft.world.item.Item shearDropItem() {
if (this instanceof AppleCowEntityVariants.EnchantedGoldenAppleCow) {
return Items.ENCHANTED_GOLDEN_APPLE;
@@ -389,57 +402,4 @@ public abstract class AppleCowEntity extends Cow implements GeoEntity, Shearable
AppleCowEntity.this.applySleepStillness();
}
}
private final class AppleCowGrazeGoal extends Goal {
private AppleCowGrazeGoal() {
this.setFlags(EnumSet.of(Flag.MOVE, Flag.LOOK));
}
@Override
public boolean canUse() {
return AppleCowEntity.this.isSheared()
&& AppleCowEntity.this.isOnGrassBlock()
&& AppleCowEntity.this.isEligibleForSleep()
&& AppleCowEntity.this.random.nextInt(20) == 0;
}
@Override
public boolean canContinueToUse() {
return AppleCowEntity.this.isSheared()
&& AppleCowEntity.this.isOnGrassBlock()
&& AppleCowEntity.this.grassEatTicks < GRASS_REGROW_TICKS;
}
@Override
public void start() {
AppleCowEntity.this.getNavigation().stop();
}
@Override
public void tick() {
AppleCowEntity.this.getNavigation().stop();
if (!AppleCowEntity.this.level().isClientSide && AppleCowEntity.this.isSheared() && AppleCowEntity.this.isOnGrassBlock()) {
AppleCowEntity.this.grassEatTicks++;
if (AppleCowEntity.this.grassEatTicks >= GRASS_REGROW_TICKS) {
AppleCowEntity.this.setSheared(false);
AppleCowEntity.this.grassEatTicks = 0;
AppleCowEntity.this.playSound(SoundEvents.GENERIC_EAT, 0.7F, 1.0F + AppleCowEntity.this.random.nextFloat() * 0.2F);
} else if (AppleCowEntity.this.grassEatTicks % 40 == 0) {
AppleCowEntity.this.playSound(SoundEvents.GENERIC_EAT, 0.4F, 1.0F + AppleCowEntity.this.random.nextFloat() * 0.15F);
}
}
}
@Override
public void stop() {
if (!AppleCowEntity.this.isSheared()) {
AppleCowEntity.this.grassEatTicks = 0;
}
}
}
private boolean isOnGrassBlock() {
BlockPos below = this.blockPosition().below();
return this.level().getBlockState(below).is(Blocks.GRASS_BLOCK);
}
}
@@ -1,14 +1,20 @@
package com.craisinlord.antarchy.content.entity;
import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.AntarchySoundEvents;
import java.util.Objects;
import net.minecraft.core.BlockPos;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.tags.DamageTypeTags;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.util.RandomSource;
import net.minecraft.world.Difficulty;
import net.minecraft.world.DifficultyInstance;
@@ -48,17 +54,21 @@ 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.animation.keyframe.event.builtin.AutoPlayingSoundKeyframeHandler;
import software.bernie.geckolib.util.GeckoLibUtil;
public class BomberEntity extends Monster implements GeoEntity {
private static final ResourceLocation INTENTIONALLY_EMPTY_SOUND_ID = ResourceLocation.withDefaultNamespace("intentionally_empty");
private static final EntityDataAccessor<Integer> FUSE_TICKS_DATA =
SynchedEntityData.defineId(BomberEntity.class, EntityDataSerializers.INT);
private static final EntityDataAccessor<Boolean> DETONATING_DATA =
SynchedEntityData.defineId(BomberEntity.class, EntityDataSerializers.BOOLEAN);
private static final int DEFAULT_FUSE_TICKS = 80;
private static final int DEFAULT_FUSE_TICKS = 120;
private static final int EXPLODE_ANIM_START_TICKS = 25;
private static final int FLASH_INTERVAL_TICKS = 5;
private static final double KNOCKBACK_MULTIPLIER = 5.5D;
private static final float EXPLOSION_RADIUS_MULTIPLIER = 0.45F;
private static final RawAnimation IDLE_ANIM = RawAnimation.begin().thenLoop("idle");
private static final RawAnimation WALK_ANIM = RawAnimation.begin().thenLoop("walk");
@@ -117,25 +127,25 @@ public class BomberEntity extends Monster implements GeoEntity {
Objects.requireNonNull(this.getAttribute(Attributes.MAX_HEALTH)).setBaseValue(maxHealth);
this.setHealth((float) maxHealth);
Objects.requireNonNull(this.getAttribute(Attributes.ATTACK_DAMAGE)).setBaseValue(attackDamage);
return super.finalizeSpawn(level, difficulty, spawnReason, spawnGroupData);
SpawnGroupData spawnData = super.finalizeSpawn(level, difficulty, spawnReason, spawnGroupData);
this.primeFuse();
return spawnData;
}
@Override
public boolean doHurtTarget(Entity target) {
if (!(target instanceof LivingEntity livingTarget)) {
if (!(target instanceof LivingEntity)) {
return super.doHurtTarget(target);
}
livingTarget.hurt(this.damageSources().mobAttack(this), (float) this.getAttributeValue(Attributes.ATTACK_DAMAGE));
this.primeFuse();
return true;
}
@Override
public boolean hurt(DamageSource source, float amount) {
if (source.is(DamageTypeTags.IS_FIRE)) {
if (source.is(DamageTypeTags.IS_FIRE) || source.is(DamageTypeTags.IS_EXPLOSION)) {
this.primeFuse();
return false;
}
Entity directEntity = source.getDirectEntity();
@@ -143,7 +153,19 @@ public class BomberEntity extends Monster implements GeoEntity {
this.primeFuse();
}
return super.hurt(source, amount);
this.playSound(AntarchySoundEvents.BOMBER_KNOCK.get(), 1.0F, 0.9F + this.random.nextFloat() * 0.2F);
this.pushAwayFrom(source);
return false;
}
@Override
public void knockback(double strength, double x, double z) {
super.knockback(strength * KNOCKBACK_MULTIPLIER, x, z);
}
@Override
protected SoundEvent getHurtSound(DamageSource damageSource) {
return AntarchySoundEvents.BOMBER_KNOCK.get();
}
@Override
@@ -176,7 +198,10 @@ public class BomberEntity extends Monster implements GeoEntity {
@Override
public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
controllers.add(new AnimationController<>(this, "main_controller", 0, this::mainAnimController));
controllers.add(new AnimationController<>(this, "movement_controller", 0, this::movementAnimController)
.setSoundKeyframeHandler(new AutoPlayingSoundKeyframeHandler<>()));
controllers.add(new AnimationController<>(this, "explode_controller", 0, this::explodeAnimController)
.setSoundKeyframeHandler(new AutoPlayingSoundKeyframeHandler<>()));
}
@Override
@@ -188,11 +213,12 @@ public class BomberEntity extends Monster implements GeoEntity {
public void tick() {
super.tick();
if (!this.level().isClientSide && this.detonating) {
if (this.detonating && this.fuseTicks <= EXPLODE_ANIM_START_TICKS) {
this.getNavigation().stop();
this.setDeltaMovement(0.0D, 0.0D, 0.0D);
this.hasImpulse = false;
this.setDeltaMovement(Vec3.ZERO);
}
if (!this.level().isClientSide && this.detonating) {
if (this.fuseTicks > 0) {
this.fuseTicks--;
this.entityData.set(FUSE_TICKS_DATA, this.fuseTicks);
@@ -218,15 +244,23 @@ public class BomberEntity extends Monster implements GeoEntity {
&& (this.getFuseTicks() / FLASH_INTERVAL_TICKS) % 2 == 0;
}
private PlayState mainAnimController(AnimationState<BomberEntity> state) {
private PlayState movementAnimController(AnimationState<BomberEntity> state) {
if (this.isDetonating() && this.getFuseTicks() <= EXPLODE_ANIM_START_TICKS) {
return PlayState.STOP;
}
return state.setAndContinue(this.isMovingForAnimation() ? WALK_ANIM : IDLE_ANIM);
}
private PlayState explodeAnimController(AnimationState<BomberEntity> state) {
if (this.isDetonating() && this.getFuseTicks() <= EXPLODE_ANIM_START_TICKS) {
return state.setAndContinue(EXPLODE_ANIM);
}
return state.setAndContinue(state.isMoving() ? WALK_ANIM : IDLE_ANIM);
return PlayState.STOP;
}
private void primeFuse() {
public void primeFuse() {
if (this.detonating) {
return;
}
@@ -235,10 +269,7 @@ public class BomberEntity extends Monster implements GeoEntity {
this.fuseTicks = DEFAULT_FUSE_TICKS;
this.entityData.set(DETONATING_DATA, true);
this.entityData.set(FUSE_TICKS_DATA, this.fuseTicks);
this.getNavigation().stop();
this.setDeltaMovement(Vec3.ZERO);
this.hasImpulse = false;
this.playSound(SoundEvents.TNT_PRIMED, 1.0F, 0.9F + this.random.nextFloat() * 0.2F);
this.playSound(AntarchySoundEvents.BOMBER_EXPLODE.get(), 1.0F, 1.0F);
}
private void detonate() {
@@ -250,8 +281,22 @@ public class BomberEntity extends Monster implements GeoEntity {
double x = this.getX();
double y = this.getY(0.0625D);
double z = this.getZ();
float radius = (float) AntarchySettings.bomberExplosionRadius();
serverLevel.explode(this, x, y, z, radius, ExplosionInteraction.TNT);
float radius = (float) (AntarchySettings.bomberExplosionRadius() * EXPLOSION_RADIUS_MULTIPLIER);
serverLevel.playSound(null, x, y, z, SoundEvents.GENERIC_EXPLODE.value(), SoundSource.HOSTILE, 0.55F, 1.0F);
serverLevel.explode(
this,
null,
null,
x,
y,
z,
radius,
false,
ExplosionInteraction.TNT,
ParticleTypes.EXPLOSION_EMITTER,
ParticleTypes.EXPLOSION,
BuiltInRegistries.SOUND_EVENT.getHolder(INTENTIONALLY_EMPTY_SOUND_ID).orElseThrow()
);
double extraDamage = AntarchySettings.bomberExplosionDamage();
if (extraDamage > 0.0D) {
@@ -274,6 +319,30 @@ public class BomberEntity extends Monster implements GeoEntity {
this.discard();
}
private void pushAwayFrom(DamageSource source) {
Entity sourceEntity = source.getDirectEntity();
if (sourceEntity == null) {
sourceEntity = source.getEntity();
}
if (sourceEntity == null || sourceEntity == this) {
return;
}
double dx = this.getX() - sourceEntity.getX();
double dz = this.getZ() - sourceEntity.getZ();
double distance = Math.sqrt(dx * dx + dz * dz);
if (distance < 1.0E-4D) {
return;
}
double strength = 0.4D * KNOCKBACK_MULTIPLIER;
this.push(dx / distance * strength, 0.08D, dz / distance * strength);
}
private boolean isMovingForAnimation() {
return this.getDeltaMovement().horizontalDistanceSqr() > 1.0E-4D || this.getNavigation().isInProgress();
}
private final class BomberAttackGoal extends MeleeAttackGoal {
private BomberAttackGoal() {
super(BomberEntity.this, 1.1D, true);
@@ -281,12 +350,12 @@ public class BomberEntity extends Monster implements GeoEntity {
@Override
public boolean canUse() {
return !BomberEntity.this.detonating && super.canUse();
return !BomberEntity.this.isDetonating() || BomberEntity.this.getFuseTicks() > EXPLODE_ANIM_START_TICKS && super.canUse();
}
@Override
public boolean canContinueToUse() {
return !BomberEntity.this.detonating && super.canContinueToUse();
return !BomberEntity.this.isDetonating() || BomberEntity.this.getFuseTicks() > EXPLODE_ANIM_START_TICKS && super.canContinueToUse();
}
}
}
@@ -0,0 +1,95 @@
package com.craisinlord.antarchy.content.entity;
import com.craisinlord.antarchy.config.AntarchySettings;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.ai.goal.MeleeAttackGoal;
import net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal;
import net.minecraft.world.entity.animal.AbstractFish;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.Level;
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 CheepEntity extends AbstractFish implements GeoEntity {
private static final RawAnimation IDLE_ANIM = RawAnimation.begin().thenLoop("idle");
private static final RawAnimation SWIM_ANIM = RawAnimation.begin().thenLoop("swim");
private static final RawAnimation ATTACK_ANIM = RawAnimation.begin().thenLoop("attack");
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
public CheepEntity(EntityType<? extends CheepEntity> entityType, Level level) {
super(entityType, level);
}
@Override
protected void registerGoals() {
super.registerGoals();
this.goalSelector.addGoal(1, new MeleeAttackGoal(this, 1.2D, true));
this.targetSelector.addGoal(1, new HurtByTargetGoal(this));
}
public static AttributeSupplier.Builder createAttributes() {
return AbstractFish.createAttributes()
.add(Attributes.MAX_HEALTH, AntarchySettings.cheepHealth())
.add(Attributes.ATTACK_DAMAGE, AntarchySettings.cheepAttackDamage())
.add(Attributes.MOVEMENT_SPEED, 0.5D);
}
@Override
public ItemStack getBucketItemStack() {
return new ItemStack(Items.WATER_BUCKET);
}
@Override
protected SoundEvent getAmbientSound() {
return SoundEvents.COD_AMBIENT;
}
@Override
protected SoundEvent getDeathSound() {
return SoundEvents.COD_DEATH;
}
@Override
protected SoundEvent getHurtSound(DamageSource source) {
return SoundEvents.COD_HURT;
}
@Override
protected SoundEvent getFlopSound() {
return SoundEvents.COD_FLOP;
}
@Override
public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
controllers.add(new AnimationController<>(this, "main_controller", 2, this::mainAnimController));
}
private PlayState mainAnimController(AnimationState<CheepEntity> state) {
if (this.isAggressive()) {
return state.setAndContinue(ATTACK_ANIM);
}
if (state.isMoving()) {
return state.setAndContinue(SWIM_ANIM);
}
return state.setAndContinue(IDLE_ANIM);
}
@Override
public AnimatableInstanceCache getAnimatableInstanceCache() {
return this.geoCache;
}
}
@@ -0,0 +1,198 @@
package com.craisinlord.antarchy.content.entity;
import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.AntarchySoundEvents;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.util.RandomSource;
import net.minecraft.world.Difficulty;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
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.ai.attributes.AttributeSupplier;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.ai.goal.FloatGoal;
import net.minecraft.world.entity.ai.goal.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.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.phys.Vec3;
import net.minecraft.world.level.block.state.BlockState;
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 CreepingHorrorEntity extends Monster implements GeoEntity {
private static final EntityDataAccessor<Boolean> CLIMBING =
SynchedEntityData.defineId(CreepingHorrorEntity.class, EntityDataSerializers.BOOLEAN);
private static final RawAnimation IDLE_ANIM = RawAnimation.begin().thenLoop("idle");
private static final RawAnimation WALK_ANIM = RawAnimation.begin().thenLoop("walk");
private static final RawAnimation ATTACK_ANIM = RawAnimation.begin().thenLoop("attack");
private static final byte ATTACK_ANIM_EVENT = 4;
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
private int attackAnimTicks = 0;
public CreepingHorrorEntity(EntityType<? extends CreepingHorrorEntity> type, Level level) {
super(type, level);
}
@Override
public float maxUpStep() {
return 1.0F;
}
@Override
protected void defineSynchedData(SynchedEntityData.Builder builder) {
super.defineSynchedData(builder);
builder.define(CLIMBING, false);
}
public static AttributeSupplier.Builder createAttributes() {
return Mob.createMobAttributes()
.add(Attributes.MAX_HEALTH, AntarchySettings.creepingHorrorHealth())
.add(Attributes.MOVEMENT_SPEED, 0.38D)
.add(Attributes.ATTACK_DAMAGE, AntarchySettings.creepingHorrorAttackDamage())
.add(Attributes.FOLLOW_RANGE, 16.0D);
}
public static boolean canSpawn(EntityType<CreepingHorrorEntity> entityType, ServerLevelAccessor level, MobSpawnType spawnReason, BlockPos pos, RandomSource random) {
if (spawnReason == MobSpawnType.SPAWN_EGG || spawnReason == MobSpawnType.SPAWNER || spawnReason == MobSpawnType.COMMAND) {
return true;
}
BlockPos belowPos = pos.below();
BlockState belowState = level.getBlockState(belowPos);
boolean validSupport = !belowState.is(Blocks.BEDROCK)
&& belowState.blocksMotion()
&& belowState.isFaceSturdy(level, belowPos, Direction.UP)
&& belowState.isCollisionShapeFullBlock(level, belowPos);
return level.getDifficulty() != Difficulty.PEACEFUL
&& validSupport
&& level.isEmptyBlock(pos)
&& level.isEmptyBlock(pos.above())
&& Monster.checkMonsterSpawnRules(entityType, level, spawnReason, pos, random);
}
@Override
protected void registerGoals() {
this.goalSelector.addGoal(0, new FloatGoal(this));
this.goalSelector.addGoal(1, new MeleeAttackGoal(this, 1.0D, true));
this.goalSelector.addGoal(2, new WaterAvoidingRandomStrollGoal(this, 0.85D));
this.goalSelector.addGoal(3, new LookAtPlayerGoal(this, Player.class, 8.0F));
this.goalSelector.addGoal(4, new RandomLookAroundGoal(this));
this.targetSelector.addGoal(1, new HurtByTargetGoal(this));
this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true));
}
@Override
public boolean onClimbable() {
return this.entityData.get(CLIMBING);
}
public boolean isWallClimbing() {
return this.entityData.get(CLIMBING) && !this.onGround();
}
@Override
public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
controllers.add(new AnimationController<>(this, "main_controller", 3, this::mainAnimController));
}
private PlayState mainAnimController(AnimationState<CreepingHorrorEntity> state) {
if (attackAnimTicks > 0) {
return state.setAndContinue(ATTACK_ANIM);
}
if (state.isMoving()) {
return state.setAndContinue(WALK_ANIM);
}
return state.setAndContinue(IDLE_ANIM);
}
@Override
public AnimatableInstanceCache getAnimatableInstanceCache() {
return geoCache;
}
@Override
public boolean doHurtTarget(net.minecraft.world.entity.Entity target) {
boolean result = super.doHurtTarget(target);
if (result) {
this.playSound(AntarchySoundEvents.CREEPING_HORROR_BITE.get(), 1.0F, 0.95F + this.random.nextFloat() * 0.1F);
attackAnimTicks = 20;
this.level().broadcastEntityEvent(this, ATTACK_ANIM_EVENT);
}
return result;
}
@Override
protected SoundEvent getAmbientSound() {
return AntarchySoundEvents.CREEPING_HORROR_GROWL.get();
}
@Override
protected SoundEvent getHurtSound(net.minecraft.world.damagesource.DamageSource source) {
return AntarchySoundEvents.CREEPING_HORROR_HURT.get();
}
@Override
protected SoundEvent getDeathSound() {
return AntarchySoundEvents.CREEPING_HORROR_HURT.get();
}
@Override
public void handleEntityEvent(byte id) {
if (id == ATTACK_ANIM_EVENT) {
attackAnimTicks = 20;
return;
}
super.handleEntityEvent(id);
}
@Override
public void tick() {
super.tick();
if (attackAnimTicks > 0) attackAnimTicks--;
if (this.level().isClientSide()) return;
boolean climbing = this.horizontalCollision;
this.entityData.set(CLIMBING, climbing);
if (climbing && this.getTarget() != null) {
LivingEntity target = this.getTarget();
Vec3 toTarget = target.position().subtract(this.position());
double hDist = Math.sqrt(toTarget.x * toTarget.x + toTarget.z * toTarget.z);
double speed = this.getAttributeValue(Attributes.MOVEMENT_SPEED) * 2.8D;
if (hDist > 0.01D) {
this.setDeltaMovement(
(toTarget.x / hDist) * speed,
0.22D,
(toTarget.z / hDist) * speed
);
} else {
Vec3 mov = this.getDeltaMovement();
this.setDeltaMovement(mov.x, 0.22D, mov.z);
}
this.getNavigation().stop();
}
}
}
@@ -0,0 +1,479 @@
package com.craisinlord.antarchy.content.entity;
import com.craisinlord.antarchy.config.AntarchySettings;
import java.util.EnumSet;
import java.util.List;
import net.minecraft.core.registries.BuiltInRegistries;
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.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.AgeableMob;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.Entity.MoveFunction;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.ai.control.SmoothSwimmingLookControl;
import net.minecraft.world.entity.ai.control.SmoothSwimmingMoveControl;
import net.minecraft.world.entity.ai.goal.BreathAirGoal;
import net.minecraft.world.entity.ai.goal.FloatGoal;
import net.minecraft.world.entity.ai.goal.Goal;
import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal;
import net.minecraft.world.entity.ai.goal.RandomLookAroundGoal;
import net.minecraft.world.entity.ai.goal.RandomSwimmingGoal;
import net.minecraft.world.entity.ai.goal.TryFindWaterGoal;
import net.minecraft.world.entity.ai.navigation.PathNavigation;
import net.minecraft.world.entity.ai.navigation.WaterBoundPathNavigation;
import net.minecraft.world.entity.animal.Animal;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.Level;
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 DorrieEntity extends Animal implements GeoEntity {
private static final ResourceLocation CHEEP_ITEM_ID = ResourceLocation.fromNamespaceAndPath("antarchy", "cheep");
private static final EntityDataAccessor<Boolean> HAS_SADDLE =
SynchedEntityData.defineId(DorrieEntity.class, EntityDataSerializers.BOOLEAN);
private static final EntityDataAccessor<Boolean> TAMED =
SynchedEntityData.defineId(DorrieEntity.class, EntityDataSerializers.BOOLEAN);
private static final EntityDataAccessor<Integer> JUMP_CHARGE =
SynchedEntityData.defineId(DorrieEntity.class, EntityDataSerializers.INT);
private static final int MAX_CHARGE_TICKS = 40;
private static final float WATER_RIDE_SPEED = 1.26F;
private static final float LAND_BEACH_SPEED = 0.04F;
private static final RawAnimation IDLE_ANIM = RawAnimation.begin().thenLoop("idle");
private static final RawAnimation SWIM_ANIM = RawAnimation.begin().thenLoop("swim");
private static final RawAnimation JUMP_START_ANIM = RawAnimation.begin().thenPlay("jump_1");
private static final RawAnimation JUMP_LOOP_ANIM = RawAnimation.begin().thenLoop("jump_2");
private static final RawAnimation JUMP_LAND_ANIM = RawAnimation.begin().thenPlay("jump_3");
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
private static final int RIDES_TO_TAME = 4;
private static final int BUCK_DELAY_TICKS = 60;
private int chargeTicks = 0;
private boolean isCharging = false;
private boolean isLeaping = false;
private boolean wasInAir = false;
private int landAnimTicks = 0;
private int rideAttempts = 0;
private int buckTimer = 0;
private int swimForwardTicks = 0;
public DorrieEntity(EntityType<? extends DorrieEntity> entityType, Level level) {
super(entityType, level);
this.moveControl = new SmoothSwimmingMoveControl(this, 85, 10, 0.02F, 1.0F, true);
this.lookControl = new SmoothSwimmingLookControl(this, 10);
}
@Override
protected void defineSynchedData(SynchedEntityData.Builder builder) {
super.defineSynchedData(builder);
builder.define(HAS_SADDLE, false);
builder.define(TAMED, false);
builder.define(JUMP_CHARGE, 0);
}
@Override
protected PathNavigation createNavigation(Level level) {
return new WaterBoundPathNavigation(this, level);
}
@Override
protected void registerGoals() {
this.goalSelector.addGoal(0, new FloatGoal(this));
this.goalSelector.addGoal(1, new BreathAirGoal(this));
this.goalSelector.addGoal(2, new TryFindWaterGoal(this));
this.goalSelector.addGoal(3, new HuntCheepGoal(this));
this.goalSelector.addGoal(4, new RandomSwimmingGoal(this, 1.0D, 10));
this.goalSelector.addGoal(5, new LookAtPlayerGoal(this, Player.class, 6.0F));
this.goalSelector.addGoal(6, new RandomLookAroundGoal(this));
}
public static AttributeSupplier.Builder createAttributes() {
return Mob.createMobAttributes()
.add(Attributes.MAX_HEALTH, AntarchySettings.dorrieHealth())
.add(Attributes.MOVEMENT_SPEED, 1.0D)
.add(Attributes.FOLLOW_RANGE, 16.0D)
.add(Attributes.ATTACK_DAMAGE, 4.0D);
}
public boolean hasSaddle() {
return this.entityData.get(HAS_SADDLE);
}
public void setSaddle(boolean saddle) {
this.entityData.set(HAS_SADDLE, saddle);
}
public boolean isTamed() {
return this.entityData.get(TAMED);
}
public void setTamed(boolean tamed) {
this.entityData.set(TAMED, tamed);
}
public int getJumpCharge() {
return this.entityData.get(JUMP_CHARGE);
}
private void setJumpCharge(int charge) {
this.entityData.set(JUMP_CHARGE, charge);
}
@Override
public void addAdditionalSaveData(CompoundTag tag) {
super.addAdditionalSaveData(tag);
tag.putBoolean("Tamed", this.isTamed());
tag.putBoolean("HasSaddle", this.hasSaddle());
tag.putInt("RideAttempts", this.rideAttempts);
}
@Override
public void readAdditionalSaveData(CompoundTag tag) {
super.readAdditionalSaveData(tag);
this.setTamed(tag.getBoolean("Tamed"));
this.setSaddle(tag.getBoolean("HasSaddle"));
this.rideAttempts = tag.getInt("RideAttempts");
}
@Override
public InteractionResult mobInteract(Player player, InteractionHand hand) {
ItemStack stack = player.getItemInHand(hand);
if (!this.isTamed()) {
if (!this.level().isClientSide) {
player.startRiding(this);
}
return InteractionResult.sidedSuccess(this.level().isClientSide);
}
if (this.hasSaddle() && stack.isEmpty() && !player.isPassengerOfSameVehicle(this)) {
if (!this.level().isClientSide) {
this.setSaddle(false);
this.spawnAtLocation(new ItemStack(Items.SADDLE));
this.playSound(SoundEvents.HORSE_SADDLE, 1.0F, 1.0F);
}
return InteractionResult.sidedSuccess(this.level().isClientSide);
}
if (!this.hasSaddle() && stack.is(Items.SADDLE)) {
if (!this.level().isClientSide) {
this.setSaddle(true);
if (!player.getAbilities().instabuild) {
stack.shrink(1);
}
this.playSound(SoundEvents.HORSE_SADDLE, 1.0F, 1.0F);
}
return InteractionResult.sidedSuccess(this.level().isClientSide);
}
if (this.hasSaddle() && !player.isPassengerOfSameVehicle(this)) {
if (!this.level().isClientSide) {
player.startRiding(this);
}
return InteractionResult.sidedSuccess(this.level().isClientSide);
}
return super.mobInteract(player, hand);
}
@Override
@Nullable
public LivingEntity getControllingPassenger() {
if (!this.isTamed() || !this.hasSaddle()) {
return null;
}
Entity passenger = this.getFirstPassenger();
if (passenger instanceof Player player) {
return player;
}
return null;
}
@Override
protected Vec3 getRiddenInput(Player player, Vec3 travelVector) {
float forward = player.zza;
float strafe = player.xxa * 0.5F;
return new Vec3(strafe, 0, forward);
}
@Override
protected float getRiddenSpeed(Player player) {
float speed = (this.isInWater() || this.isInLava())
? WATER_RIDE_SPEED * (1.0F + Math.min(this.swimForwardTicks, 60) * 0.025F)
: LAND_BEACH_SPEED;
return (float) this.getAttributeValue(Attributes.MOVEMENT_SPEED) * speed;
}
@Override
public void tick() {
super.tick();
if (this.getControllingPassenger() instanceof Player player) {
this.setYRot(player.getYRot());
this.yRotO = this.getYRot();
this.yBodyRot = this.getYRot();
this.yHeadRot = this.getYRot();
if (this.isInWater() || this.isInLava()) {
if (player.zza > 0.05F) {
this.swimForwardTicks = Math.min(this.swimForwardTicks + 1, 60);
} else {
this.swimForwardTicks = Math.max(this.swimForwardTicks - 2, 0);
}
} else {
this.swimForwardTicks = 0;
}
} else {
this.swimForwardTicks = 0;
}
if (!this.level().isClientSide && !this.isTamed() && this.getFirstPassenger() instanceof Player rider) {
buckTimer++;
if (buckTimer >= BUCK_DELAY_TICKS) {
rider.stopRiding();
buckTimer = 0;
rideAttempts++;
if (rideAttempts >= RIDES_TO_TAME) {
this.setTamed(true);
this.playSound(SoundEvents.HORSE_BREATHE, 0.8F, 1.0F);
} else {
this.playSound(SoundEvents.HORSE_ANGRY, 0.8F, 1.0F);
}
}
} else if (!this.isTamed() && this.getFirstPassenger() == null) {
buckTimer = 0;
}
if (!this.level().isClientSide && this.getControllingPassenger() instanceof Player && isCharging) {
chargeTicks = Math.min(chargeTicks + 1, MAX_CHARGE_TICKS);
setJumpCharge((int) ((chargeTicks / (float) MAX_CHARGE_TICKS) * 100));
}
if (this.isInWater() && !isLeaping) {
Vec3 mov = this.getDeltaMovement();
double push = this.isUnderWater() ? 0.07D : 0.015D;
double cappedY = this.isUnderWater() ? 0.3D : 0.05D;
this.setDeltaMovement(mov.x, Math.min(mov.y + push, cappedY), mov.z);
}
if (landAnimTicks > 0) {
landAnimTicks--;
}
boolean inAirNow = !this.onGround() && !this.isInWater() && !this.isInLava();
if (wasInAir && !inAirNow && isLeaping) {
isLeaping = false;
landAnimTicks = 12;
}
wasInAir = inAirNow;
}
public void setPressingJump(boolean pressing) {}
public void startJumpCharge() {
if (!this.level().isClientSide) {
isCharging = true;
}
}
public void releaseJump() {
if (!this.level().isClientSide && isCharging) {
float power = chargeTicks / (float) MAX_CHARGE_TICKS;
if (power > 0.05F) {
Vec3 look = this.getLookAngle();
Vec3 current = this.getDeltaMovement();
double forwardAdd = power * 0.5D;
double vertical = 0.55D + power * 0.9D;
this.setDeltaMovement(
current.x + look.x * forwardAdd,
vertical,
current.z + look.z * forwardAdd
);
this.hasImpulse = true;
isLeaping = true;
}
isCharging = false;
chargeTicks = 0;
setJumpCharge(0);
}
}
@Override
protected void positionRider(Entity passenger, MoveFunction moveFunction) {
if (this.hasPassenger(passenger)) {
Vec3 forward = this.getLookAngle().multiply(1.0D, 0.0D, 1.0D);
moveFunction.accept(passenger,
this.getX() - forward.x * 0.25D,
this.getY() + 0.5D,
this.getZ() - forward.z * 0.25D);
}
}
public double getPassengersRidingOffset() {
return 0.6D;
}
@Override
protected void dropEquipment() {
super.dropEquipment();
if (this.hasSaddle()) {
this.spawnAtLocation(new ItemStack(Items.SADDLE));
}
}
@Override
@Nullable
public DorrieEntity getBreedOffspring(ServerLevel level, AgeableMob mob) {
return null;
}
@Override
public boolean canBeLeashed() {
return true;
}
@Override
protected SoundEvent getAmbientSound() {
return SoundEvents.DOLPHIN_AMBIENT;
}
@Override
protected SoundEvent getHurtSound(DamageSource source) {
return SoundEvents.DOLPHIN_HURT;
}
@Override
protected SoundEvent getDeathSound() {
return SoundEvents.DOLPHIN_DEATH;
}
@Override
protected SoundEvent getSwimSound() {
return SoundEvents.DOLPHIN_SWIM;
}
@Override
public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
controllers.add(new AnimationController<>(this, "main_controller", 2, this::mainAnimController));
}
private PlayState mainAnimController(AnimationState<DorrieEntity> state) {
if (landAnimTicks > 0) {
return state.setAndContinue(JUMP_LAND_ANIM);
}
if (isLeaping) {
return state.setAndContinue(wasInAir ? JUMP_LOOP_ANIM : JUMP_START_ANIM);
}
return state.setAndContinue(state.isMoving() ? SWIM_ANIM : IDLE_ANIM);
}
@Override
public AnimatableInstanceCache getAnimatableInstanceCache() {
return this.geoCache;
}
@Override
public boolean isFood(ItemStack stack) {
return this.isCheepItem(stack);
}
private boolean isCheepItem(ItemStack stack) {
return !stack.isEmpty() && stack.is(BuiltInRegistries.ITEM.getOptional(CHEEP_ITEM_ID).orElse(Items.AIR));
}
private static class HuntCheepGoal extends Goal {
private static final int COOLDOWN_TICKS = 600;
private static final double HUNT_RANGE = 16.0D;
private static final double ATTACK_RANGE_SQ = 2.5D * 2.5D;
private final DorrieEntity dorrie;
private CheepEntity target;
private int cooldown = 0;
HuntCheepGoal(DorrieEntity dorrie) {
this.dorrie = dorrie;
setFlags(EnumSet.of(Flag.MOVE, Flag.LOOK));
}
@Override
public boolean canUse() {
if (dorrie.level().isClientSide) {
return false;
}
if (cooldown > 0) {
cooldown--;
return false;
}
if (dorrie.getControllingPassenger() != null) {
return false;
}
List<CheepEntity> nearby = dorrie.level().getEntitiesOfClass(
CheepEntity.class, dorrie.getBoundingBox().inflate(HUNT_RANGE));
if (nearby.isEmpty()) {
return false;
}
nearby.sort((a, b) -> Double.compare(a.distanceToSqr(dorrie), b.distanceToSqr(dorrie)));
target = nearby.get(0);
return true;
}
@Override
public boolean canContinueToUse() {
return target != null && target.isAlive()
&& dorrie.getControllingPassenger() == null
&& dorrie.distanceToSqr(target) <= HUNT_RANGE * HUNT_RANGE * 4;
}
@Override
public void start() {
dorrie.getNavigation().moveTo(target, 1.4D);
}
@Override
public void tick() {
if (target == null) {
return;
}
dorrie.getLookControl().setLookAt(target, 30.0F, 30.0F);
dorrie.getNavigation().moveTo(target, 1.4D);
if (dorrie.distanceToSqr(target) <= ATTACK_RANGE_SQ) {
target.hurt(dorrie.damageSources().mobAttack(dorrie),
(float) dorrie.getAttributeValue(Attributes.ATTACK_DAMAGE));
stop();
}
}
@Override
public void stop() {
target = null;
dorrie.getNavigation().stop();
cooldown = COOLDOWN_TICKS;
}
}
}
@@ -3,6 +3,9 @@ package com.craisinlord.antarchy.content.entity;
import com.craisinlord.antarchy.content.entity.trades.DrTrayaurusTradeManager;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.AgeableMob;
@@ -80,6 +83,21 @@ public class DrTrayaurusEntity extends AbstractVillager {
this.overrideXp(1);
}
@Override
protected SoundEvent getAmbientSound() {
return SoundEvents.VILLAGER_AMBIENT;
}
@Override
protected SoundEvent getHurtSound(DamageSource damageSource) {
return SoundEvents.VILLAGER_HURT;
}
@Override
protected SoundEvent getDeathSound() {
return SoundEvents.VILLAGER_DEATH;
}
@Override
public InteractionResult mobInteract(Player player, InteractionHand hand) {
ItemStack stack = player.getItemInHand(hand);
@@ -48,40 +48,50 @@ import software.bernie.geckolib.animation.RawAnimation;
import software.bernie.geckolib.util.GeckoLibUtil;
public class EmperorScorpionEntity extends Monster implements GeoEntity {
private static final byte CLAW_ATTACK_ANIM_EVENT = 4;
private static final byte SNAP_ATTACK_ANIM_EVENT = 4;
private static final byte STING_ATTACK_ANIM_EVENT = 5;
private static final byte AGRO_ANIM_EVENT = 6;
private static final int CLAW_ATTACK_ANIM_TICKS = 12;
private static final int CLAW_ATTACK_HIT_TICK = 6;
private static final int CLAW_ATTACK_COOLDOWN_TICKS = 16;
private static final int STING_ATTACK_ANIM_TICKS = 18;
private static final int STING_ATTACK_HIT_TICK = 9;
private static final int STING_ATTACK_COOLDOWN_TICKS = 42;
private static final int POISON_DURATION_TICKS = 100;
private static final int WEAKNESS_DURATION_TICKS = 80;
private static final int SUMMON_INTERVAL_TICKS = 180;
private static final int MAX_SUMMONED_SCORPIONS = 4;
private static final int SNAP_ATTACK_ANIM_TICKS = 23;
private static final int SNAP_ATTACK_HIT_TICK = 12;
private static final int SNAP_ATTACK_COOLDOWN_TICKS = 28;
private static final int STING_ATTACK_ANIM_TICKS = 25;
private static final int STING_ATTACK_HIT_TICK = 13;
private static final int STING_ATTACK_COOLDOWN_TICKS = 60;
private static final int AGRO_ANIM_TICKS = 30;
private static final int DEATH_ANIM_TICKS = 40;
private static final float STING_DAMAGE = 18.0F;
private static final double STING_AOE_RADIUS = 5.0D;
private static final int POISON_DURATION_TICKS = 300;
private static final int WEAKNESS_DURATION_TICKS = 120;
private static final int SUMMON_INTERVAL_TICKS = 140;
private static final int MAX_SUMMONED_SCORPIONS = 8;
private static final String EMPEROR_SUMMON_TAG = "antarchy_emperor_summoned";
private static final ResourceKey<Level> THORAXIS_KEY =
ResourceKey.create(Registries.DIMENSION,
ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "thoraxis"));
private static final RawAnimation IDLE_ANIM = RawAnimation.begin().thenLoop("idle");
private static final RawAnimation WALK_ANIM = RawAnimation.begin().thenLoop("walk");
private static final RawAnimation ATTACK_ANIM = RawAnimation.begin().thenPlay("attack");
private static final RawAnimation IDLE_ANIM = RawAnimation.begin().thenLoop("idle");
private static final RawAnimation WALK_ANIM = RawAnimation.begin().thenLoop("walk");
private static final RawAnimation SNAP_ANIM = RawAnimation.begin().thenPlay("snap");
private static final RawAnimation STING_ANIM = RawAnimation.begin().thenPlay("sting");
private static final RawAnimation AGRO_ANIM = RawAnimation.begin().thenPlay("agro");
private static final RawAnimation DEATH_ANIM = RawAnimation.begin().thenPlay("death");
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
private final ServerBossEvent bossEvent =
new ServerBossEvent(this.getDisplayName(), BossEvent.BossBarColor.RED, BossEvent.BossBarOverlay.PROGRESS);
new ServerBossEvent(this.getDisplayName(), BossEvent.BossBarColor.PURPLE, BossEvent.BossBarOverlay.PROGRESS);
private int attackAnimTicks = 0;
private int attackHitTick = 0;
private int attackCooldownTicks = 0;
private int stingCooldownTicks = 0;
private int agroAnimTicks = 0;
private int summonCooldown = 0;
private boolean attackDamageApplied = false;
private boolean stingAttackActive = false;
private boolean hasAgroed = false;
@Nullable private LivingEntity attackTarget;
public EmperorScorpionEntity(EntityType<? extends EmperorScorpionEntity> entityType, Level level) {
@@ -92,12 +102,13 @@ public class EmperorScorpionEntity extends Monster implements GeoEntity {
public static AttributeSupplier.Builder createAttributes() {
return Monster.createMonsterAttributes()
.add(Attributes.MAX_HEALTH, 200.0D)
.add(Attributes.MAX_HEALTH, 400.0D)
.add(Attributes.MOVEMENT_SPEED, 0.32D)
.add(Attributes.ATTACK_DAMAGE, 18.0D)
.add(Attributes.ARMOR, 6.0D)
.add(Attributes.KNOCKBACK_RESISTANCE, 0.6D)
.add(Attributes.FOLLOW_RANGE, 32.0D);
.add(Attributes.ATTACK_DAMAGE, 28.0D)
.add(Attributes.ARMOR, 25.0D)
.add(Attributes.ARMOR_TOUGHNESS, 10.0D)
.add(Attributes.KNOCKBACK_RESISTANCE, 0.8D)
.add(Attributes.FOLLOW_RANGE, 40.0D);
}
public static boolean canSpawn(EntityType<EmperorScorpionEntity> entityType, ServerLevelAccessor level,
@@ -154,10 +165,27 @@ public class EmperorScorpionEntity extends Monster implements GeoEntity {
this.resetAttackState();
}
}
if (this.agroAnimTicks > 0) {
this.agroAnimTicks--;
}
if (this.summonCooldown > 0) {
this.summonCooldown--;
}
if (!this.level().isClientSide) {
if (this.getTarget() != null) {
if (!this.hasAgroed) {
this.hasAgroed = true;
this.agroAnimTicks = AGRO_ANIM_TICKS;
this.getNavigation().stop();
this.level().broadcastEntityEvent(this, AGRO_ANIM_EVENT);
this.playSound(AntarchySoundEvents.EMPEROR_SCORPION_ROAR.get(), 2.0F, 0.7F);
}
} else {
this.hasAgroed = false;
}
}
if (this.getTarget() != null
&& this.summonCooldown <= 0
&& !this.isAttackLocked()
@@ -175,7 +203,7 @@ public class EmperorScorpionEntity extends Monster implements GeoEntity {
return;
}
int toSpawn = 1 + this.random.nextInt(2);
int toSpawn = 2 + this.random.nextInt(2);
EntityType<ScorpionEntity> scorpionType = AntarchyObjects.SCORPION.get();
for (int i = 0; i < toSpawn; i++) {
double offsetX = (this.random.nextDouble() - 0.5D) * 6.0D;
@@ -198,10 +226,10 @@ public class EmperorScorpionEntity extends Monster implements GeoEntity {
@Override
public boolean doHurtTarget(Entity target) {
return this.performStingAttack(target);
return this.performSnapAttack(target);
}
private boolean performClawAttack(Entity target) {
private boolean performSnapAttack(Entity target) {
boolean hurt = super.doHurtTarget(target);
if (hurt) {
this.playSound(AntarchySoundEvents.EMPEROR_SCORPION_ATTACK.get(), 0.9F, 0.95F + this.random.nextFloat() * 0.08F);
@@ -210,32 +238,39 @@ public class EmperorScorpionEntity extends Monster implements GeoEntity {
}
private boolean performStingAttack(Entity target) {
boolean hurt;
if (this.level() instanceof ServerLevel serverLevel) {
hurt = target.hurt(AntarchyDamageSources.emperorScorpionSting(serverLevel, this),
(float) this.getAttributeValue(Attributes.ATTACK_DAMAGE));
} else {
hurt = super.doHurtTarget(target);
if (!(this.level() instanceof ServerLevel serverLevel)) {
return false;
}
if (hurt && target instanceof LivingEntity living) {
int poisonDuration = this.level().getDifficulty() == Difficulty.HARD
? POISON_DURATION_TICKS * 2 : POISON_DURATION_TICKS;
living.addEffect(new MobEffectInstance(MobEffects.POISON, poisonDuration, 0));
living.addEffect(new MobEffectInstance(MobEffects.WEAKNESS, WEAKNESS_DURATION_TICKS, 0));
boolean hurtAny = false;
for (LivingEntity living : serverLevel.getEntitiesOfClass(LivingEntity.class,
this.getBoundingBox().inflate(STING_AOE_RADIUS),
entity -> entity != this
&& entity.isAlive()
&& !(entity instanceof EmperorScorpionEntity)
&& !(entity instanceof ScorpionEntity))) {
if (living.hurt(AntarchyDamageSources.emperorScorpionSting(serverLevel, this), STING_DAMAGE)) {
hurtAny = true;
int poisonDuration = this.level().getDifficulty() == Difficulty.HARD
? POISON_DURATION_TICKS * 2 : POISON_DURATION_TICKS;
living.addEffect(new MobEffectInstance(MobEffects.POISON, poisonDuration, 1));
living.addEffect(new MobEffectInstance(MobEffects.WEAKNESS, WEAKNESS_DURATION_TICKS, 0));
}
}
if (hurtAny) {
this.playSound(AntarchySoundEvents.EMPEROR_SCORPION_ATTACK.get(), 1.0F, 0.9F + this.random.nextFloat() * 0.08F);
}
return hurt;
return hurtAny;
}
private void beginClawAttack(LivingEntity target) {
private void beginSnapAttack(LivingEntity target) {
this.attackTarget = target;
this.attackAnimTicks = CLAW_ATTACK_ANIM_TICKS;
this.attackHitTick = CLAW_ATTACK_HIT_TICK;
this.attackCooldownTicks = CLAW_ATTACK_COOLDOWN_TICKS;
this.attackAnimTicks = SNAP_ATTACK_ANIM_TICKS;
this.attackHitTick = SNAP_ATTACK_HIT_TICK;
this.attackCooldownTicks = SNAP_ATTACK_COOLDOWN_TICKS;
this.attackDamageApplied = false;
this.stingAttackActive = false;
this.getNavigation().stop();
this.level().broadcastEntityEvent(this, CLAW_ATTACK_ANIM_EVENT);
this.level().broadcastEntityEvent(this, SNAP_ATTACK_ANIM_EVENT);
this.commitLunge(target, 0.42D, 0.10D);
}
@@ -259,14 +294,13 @@ public class EmperorScorpionEntity extends Monster implements GeoEntity {
}
this.getLookControl().setLookAt(this.attackTarget, 30.0F, 30.0F);
if (!this.attackDamageApplied
&& this.attackAnimTicks == this.attackHitTick
&& this.distanceToSqr(this.attackTarget) <= this.getAttackReachSqr(this.attackTarget)) {
this.attackDamageApplied = true;
if (!this.attackDamageApplied && this.attackAnimTicks == this.attackHitTick) {
if (this.stingAttackActive) {
this.attackDamageApplied = true;
this.performStingAttack(this.attackTarget);
} else {
this.performClawAttack(this.attackTarget);
} else if (this.distanceToSqr(this.attackTarget) <= this.getAttackReachSqr(this.attackTarget)) {
this.attackDamageApplied = true;
this.performSnapAttack(this.attackTarget);
}
}
@@ -306,13 +340,13 @@ public class EmperorScorpionEntity extends Monster implements GeoEntity {
}
private boolean isAttackLocked() {
return this.attackAnimTicks > 0;
return this.attackAnimTicks > 0 || this.agroAnimTicks > 0;
}
@Override
public void handleEntityEvent(byte id) {
if (id == CLAW_ATTACK_ANIM_EVENT) {
this.attackAnimTicks = CLAW_ATTACK_ANIM_TICKS;
if (id == SNAP_ATTACK_ANIM_EVENT) {
this.attackAnimTicks = SNAP_ATTACK_ANIM_TICKS;
this.attackDamageApplied = false;
this.stingAttackActive = false;
return;
@@ -323,6 +357,10 @@ public class EmperorScorpionEntity extends Monster implements GeoEntity {
this.stingAttackActive = true;
return;
}
if (id == AGRO_ANIM_EVENT) {
this.agroAnimTicks = AGRO_ANIM_TICKS;
return;
}
super.handleEntityEvent(id);
}
@@ -332,8 +370,14 @@ public class EmperorScorpionEntity extends Monster implements GeoEntity {
}
private PlayState mainController(AnimationState<EmperorScorpionEntity> state) {
if (this.isDeadOrDying()) {
return state.setAndContinue(DEATH_ANIM);
}
if (this.attackAnimTicks > 0) {
return state.setAndContinue(ATTACK_ANIM);
return state.setAndContinue(this.stingAttackActive ? STING_ANIM : SNAP_ANIM);
}
if (this.agroAnimTicks > 0) {
return state.setAndContinue(AGRO_ANIM);
}
if (state.isMoving()) {
return state.setAndContinue(WALK_ANIM);
@@ -341,6 +385,15 @@ public class EmperorScorpionEntity extends Monster implements GeoEntity {
return state.setAndContinue(IDLE_ANIM);
}
@Override
protected void tickDeath() {
this.deathTime++;
if (this.deathTime >= DEATH_ANIM_TICKS && !this.level().isClientSide && !this.isRemoved()) {
this.level().broadcastEntityEvent(this, (byte) 60);
this.remove(RemovalReason.KILLED);
}
}
@Override
public AnimatableInstanceCache getAnimatableInstanceCache() {
return this.geoCache;
@@ -421,7 +474,7 @@ public class EmperorScorpionEntity extends Monster implements GeoEntity {
if (EmperorScorpionEntity.this.shouldUseSting(enemy)) {
EmperorScorpionEntity.this.beginStingAttack(enemy);
} else {
EmperorScorpionEntity.this.beginClawAttack(enemy);
EmperorScorpionEntity.this.beginSnapAttack(enemy);
}
return;
}
@@ -0,0 +1,737 @@
package com.craisinlord.antarchy.content.entity;
import com.craisinlord.antarchy.config.AntarchySettings;
import com.mojang.serialization.Codec;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.util.ByIdMap;
import net.minecraft.util.Mth;
import net.minecraft.util.RandomSource;
import net.minecraft.util.StringRepresentable;
import net.minecraft.world.Difficulty;
import net.minecraft.world.DifficultyInstance;
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.MobSpawnType;
import net.minecraft.world.entity.MoverType;
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.goal.FloatGoal;
import net.minecraft.world.entity.ai.goal.Goal;
import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal;
import net.minecraft.world.entity.ai.goal.RandomLookAroundGoal;
import net.minecraft.world.entity.ai.goal.RandomStrollGoal;
import net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal;
import net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal;
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.state.BlockState;
import net.minecraft.world.level.levelgen.Heightmap;
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;
import java.util.EnumSet;
import java.util.function.IntFunction;
public class JumpyBugEntity extends Monster implements GeoEntity {
private static final EntityDataAccessor<Boolean> CAMOUFLAGED =
SynchedEntityData.defineId(JumpyBugEntity.class, EntityDataSerializers.BOOLEAN);
private static final EntityDataAccessor<Boolean> CLINGING_TO_CEILING =
SynchedEntityData.defineId(JumpyBugEntity.class, EntityDataSerializers.BOOLEAN);
private static final EntityDataAccessor<Integer> ATTACK_STATE =
SynchedEntityData.defineId(JumpyBugEntity.class, EntityDataSerializers.INT);
private static final String POUNCE_COOLDOWN_KEY = "PounceCooldown";
private static final String RECOVERY_TICKS_KEY = "RecoveryTicks";
private static final String POUNCE_TICKS_KEY = "PounceTicks";
private static final String LEAPING_TICKS_KEY = "LeapingTicks";
private static final double DEFAULT_MOVEMENT_SPEED = 0.25D;
private static final double DEFAULT_FOLLOW_RANGE = 16.0D;
private static final double MELEE_WALK_RANGE = 4.0D;
private static final double CHASE_LEAP_RANGE_SQR = 8.0D * 8.0D;
private static final int DEFAULT_POUNCE_COOLDOWN = 60;
private static final int DEFAULT_RECOVERY_TICKS = 20;
private static final int CHASE_JUMP_COOLDOWN = 25;
private static final double POUNCE_HORIZONTAL_SPEED = 0.92D;
private static final double POUNCE_UPWARD_SPEED = 0.75D;
private static final double CHASE_LEAP_HORIZONTAL = 1.5D;
private static final double CHASE_LEAP_VERTICAL = 0.55D;
private static final double QUICK_MOVE_SQR = 0.14D * 0.14D;
private static final double POUNCE_LAND_DAMAGE_RANGE = 5.0D;
private static final int CEILING_SEARCH_RANGE = 30;
private static final double CAMOUFLAGE_PLAYER_RANGE = 24.0D;
private static final RawAnimation IDLE_ANIM = RawAnimation.begin().thenLoop("idle");
private static final RawAnimation WALK_ANIM = RawAnimation.begin().thenLoop("walk");
private static final RawAnimation JUMP_ANIM = RawAnimation.begin().thenLoop("jump");
private static final RawAnimation ATTACK_ANIM = RawAnimation.begin().thenPlay("attack");
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
private int pounceCooldownTicks;
private int recoveryTicks;
private int pounceTicks;
private int leapingTicks;
private int chaseJumpCooldownTicks;
private float visualAlpha = 1.0F;
private float previousVisualAlpha = 1.0F;
public JumpyBugEntity(EntityType<? extends JumpyBugEntity> entityType, Level level) {
super(entityType, level);
this.xpReward = 6;
}
public static AttributeSupplier.Builder createAttributes() {
return Mob.createMobAttributes()
.add(Attributes.MAX_HEALTH, AntarchySettings.jumpyBugHealth())
.add(Attributes.ATTACK_DAMAGE, AntarchySettings.jumpyBugPounceDamage())
.add(Attributes.MOVEMENT_SPEED, DEFAULT_MOVEMENT_SPEED)
.add(Attributes.FOLLOW_RANGE, DEFAULT_FOLLOW_RANGE);
}
public static boolean canSpawn(EntityType<JumpyBugEntity> entityType, ServerLevelAccessor level, MobSpawnType spawnReason, BlockPos pos, RandomSource random) {
if (spawnReason == MobSpawnType.SPAWN_EGG || spawnReason == MobSpawnType.SPAWNER || spawnReason == MobSpawnType.COMMAND) {
return true;
}
return level.getDifficulty() != Difficulty.PEACEFUL
&& Monster.checkMonsterSpawnRules(entityType, level, spawnReason, pos, random);
}
@Override
protected void defineSynchedData(SynchedEntityData.Builder builder) {
super.defineSynchedData(builder);
builder.define(CAMOUFLAGED, false);
builder.define(CLINGING_TO_CEILING, false);
builder.define(ATTACK_STATE, AttackState.GROUND.id());
}
@Override
protected void registerGoals() {
this.goalSelector.addGoal(0, new FloatGoal(this));
this.goalSelector.addGoal(1, new CloseMeleeGoal());
this.goalSelector.addGoal(2, new PounceAttackGoal());
this.goalSelector.addGoal(3, new HideOnCeilingGoal());
this.goalSelector.addGoal(4, new ChaseTargetGoal());
this.goalSelector.addGoal(5, new WanderGoal());
this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 10.0F));
this.goalSelector.addGoal(7, new RandomLookAroundGoal(this));
this.targetSelector.addGoal(1, new HurtByTargetGoal(this));
this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true));
}
@Override
public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
controllers.add(new AnimationController<>(this, "main_controller", 2, this::mainAnimController));
}
@Override
public AnimatableInstanceCache getAnimatableInstanceCache() {
return this.geoCache;
}
@Override
public void addAdditionalSaveData(CompoundTag tag) {
super.addAdditionalSaveData(tag);
tag.putInt(POUNCE_COOLDOWN_KEY, this.pounceCooldownTicks);
tag.putInt(RECOVERY_TICKS_KEY, this.recoveryTicks);
tag.putInt(POUNCE_TICKS_KEY, this.pounceTicks);
tag.putInt(LEAPING_TICKS_KEY, this.leapingTicks);
}
@Override
public void readAdditionalSaveData(CompoundTag tag) {
super.readAdditionalSaveData(tag);
this.pounceCooldownTicks = tag.getInt(POUNCE_COOLDOWN_KEY);
this.recoveryTicks = tag.getInt(RECOVERY_TICKS_KEY);
this.pounceTicks = tag.getInt(POUNCE_TICKS_KEY);
this.leapingTicks = tag.getInt(LEAPING_TICKS_KEY);
}
@Override
@Nullable
public SpawnGroupData finalizeSpawn(ServerLevelAccessor level, DifficultyInstance difficulty, MobSpawnType spawnReason, @Nullable SpawnGroupData spawnGroupData) {
this.setHealth((float) AntarchySettings.jumpyBugHealth());
return super.finalizeSpawn(level, difficulty, spawnReason, spawnGroupData);
}
@Override
public void tick() {
this.previousVisualAlpha = this.visualAlpha;
float targetAlpha = this.getTargetVisualAlpha();
if (this.visualAlpha != targetAlpha) {
this.visualAlpha += (targetAlpha - this.visualAlpha) * 0.25F;
}
super.tick();
if (this.level().isClientSide) {
return;
}
if (this.pounceCooldownTicks > 0) {
this.pounceCooldownTicks--;
}
if (this.recoveryTicks > 0) {
this.recoveryTicks--;
}
if (this.chaseJumpCooldownTicks > 0) {
this.chaseJumpCooldownTicks--;
}
if (this.isClingingToCeiling()) {
this.tickClinging();
} else if (this.isPouncing()) {
this.tickPouncing();
} else if (this.isLeapingToCeiling()) {
this.tickLeapingToCeiling();
} else {
this.setNoGravity(false);
this.noPhysics = false;
if (this.getAttackState() != AttackState.RECOVERING || this.recoveryTicks <= 0) {
this.setAttackState(AttackState.GROUND);
}
}
this.updateCamouflage();
}
@Override
public void travel(Vec3 travelVector) {
if (this.isClingingToCeiling()) {
this.setDeltaMovement(Vec3.ZERO);
return;
}
super.travel(travelVector);
}
@Override
public boolean hurt(DamageSource source, float amount) {
boolean hurt = super.hurt(source, amount);
if (!hurt) {
return false;
}
this.setCamouflaged(false);
this.recoveryTicks = Math.max(this.recoveryTicks, DEFAULT_RECOVERY_TICKS);
if (this.isClingingToCeiling()) {
this.stopClinging();
}
if (this.isPouncing()) {
this.finishPounce(false);
}
if (this.isLeapingToCeiling()) {
this.cancelLeap();
}
return true;
}
@Override
public boolean causeFallDamage(float fallDistance, float multiplier, DamageSource source) {
return false;
}
@Override
protected SoundEvent getAmbientSound() {
return SoundEvents.SPIDER_AMBIENT;
}
@Override
protected SoundEvent getHurtSound(DamageSource damageSource) {
return SoundEvents.SPIDER_HURT;
}
@Override
protected SoundEvent getDeathSound() {
return SoundEvents.SPIDER_DEATH;
}
@Override
protected void playStepSound(BlockPos pos, BlockState blockState) {
this.playSound(SoundEvents.SPIDER_STEP, 0.12F, 1.1F);
}
public boolean isCamouflaged() {
return this.entityData.get(CAMOUFLAGED);
}
public boolean isClingingToCeiling() {
return this.entityData.get(CLINGING_TO_CEILING);
}
public boolean isPouncing() {
return this.getAttackState() == AttackState.POUNCING;
}
public boolean isLeapingToCeiling() {
return this.getAttackState() == AttackState.LEAPING_TO_CEILING;
}
public float getVisualAlpha(float partialTick) {
return Mth.lerp(partialTick, this.previousVisualAlpha, this.visualAlpha);
}
public AttackState getAttackState() {
return AttackState.BY_ID.apply(this.entityData.get(ATTACK_STATE));
}
private void setCamouflaged(boolean value) {
this.entityData.set(CAMOUFLAGED, value);
}
private void setClingingToCeiling(boolean value) {
this.entityData.set(CLINGING_TO_CEILING, value);
}
private void setAttackState(AttackState state) {
this.entityData.set(ATTACK_STATE, state.id());
}
private float getTargetVisualAlpha() {
return this.isCamouflaged() ? (float) AntarchySettings.jumpyBugCamouflageAlpha() : 1.0F;
}
private PlayState mainAnimController(AnimationState<JumpyBugEntity> state) {
AttackState attackState = this.getAttackState();
if (attackState == AttackState.POUNCING || attackState == AttackState.CLINGING || attackState == AttackState.LEAPING_TO_CEILING) {
return state.setAndContinue(JUMP_ANIM);
}
if (attackState == AttackState.RECOVERING) {
return state.setAndContinue(ATTACK_ANIM);
}
return state.setAndContinue(state.isMoving() ? WALK_ANIM : IDLE_ANIM);
}
private void tickClinging() {
BlockPos clingPos = this.findCeilingWithinRange(2);
if (clingPos == null) {
this.stopClinging();
return;
}
this.setAttackState(AttackState.CLINGING);
this.setCamouflaged(true);
this.setNoGravity(true);
this.noPhysics = true;
this.getNavigation().stop();
this.setDeltaMovement(Vec3.ZERO);
this.move(MoverType.SELF, Vec3.ZERO);
this.setPos(this.getX(), clingPos.getY() - this.getBbHeight() - 0.01D, this.getZ());
}
private void tickPouncing() {
this.setAttackState(AttackState.POUNCING);
this.setCamouflaged(false);
this.pounceTicks++;
if (this.onGround() && this.pounceTicks > 8) {
LivingEntity target = this.getTarget();
if (target != null && target.isAlive() && this.distanceToSqr(target) <= POUNCE_LAND_DAMAGE_RANGE * POUNCE_LAND_DAMAGE_RANGE) {
target.hurt(this.damageSources().mobAttack(this), (float) AntarchySettings.jumpyBugPounceDamage());
}
this.finishPounce(false);
}
}
private void tickLeapingToCeiling() {
this.leapingTicks++;
if (this.leapingTicks > 80) {
this.cancelLeap();
return;
}
this.getNavigation().stop();
BlockPos ceiling = this.findCeilingWithinRange(2);
if (ceiling != null) {
this.leapingTicks = 0;
this.beginClinging();
}
}
private void updateCamouflage() {
if (this.isPouncing() || this.isLeapingToCeiling()) {
this.setCamouflaged(false);
return;
}
if (this.isClingingToCeiling()) {
this.setCamouflaged(true);
return;
}
boolean playerNearby = this.level().getNearestPlayer(this, CAMOUFLAGE_PLAYER_RANGE) != null;
if (!playerNearby) {
this.setCamouflaged(false);
return;
}
boolean idle = this.getTarget() == null && this.getDeltaMovement().horizontalDistanceSqr() < 0.003D;
boolean movingQuickly = this.getDeltaMovement().lengthSqr() > QUICK_MOVE_SQR;
this.setCamouflaged(!movingQuickly && idle);
}
@Nullable
private BlockPos findCeilingWithinRange(int maxBlocks) {
BlockPos origin = this.blockPosition();
for (int dy = 1; dy <= maxBlocks; dy++) {
BlockPos candidate = origin.above(dy);
if (isCeilingBlock(candidate)) {
return candidate;
}
}
return null;
}
@Nullable
private BlockPos findCeilingForLeap() {
return findCeilingWithinRange(CEILING_SEARCH_RANGE);
}
private boolean isCeilingBlock(BlockPos pos) {
return this.level().getBlockState(pos).isFaceSturdy(this.level(), pos, net.minecraft.core.Direction.DOWN);
}
private void beginClinging() {
this.setClingingToCeiling(true);
this.setAttackState(AttackState.CLINGING);
this.setNoGravity(true);
this.noPhysics = true;
this.getNavigation().stop();
this.setDeltaMovement(Vec3.ZERO);
}
private void stopClinging() {
this.setClingingToCeiling(false);
this.setAttackState(AttackState.GROUND);
this.setNoGravity(false);
this.noPhysics = false;
}
private void beginLeapToCeiling(BlockPos ceilingPos) {
this.setAttackState(AttackState.LEAPING_TO_CEILING);
this.setCamouflaged(false);
this.leapingTicks = 0;
this.getNavigation().stop();
double dy = ceilingPos.getY() - this.getY();
double upwardSpeed = Math.min(2.5D, Math.sqrt(0.16D * Math.max(dy, 1.0D)) + 0.4D);
this.setDeltaMovement(0.0D, upwardSpeed, 0.0D);
this.hasImpulse = true;
}
private void cancelLeap() {
this.leapingTicks = 0;
this.setAttackState(AttackState.GROUND);
}
private void beginPounce(LivingEntity target) {
this.stopClinging();
this.setTarget(target);
this.setAttackState(AttackState.POUNCING);
this.setCamouflaged(false);
this.getNavigation().stop();
this.pounceCooldownTicks = DEFAULT_POUNCE_COOLDOWN;
this.pounceTicks = 0;
Vec3 from = this.position();
Vec3 to = target.getEyePosition().subtract(from);
Vec3 horizontal = new Vec3(to.x, 0.0D, to.z);
Vec3 direction = horizontal.lengthSqr() > 1.0E-4D ? horizontal.normalize() : target.getLookAngle();
double upward = POUNCE_UPWARD_SPEED + Mth.clamp(to.y * 0.08D, -0.04D, 0.22D);
this.setDeltaMovement(direction.x * POUNCE_HORIZONTAL_SPEED, upward, direction.z * POUNCE_HORIZONTAL_SPEED);
this.hasImpulse = true;
}
private void finishPounce(boolean keepMomentum) {
this.pounceTicks = 0;
if (!keepMomentum) {
Vec3 movement = this.getDeltaMovement();
this.setDeltaMovement(movement.x * 0.25D, Math.max(0.0D, movement.y), movement.z * 0.25D);
}
this.setAttackState(AttackState.RECOVERING);
this.recoveryTicks = DEFAULT_RECOVERY_TICKS;
}
private boolean canUseAmbush() {
return !this.isPouncing()
&& !this.isLeapingToCeiling()
&& this.recoveryTicks <= 0;
}
public enum AttackState implements StringRepresentable {
GROUND(0, "ground"),
CLINGING(1, "clinging"),
POUNCING(2, "pouncing"),
RECOVERING(3, "recovering"),
LEAPING_TO_CEILING(4, "leaping_to_ceiling");
private static final IntFunction<AttackState> BY_ID = ByIdMap.continuous(AttackState::id, values(), ByIdMap.OutOfBoundsStrategy.ZERO);
public static final Codec<AttackState> CODEC = StringRepresentable.fromEnum(AttackState::values);
private final int id;
private final String name;
AttackState(int id, String name) {
this.id = id;
this.name = name;
}
public int id() {
return this.id;
}
@Override
public String getSerializedName() {
return this.name;
}
}
private final class CloseMeleeGoal extends Goal {
private static final double MELEE_RANGE_SQR = 5.0D * 5.0D;
private static final double CONTINUE_RANGE_SQR = 7.0D * 7.0D;
private static final int ATTACK_COOLDOWN = 20;
private int attackCooldown;
private CloseMeleeGoal() {
this.setFlags(EnumSet.of(Flag.MOVE, Flag.LOOK));
}
@Override
public boolean canUse() {
LivingEntity target = JumpyBugEntity.this.getTarget();
return target != null
&& target.isAlive()
&& !JumpyBugEntity.this.isPouncing()
&& !JumpyBugEntity.this.isLeapingToCeiling()
&& !JumpyBugEntity.this.isClingingToCeiling()
&& JumpyBugEntity.this.distanceToSqr(target) <= MELEE_RANGE_SQR;
}
@Override
public boolean canContinueToUse() {
LivingEntity target = JumpyBugEntity.this.getTarget();
return target != null
&& target.isAlive()
&& !JumpyBugEntity.this.isPouncing()
&& !JumpyBugEntity.this.isLeapingToCeiling()
&& !JumpyBugEntity.this.isClingingToCeiling()
&& JumpyBugEntity.this.distanceToSqr(target) <= CONTINUE_RANGE_SQR;
}
@Override
public void start() {
this.attackCooldown = 0;
}
@Override
public void tick() {
LivingEntity target = JumpyBugEntity.this.getTarget();
if (target == null) return;
JumpyBugEntity.this.getLookControl().setLookAt(target, 30.0F, 30.0F);
JumpyBugEntity.this.getNavigation().moveTo(target, 1.1D);
if (this.attackCooldown > 0) {
this.attackCooldown--;
}
if (this.attackCooldown <= 0 && JumpyBugEntity.this.distanceToSqr(target) <= MELEE_RANGE_SQR) {
JumpyBugEntity.this.doHurtTarget(target);
this.attackCooldown = ATTACK_COOLDOWN;
}
}
}
private final class PounceAttackGoal extends Goal {
private PounceAttackGoal() {
this.setFlags(EnumSet.of(Flag.MOVE, Flag.LOOK, Flag.JUMP));
}
@Override
public boolean canUse() {
LivingEntity target = JumpyBugEntity.this.getTarget();
return target != null
&& target.isAlive()
&& JumpyBugEntity.this.canUseAmbush()
&& JumpyBugEntity.this.pounceCooldownTicks <= 0
&& (JumpyBugEntity.this.hasLineOfSight(target) || JumpyBugEntity.this.distanceToSqr(target) <= 9.0D);
}
@Override
public boolean canContinueToUse() {
return JumpyBugEntity.this.isPouncing();
}
@Override
public void start() {
LivingEntity target = JumpyBugEntity.this.getTarget();
if (target != null) {
JumpyBugEntity.this.beginPounce(target);
}
}
}
private final class HideOnCeilingGoal extends Goal {
@Nullable
private BlockPos targetCeiling;
private HideOnCeilingGoal() {
this.setFlags(EnumSet.of(Flag.MOVE, Flag.LOOK, Flag.JUMP));
}
@Override
public boolean canUse() {
if (!JumpyBugEntity.this.canUseAmbush() || JumpyBugEntity.this.isClingingToCeiling()) {
return false;
}
LivingEntity target = JumpyBugEntity.this.getTarget();
boolean targetFarAway = target != null
&& JumpyBugEntity.this.distanceToSqr(target) > 12.0D * 12.0D;
boolean noTarget = target == null;
if (!noTarget && !targetFarAway) {
return false;
}
if (JumpyBugEntity.this.random.nextInt(25) != 0) {
return false;
}
this.targetCeiling = JumpyBugEntity.this.findCeilingForLeap();
return this.targetCeiling != null;
}
@Override
public boolean canContinueToUse() {
return JumpyBugEntity.this.isClingingToCeiling() || JumpyBugEntity.this.isLeapingToCeiling();
}
@Override
public void start() {
if (this.targetCeiling != null) {
BlockPos ceil = this.targetCeiling;
// If we're already adjacent to the ceiling, just cling directly
if (ceil.getY() - JumpyBugEntity.this.getY() <= 2) {
JumpyBugEntity.this.beginClinging();
} else {
JumpyBugEntity.this.beginLeapToCeiling(ceil);
}
}
}
@Override
public void stop() {
if (JumpyBugEntity.this.isClingingToCeiling()) {
JumpyBugEntity.this.stopClinging();
} else if (JumpyBugEntity.this.isLeapingToCeiling()) {
JumpyBugEntity.this.cancelLeap();
}
this.targetCeiling = null;
}
@Override
public void tick() {
if (JumpyBugEntity.this.isClingingToCeiling()) {
LivingEntity target = JumpyBugEntity.this.getTarget();
if (target != null) {
JumpyBugEntity.this.getLookControl().setLookAt(target, 30.0F, 30.0F);
if (JumpyBugEntity.this.pounceCooldownTicks <= 0
&& (JumpyBugEntity.this.hasLineOfSight(target) || JumpyBugEntity.this.distanceToSqr(target) <= 4.0D)) {
JumpyBugEntity.this.stopClinging();
JumpyBugEntity.this.beginPounce(target);
}
}
}
}
}
private final class ChaseTargetGoal extends Goal {
private ChaseTargetGoal() {
this.setFlags(EnumSet.of(Flag.MOVE, Flag.LOOK));
}
@Override
public boolean canUse() {
LivingEntity target = JumpyBugEntity.this.getTarget();
return target != null
&& target.isAlive()
&& !JumpyBugEntity.this.isClingingToCeiling()
&& !JumpyBugEntity.this.isPouncing()
&& !JumpyBugEntity.this.isLeapingToCeiling()
&& JumpyBugEntity.this.recoveryTicks <= 0;
}
@Override
public boolean canContinueToUse() {
return this.canUse();
}
@Override
public void tick() {
LivingEntity target = JumpyBugEntity.this.getTarget();
if (target == null) {
return;
}
JumpyBugEntity.this.getLookControl().setLookAt(target, 30.0F, 30.0F);
double distSqr = JumpyBugEntity.this.distanceToSqr(target);
if (distSqr <= MELEE_WALK_RANGE * MELEE_WALK_RANGE) {
JumpyBugEntity.this.getNavigation().moveTo(target, 1.05D);
} else if (distSqr > CHASE_LEAP_RANGE_SQR) {
if (JumpyBugEntity.this.onGround() && JumpyBugEntity.this.chaseJumpCooldownTicks <= 0) {
Vec3 dir = target.position().subtract(JumpyBugEntity.this.position()).normalize();
JumpyBugEntity.this.setDeltaMovement(
dir.x * CHASE_LEAP_HORIZONTAL,
CHASE_LEAP_VERTICAL,
dir.z * CHASE_LEAP_HORIZONTAL
);
JumpyBugEntity.this.hasImpulse = true;
JumpyBugEntity.this.chaseJumpCooldownTicks = CHASE_JUMP_COOLDOWN;
}
JumpyBugEntity.this.getNavigation().stop();
} else {
JumpyBugEntity.this.getNavigation().moveTo(target, 1.05D);
}
}
}
private final class WanderGoal extends RandomStrollGoal {
private WanderGoal() {
super(JumpyBugEntity.this, 0.8D);
}
@Override
public boolean canUse() {
return !JumpyBugEntity.this.isClingingToCeiling()
&& !JumpyBugEntity.this.isPouncing()
&& !JumpyBugEntity.this.isLeapingToCeiling()
&& JumpyBugEntity.this.getTarget() == null
&& super.canUse();
}
@Override
public boolean canContinueToUse() {
return !JumpyBugEntity.this.isClingingToCeiling()
&& !JumpyBugEntity.this.isPouncing()
&& !JumpyBugEntity.this.isLeapingToCeiling()
&& super.canContinueToUse();
}
}
}
@@ -0,0 +1,178 @@
package com.craisinlord.antarchy.content.entity;
import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.AntarchySoundEvents;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.util.RandomSource;
import net.minecraft.world.Difficulty;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.MobSpawnType;
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.ai.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.sounds.SoundEvent;
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 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 LurkingTerrorEntity extends Monster implements GeoEntity {
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 ATTACK_LAND_ANIM = RawAnimation.begin().thenLoop("attackland");
private static final RawAnimation ATTACK_AIR_ANIM = RawAnimation.begin().thenLoop("attackair");
private static final byte ATTACK_ANIM_EVENT = 5;
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
private int attackAnimTicks = 0;
public LurkingTerrorEntity(EntityType<? extends LurkingTerrorEntity> type, Level level) {
super(type, level);
this.moveControl = new FlyingMoveControl(this, 10, true);
}
public static AttributeSupplier.Builder createAttributes() {
return Mob.createMobAttributes()
.add(Attributes.MAX_HEALTH, AntarchySettings.lurkingTerrorHealth())
.add(Attributes.MOVEMENT_SPEED, 0.35D)
.add(Attributes.ATTACK_DAMAGE, AntarchySettings.lurkingTerrorAttackDamage())
.add(Attributes.FOLLOW_RANGE, 16.0D)
.add(Attributes.FLYING_SPEED, 0.55D);
}
public static boolean canSpawn(EntityType<LurkingTerrorEntity> entityType, ServerLevelAccessor level, MobSpawnType spawnReason, BlockPos pos, RandomSource random) {
if (spawnReason == MobSpawnType.SPAWN_EGG || spawnReason == MobSpawnType.SPAWNER || spawnReason == MobSpawnType.COMMAND) {
return true;
}
BlockPos belowPos = pos.below();
BlockState belowState = level.getBlockState(belowPos);
boolean validSupport = !belowState.is(Blocks.BEDROCK)
&& belowState.blocksMotion()
&& belowState.isFaceSturdy(level, belowPos, Direction.UP)
&& belowState.isCollisionShapeFullBlock(level, belowPos);
return level.getDifficulty() != Difficulty.PEACEFUL
&& validSupport
&& level.isEmptyBlock(pos)
&& level.isEmptyBlock(pos.above())
&& Monster.checkMonsterSpawnRules(entityType, level, spawnReason, pos, random);
}
@Override
protected PathNavigation createNavigation(Level level) {
FlyingPathNavigation nav = new FlyingPathNavigation(this, level);
nav.setCanOpenDoors(false);
nav.setCanFloat(true);
nav.setCanPassDoors(true);
return nav;
}
@Override
protected void registerGoals() {
this.goalSelector.addGoal(0, new FloatGoal(this));
this.goalSelector.addGoal(1, new MeleeAttackGoal(this, 1.0D, true));
this.goalSelector.addGoal(2, new WaterAvoidingRandomStrollGoal(this, 0.85D));
this.goalSelector.addGoal(3, new LookAtPlayerGoal(this, Player.class, 8.0F));
this.goalSelector.addGoal(4, new RandomLookAroundGoal(this));
this.targetSelector.addGoal(1, new HurtByTargetGoal(this));
this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true));
}
@Override
public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
controllers.add(new AnimationController<>(this, "main_controller", 3, this::mainAnimController));
}
private PlayState mainAnimController(AnimationState<LurkingTerrorEntity> state) {
if (attackAnimTicks > 0) {
if (!this.onGround()) {
return state.setAndContinue(ATTACK_AIR_ANIM);
}
return state.setAndContinue(ATTACK_LAND_ANIM);
}
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 geoCache;
}
@Override
public boolean doHurtTarget(net.minecraft.world.entity.Entity target) {
boolean result = super.doHurtTarget(target);
if (result) {
this.playSound(AntarchySoundEvents.LURKING_TERROR_BITE.get(), 1.0F, 0.95F + this.random.nextFloat() * 0.1F);
attackAnimTicks = 20;
this.level().broadcastEntityEvent(this, ATTACK_ANIM_EVENT);
}
return result;
}
@Override
protected SoundEvent getAmbientSound() {
return AntarchySoundEvents.LURKING_TERROR_SNARL.get();
}
@Override
protected SoundEvent getHurtSound(net.minecraft.world.damagesource.DamageSource source) {
return AntarchySoundEvents.LURKING_TERROR_HURT.get();
}
@Override
protected SoundEvent getDeathSound() {
return AntarchySoundEvents.LURKING_TERROR_HURT.get();
}
@Override
public void handleEntityEvent(byte id) {
if (id == ATTACK_ANIM_EVENT) {
attackAnimTicks = 20;
return;
}
super.handleEntityEvent(id);
}
@Override
public void tick() {
super.tick();
if (!this.level().isClientSide() && !this.onGround() && this.tickCount % 40 == 0) {
this.playSound(AntarchySoundEvents.LURKING_TERROR_FLY_LOOP.get(), 0.45F, 0.95F + this.random.nextFloat() * 0.1F);
}
if (!this.level().isClientSide() && attackAnimTicks > 0) attackAnimTicks--;
}
@Override
public boolean causeFallDamage(float fallDistance, float multiplier, net.minecraft.world.damagesource.DamageSource source) {
return false;
}
}
@@ -117,8 +117,9 @@ public class MantisEntity extends Monster implements GeoEntity {
boolean ignoreLightLevel = AntarchySettings.mantisIgnoreLightLevel();
// Daytime overworld spawns can't pass the vanilla darkness check, so no light gate here
if (level.getLevel().isDay() && level.getBiome(pos).is(AntarchyTags.Biomes.MANTIS_OVERWORLD_SPAWN_BIOMES)) {
return atSurface && level.canSeeSky(pos) && (ignoreLightLevel || Monster.checkMonsterSpawnRules(entityType, level, spawnReason, pos, random));
return atSurface && level.canSeeSky(pos);
}
// Elythia meadow (and any future mantis_spawn_biomes): nighttime, no sky-access requirement
@@ -62,6 +62,7 @@ public class MissileSquidEntity extends Monster implements GeoEntity {
private static final String LAUNCH_OWNER_KEY = "LaunchOwner";
private static final String ARMED_AFTER_LANDING_KEY = "ArmedAfterLanding";
private static final String ACTION_ANIM_TICKS_KEY = "ActionAnimTicks";
private static final String SPAWNED_BY_KRAKEN_KEY = "SpawnedByKraken";
private static final double AGGRO_SPEED = 0.5D;
private static final double PATROL_SPEED = 0.28D;
private static final double ATTACH_RANGE_SQR = 2.25D;
@@ -86,6 +87,7 @@ public class MissileSquidEntity extends Monster implements GeoEntity {
private int attachedTargetId = -1;
private boolean launchedFromSquidzooka;
private boolean armedAfterLanding;
private boolean spawnedByKraken;
@Nullable
private UUID launchOwnerUuid;
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
@@ -298,6 +300,7 @@ public class MissileSquidEntity extends Monster implements GeoEntity {
tag.putBoolean(LAUNCHED_FROM_SQUIDZOOKA_KEY, this.launchedFromSquidzooka);
tag.putBoolean(ARMED_AFTER_LANDING_KEY, this.armedAfterLanding);
tag.putInt(ACTION_ANIM_TICKS_KEY, this.actionAnimationTicks);
tag.putBoolean(SPAWNED_BY_KRAKEN_KEY, this.spawnedByKraken);
if (this.launchOwnerUuid != null) {
tag.putUUID(LAUNCH_OWNER_KEY, this.launchOwnerUuid);
}
@@ -311,10 +314,19 @@ public class MissileSquidEntity extends Monster implements GeoEntity {
this.launchedFromSquidzooka = tag.getBoolean(LAUNCHED_FROM_SQUIDZOOKA_KEY);
this.armedAfterLanding = tag.getBoolean(ARMED_AFTER_LANDING_KEY);
this.actionAnimationTicks = Math.max(0, tag.getInt(ACTION_ANIM_TICKS_KEY));
this.spawnedByKraken = tag.getBoolean(SPAWNED_BY_KRAKEN_KEY);
this.launchOwnerUuid = tag.hasUUID(LAUNCH_OWNER_KEY) ? tag.getUUID(LAUNCH_OWNER_KEY) : null;
this.updateAnimationState();
}
public boolean isSpawnedByKraken() {
return this.spawnedByKraken;
}
public void setSpawnedByKraken(boolean spawnedByKraken) {
this.spawnedByKraken = spawnedByKraken;
}
private void tickAggroMovement(LivingEntity target) {
this.patrolTarget = null;
this.getLookControl().setLookAt(target, 30.0F, 30.0F);
@@ -0,0 +1,613 @@
package com.craisinlord.antarchy.content.entity;
import com.craisinlord.antarchy.content.AntarchySoundEvents;
import com.craisinlord.antarchy.content.damage.AntarchyDamageSources;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.util.Mth;
import net.minecraft.util.RandomSource;
import net.minecraft.world.DifficultyInstance;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.*;
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.control.SmoothSwimmingMoveControl;
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.ai.navigation.WaterBoundPathNavigation;
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.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.*;
import software.bernie.geckolib.animation.AnimationState;
import software.bernie.geckolib.animation.keyframe.event.builtin.AutoPlayingSoundKeyframeHandler;
import software.bernie.geckolib.util.GeckoLibUtil;
import java.util.List;
import java.util.Objects;
public class OctopusBombEntity extends Monster implements GeoEntity {
private static final EntityDataAccessor<Integer> ATTACK_STATE = SynchedEntityData.defineId(OctopusBombEntity.class, EntityDataSerializers.INT);
private static final EntityDataAccessor<Integer> ANIMATION_STATE = SynchedEntityData.defineId(OctopusBombEntity.class, EntityDataSerializers.INT);
private static final EntityDataAccessor<Boolean> CAN_FLY = SynchedEntityData.defineId(OctopusBombEntity.class, EntityDataSerializers.BOOLEAN);
private static final int ATTACK_NONE = 0;
private static final int ATTACK_GRAB = 1;
private static final int ATTACK_SWIPE = 2;
private static final int ANIM_IDLE = 0;
private static final int ANIM_SWIM = 2;
private static final int ANIM_SWIPE = 3;
private static final int ANIM_GRAB = 4;
private static final int ANIM_DEATH = 5;
private static final int DEATH_ANIM_TICKS = 60;
private static final int INK_SPRAY_RANGE = 8;
private static final int INK_SPRAY_COOLDOWN = 80;
private static final RawAnimation IDLE_ANIM = RawAnimation.begin().thenLoop("idle");
private static final RawAnimation SWIM_ANIM = RawAnimation.begin().thenLoop("swim");
private static final RawAnimation SWIPE_ANIM = RawAnimation.begin().thenPlay("Swipe");
private static final RawAnimation GRAB_ANIM = RawAnimation.begin().thenPlay("Grab");
private static final RawAnimation DEATH_ANIM = RawAnimation.begin().thenPlay("Death");
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
// Squid-style wander
private float wanderDx, wanderDy, wanderDz;
private int wanderRetargetTicks;
// Combat
private int attackCooldown;
private int actionTicks;
private int lastAttackState;
private int grabbedTargetId = -1;
private int strafeRetargetTicks;
private float orbitDirection = 1.0F;
private int inkSprayCooldown;
// Kraken spawn flag
private boolean spawnedByKraken;
public OctopusBombEntity(EntityType<? extends Monster> entityType, Level level) {
super(entityType, level);
this.moveControl = new SmoothSwimmingMoveControl(this, 80, 8, 0.06F, 0.12F, true);
this.xpReward = 15;
}
public static AttributeSupplier.Builder createAttributes() {
return Mob.createMobAttributes()
.add(Attributes.MAX_HEALTH, 100.0D)
.add(Attributes.ATTACK_DAMAGE, 16.0D)
.add(Attributes.MOVEMENT_SPEED, 0.28D)
.add(Attributes.FLYING_SPEED, 0.28D)
.add(Attributes.FOLLOW_RANGE, 32.0D)
.add(Attributes.KNOCKBACK_RESISTANCE, 0.4D);
}
public static boolean canSpawn(EntityType<OctopusBombEntity> entityType, ServerLevelAccessor level, MobSpawnType spawnReason, net.minecraft.core.BlockPos pos, RandomSource random) {
return level.getFluidState(pos).is(net.minecraft.tags.FluidTags.WATER)
&& level.getFluidState(pos.above()).is(net.minecraft.tags.FluidTags.WATER);
}
@Override
protected void defineSynchedData(SynchedEntityData.Builder builder) {
super.defineSynchedData(builder);
builder.define(ATTACK_STATE, ATTACK_NONE);
builder.define(ANIMATION_STATE, ANIM_IDLE);
builder.define(CAN_FLY, false);
}
@Override
public void onSyncedDataUpdated(EntityDataAccessor<?> key) {
super.onSyncedDataUpdated(key);
if (CAN_FLY.equals(key)) {
this.moveControl = this.canFly()
? new FlyingMoveControl(this, 20, true)
: new SmoothSwimmingMoveControl(this, 80, 8, 0.06F, 0.12F, true);
this.navigation = this.canFly()
? new FlyingPathNavigation(this, this.level())
: new WaterBoundPathNavigation(this, this.level());
}
}
@Override
protected void registerGoals() {
this.goalSelector.addGoal(5, new LookAtPlayerGoal(this, Player.class, 20.0F));
this.goalSelector.addGoal(6, new RandomLookAroundGoal(this));
// Only retaliate when hit — neutral otherwise
this.targetSelector.addGoal(1, new HurtByTargetGoal(this));
// When spawned by Kraken and flying, target nearest player
this.targetSelector.addGoal(2, new KrakenSummonTargetGoal(this));
}
@Override
protected PathNavigation createNavigation(Level level) {
return new WaterBoundPathNavigation(this, level);
}
@Override
public SpawnGroupData finalizeSpawn(ServerLevelAccessor level, DifficultyInstance difficulty, MobSpawnType spawnReason, @Nullable SpawnGroupData spawnData) {
Objects.requireNonNull(this.getAttribute(Attributes.MAX_HEALTH)).setBaseValue(com.craisinlord.antarchy.config.AntarchySettings.octopusBombHealth());
this.setHealth((float) com.craisinlord.antarchy.config.AntarchySettings.octopusBombHealth());
Objects.requireNonNull(this.getAttribute(Attributes.ATTACK_DAMAGE)).setBaseValue(com.craisinlord.antarchy.config.AntarchySettings.octopusBombAttackDamage());
return super.finalizeSpawn(level, difficulty, spawnReason, spawnData);
}
@Override
public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
controllers.add(new AnimationController<>(this, "main_controller", 0, this::mainAnimController)
.setSoundKeyframeHandler(new AutoPlayingSoundKeyframeHandler<>()));
}
private PlayState mainAnimController(AnimationState<OctopusBombEntity> state) {
return switch (this.getAnimationState()) {
case ANIM_GRAB -> state.setAndContinue(GRAB_ANIM);
case ANIM_SWIPE -> state.setAndContinue(SWIPE_ANIM);
case ANIM_SWIM -> state.setAndContinue(SWIM_ANIM);
case ANIM_DEATH -> state.setAndContinue(DEATH_ANIM);
default -> state.setAndContinue(IDLE_ANIM);
};
}
@Override
public AnimatableInstanceCache getAnimatableInstanceCache() {
return this.geoCache;
}
@Override
public void baseTick() {
int airSupply = this.getAirSupply();
super.baseTick();
if (!this.canFly()) {
this.handleAirSupply(airSupply);
} else {
this.setAirSupply(this.getMaxAirSupply());
}
}
@Override
public void tick() {
super.tick();
this.setNoGravity(this.isInWater() || this.canFly());
if (this.level().isClientSide) {
this.updateSwimRotation();
return;
}
if (this.attackCooldown > 0) this.attackCooldown--;
if (this.inkSprayCooldown > 0) this.inkSprayCooldown--;
// Ink spray — always active regardless of aggro state
this.tickInkSpray();
LivingEntity target = this.getTarget();
if (target == null || !target.isAlive()) {
this.resetCombatState();
if (this.isInWater()) this.tickWanderMovement();
this.updateSwimRotation();
this.updateAnimationState();
return;
}
this.getLookControl().setLookAt(target, 25.0F, 20.0F);
if (this.getAttackState() != ATTACK_NONE) {
this.tickCurrentAttack(target);
} else {
this.tickPursuitMovement(target);
if (this.attackCooldown <= 0) {
int nextAttack = this.chooseNextAttack(target);
if (nextAttack != ATTACK_NONE) {
this.startAttack(nextAttack);
}
}
}
this.updateSwimRotation();
this.updateAnimationState();
}
@Override
public void travel(Vec3 travelVector) {
if (this.isEffectiveAi() && (this.isInWater() || this.canFly())) {
this.moveRelative(this.getSpeed(), travelVector);
this.move(MoverType.SELF, this.getDeltaMovement());
this.setDeltaMovement(this.getDeltaMovement().scale(this.isInWater() ? 0.92D : 0.91D));
return;
}
super.travel(travelVector);
}
@Override
protected boolean shouldDespawnInPeaceful() {
return false;
}
@Override
public boolean removeWhenFarAway(double distanceToClosestPlayer) {
return false;
}
@Override
@Nullable
protected SoundEvent getAmbientSound() {
return null;
}
@Override
protected SoundEvent getHurtSound(DamageSource damageSource) {
return AntarchySoundEvents.KRAKEN_HURT.get();
}
@Override
protected SoundEvent getDeathSound() {
return AntarchySoundEvents.KRAKEN_DEATH.get();
}
@Override
protected float getSoundVolume() {
return 1.2F;
}
@Override
public void addAdditionalSaveData(CompoundTag tag) {
super.addAdditionalSaveData(tag);
tag.putInt("AttackCooldown", this.attackCooldown);
tag.putInt("ActionTicks", this.actionTicks);
tag.putInt("LastAttackState", this.lastAttackState);
tag.putBoolean("CanFly", this.canFly());
tag.putBoolean("SpawnedByKraken", this.spawnedByKraken);
if (this.grabbedTargetId >= 0) tag.putInt("GrabTarget", this.grabbedTargetId);
tag.putInt("AttackState", this.getAttackState());
tag.putInt("AnimationState", this.getAnimationState());
}
@Override
public void readAdditionalSaveData(CompoundTag tag) {
super.readAdditionalSaveData(tag);
this.attackCooldown = tag.getInt("AttackCooldown");
this.actionTicks = tag.getInt("ActionTicks");
this.lastAttackState = tag.contains("LastAttackState") ? tag.getInt("LastAttackState") : ATTACK_NONE;
this.setCanFly(tag.getBoolean("CanFly"));
this.spawnedByKraken = tag.getBoolean("SpawnedByKraken");
this.grabbedTargetId = tag.contains("GrabTarget") ? tag.getInt("GrabTarget") : -1;
this.entityData.set(ATTACK_STATE, tag.getInt("AttackState"));
this.entityData.set(ANIMATION_STATE, tag.contains("AnimationState") ? tag.getInt("AnimationState") : ANIM_IDLE);
}
public boolean canFly() {
return this.entityData.get(CAN_FLY);
}
public void setCanFly(boolean canFly) {
this.entityData.set(CAN_FLY, canFly);
}
public int getAttackState() {
return this.entityData.get(ATTACK_STATE);
}
public int getAnimationState() {
return this.entityData.get(ANIMATION_STATE);
}
private void setAttackState(int state) {
this.entityData.set(ATTACK_STATE, state);
}
private void setAnimationState(int state) {
this.entityData.set(ANIMATION_STATE, state);
}
public void launchAsProjectile(net.minecraft.world.entity.Entity owner, Vec3 velocity) {
this.spawnedByKraken = true;
this.setCanFly(true);
this.setDeltaMovement(velocity);
}
// -------------------------------------------------------------------------
// Squid-style wander
// -------------------------------------------------------------------------
private void tickWanderMovement() {
if (--this.wanderRetargetTicks <= 0) {
this.wanderRetargetTicks = 40 + this.random.nextInt(40);
float angle = this.random.nextFloat() * (float) (Math.PI * 2);
this.wanderDx = Mth.cos(angle) * 0.015f;
this.wanderDz = Mth.sin(angle) * 0.015f;
this.wanderDy = -0.01f + this.random.nextFloat() * 0.01f; // slight downward bias
}
// Surface avoidance: if the block above is not water, push down
float dy = this.wanderDy;
if (!this.level().getFluidState(this.blockPosition().above()).is(net.minecraft.tags.FluidTags.WATER)) {
dy = -0.03f;
}
this.setDeltaMovement(this.getDeltaMovement().add(this.wanderDx, dy, this.wanderDz));
}
// -------------------------------------------------------------------------
// Ink spray
// -------------------------------------------------------------------------
private void tickInkSpray() {
if (this.inkSprayCooldown > 0 || !(this.level() instanceof ServerLevel serverLevel)) return;
Player nearbyPlayer = this.level().getNearestPlayer(this, INK_SPRAY_RANGE);
if (nearbyPlayer == null || nearbyPlayer.isCreative() || nearbyPlayer.isSpectator()) return;
this.inkSprayCooldown = INK_SPRAY_COOLDOWN;
this.playSound(AntarchySoundEvents.KRAKEN_HURT.get(), 1.0F, 1.49F);
double cx = this.getX();
double cy = this.getY() + this.getBbHeight() * 0.5D;
double cz = this.getZ();
serverLevel.sendParticles(ParticleTypes.SQUID_INK, cx, cy, cz, 120, 2.0D, 1.2D, 2.0D, 0.04D);
serverLevel.sendParticles(ParticleTypes.SQUID_INK, cx, cy, cz, 60, 3.5D, 1.8D, 3.5D, 0.02D);
}
// -------------------------------------------------------------------------
// Combat
// -------------------------------------------------------------------------
private void tickPursuitMovement(LivingEntity target) {
if (this.strafeRetargetTicks-- <= 0) {
this.strafeRetargetTicks = 10 + this.random.nextInt(12);
if (this.random.nextFloat() < 0.4F) this.orbitDirection *= -1.0F;
}
Vec3 attackPoint = this.createCombatTarget(target);
this.getMoveControl().setWantedPosition(attackPoint.x, attackPoint.y, attackPoint.z, 1.1D);
}
private Vec3 createCombatTarget(LivingEntity target) {
double radius = 9.0D;
double angle = this.tickCount * 0.16D * this.orbitDirection + this.getId() * 0.11D;
double x = target.getX() + Math.cos(angle) * radius;
double z = target.getZ() + Math.sin(angle) * radius;
double y = Mth.clamp(
target.getY() + 1.0D + Math.sin((this.tickCount + this.getId()) * 0.18D),
target.getY() - 3.0D,
target.getY() + 4.0D
);
return new Vec3(x, y, z);
}
private void tickCurrentAttack(LivingEntity target) {
this.getNavigation().stop();
this.getLookControl().setLookAt(target, 30.0F, 25.0F);
this.setDeltaMovement(this.getDeltaMovement().scale(0.8D));
switch (this.getAttackState()) {
case ATTACK_GRAB -> this.tickGrabAttack(target);
case ATTACK_SWIPE -> this.tickSwipeAttack();
default -> this.finishAttack();
}
}
private void startAttack(int attackState) {
this.setAttackState(attackState);
this.grabbedTargetId = -1;
if (attackState == ATTACK_GRAB) {
this.actionTicks = 55;
this.playSound(AntarchySoundEvents.KRAKEN_ATTACK.get(), 1.2F, 1.07F);
} else if (attackState == ATTACK_SWIPE) {
this.actionTicks = 40;
this.playSound(AntarchySoundEvents.KRAKEN_SPIN.get(), 1.1F, 1.13F);
}
}
private void tickGrabAttack(LivingEntity target) {
if (this.actionTicks == 48 && this.distanceToSqr(target) <= 42.25D) {
this.grabbedTargetId = target.getId();
}
LivingEntity grabbed = this.getGrabbedTarget();
if (grabbed != null && grabbed.isAlive()) {
this.holdGrabbedTarget(grabbed);
if (this.actionTicks <= 45 && this.actionTicks > 10 && this.actionTicks % 10 == 0) {
if (this.level() instanceof ServerLevel serverLevel) {
grabbed.hurt(AntarchyDamageSources.krakenMauling(serverLevel, this), 4.0F);
}
}
}
if (--this.actionTicks <= 0) {
if (grabbed != null && grabbed.isAlive()) this.pullDown(grabbed, 2.5D);
this.grabbedTargetId = -1;
this.finishAttack();
}
}
private void tickSwipeAttack() {
if (this.actionTicks == 10) this.performSwipe();
if (--this.actionTicks <= 0) this.finishAttack();
}
private void finishAttack() {
int finished = this.getAttackState();
this.setAttackState(ATTACK_NONE);
this.actionTicks = 0;
this.attackCooldown = 18;
this.grabbedTargetId = -1;
this.lastAttackState = finished;
}
private int chooseNextAttack(LivingEntity target) {
double distSqr = this.distanceToSqr(target);
boolean canGrab = distSqr <= 42.25D;
boolean canSwipe = distSqr <= 81.0D && this.isTargetInFront(target, -0.15D);
if (!canGrab && !canSwipe) return ATTACK_NONE;
int grabWeight = canGrab ? (this.lastAttackState == ATTACK_GRAB ? 1 : 4) : 0;
int swipeWeight = canSwipe ? (this.lastAttackState == ATTACK_SWIPE ? 1 : 5) : 0;
int total = grabWeight + swipeWeight;
if (total == 0) return ATTACK_NONE;
int roll = this.random.nextInt(total);
return roll < grabWeight ? ATTACK_GRAB : ATTACK_SWIPE;
}
private void performSwipe() {
AABB hitBox = this.getBoundingBox().inflate(7.0D, 3.0D, 7.0D);
List<Player> players = this.level().getEntitiesOfClass(Player.class, hitBox,
p -> p.isAlive() && this.isTargetInFront(p, -0.15D));
float damage = (float) this.getAttributeValue(Attributes.ATTACK_DAMAGE);
for (Player player : players) {
if (this.level() instanceof ServerLevel serverLevel) {
player.hurt(AntarchyDamageSources.krakenMauling(serverLevel, this), damage);
Vec3 dir = player.position().subtract(this.position());
Vec3 horizontal = new Vec3(dir.x, 0.0D, dir.z).normalize().scale(1.75D);
player.push(horizontal.x, 0.3D, horizontal.z);
player.hurtMarked = true;
}
}
}
private void holdGrabbedTarget(LivingEntity target) {
Vec3 anchor = this.position().add(0.0D, -this.getBbHeight() * 0.4D, 0.0D);
target.setDeltaMovement(anchor.subtract(target.position()).scale(0.35D));
target.resetFallDistance();
if (target instanceof ServerPlayer sp) {
sp.connection.teleport(anchor.x, anchor.y, anchor.z, target.getYRot(), target.getXRot());
} else {
target.moveTo(anchor.x, anchor.y, anchor.z, target.getYRot(), target.getXRot());
}
target.hurtMarked = true;
}
private void pullDown(LivingEntity target, double strength) {
target.push(0.0D, -strength, 0.0D);
target.hurtMarked = true;
}
@Nullable
private LivingEntity getGrabbedTarget() {
if (this.grabbedTargetId < 0) return null;
net.minecraft.world.entity.Entity e = this.level().getEntity(this.grabbedTargetId);
return e instanceof LivingEntity le ? le : null;
}
private boolean isTargetInFront(LivingEntity target, double minDot) {
Vec3 forward = this.getViewVector(1.0F);
Vec3 toTarget = target.position().subtract(this.position()).normalize();
return forward.dot(toTarget) >= minDot;
}
private void resetCombatState() {
this.setAttackState(ATTACK_NONE);
this.setTarget(null);
this.actionTicks = 0;
this.grabbedTargetId = -1;
}
private void handleAirSupply(int previousAirSupply) {
if (this.isInWaterOrBubble()) {
this.setAirSupply(this.getMaxAirSupply());
return;
}
this.setAirSupply(previousAirSupply - 1);
if (this.getAirSupply() <= -20) {
this.setAirSupply(0);
this.hurt(this.damageSources().drown(), 2.0F);
}
}
private void updateSwimRotation() {
Vec3 velocity = this.getDeltaMovement();
if (velocity.horizontalDistanceSqr() > 1.0E-4D) {
float targetYaw = (float) (Mth.atan2(velocity.z, velocity.x) * (180.0D / Math.PI)) - 90.0F;
this.setYRot(Mth.approachDegrees(this.getYRot(), targetYaw, 6.0F));
this.yBodyRot = this.getYRot();
this.yHeadRot = this.getYRot();
}
if (velocity.lengthSqr() > 1.0E-4D) {
float targetPitch = (float) (-(Mth.atan2(velocity.y, velocity.horizontalDistance()) * (180.0D / Math.PI)));
this.setXRot(Mth.approachDegrees(this.getXRot(), targetPitch, 4.0F));
}
}
private void updateAnimationState() {
if (this.isDeadOrDying()) {
this.setAnimationState(ANIM_DEATH);
return;
}
switch (this.getAttackState()) {
case ATTACK_GRAB -> { this.setAnimationState(ANIM_GRAB); return; }
case ATTACK_SWIPE -> { this.setAnimationState(ANIM_SWIPE); return; }
default -> {}
}
if (!this.isInWater()) {
this.setAnimationState(ANIM_SWIM);
return;
}
Vec3 vel = this.getDeltaMovement();
double hSqr = vel.horizontalDistanceSqr();
double vSqr = vel.y * vel.y;
if (hSqr > 0.03D && hSqr > vSqr * 2.5D) {
this.setAnimationState(ANIM_SWIM);
return;
}
this.setAnimationState(ANIM_IDLE);
}
@Override
public void die(DamageSource damageSource) {
if (!this.level().isClientSide) {
this.setAnimationState(ANIM_DEATH);
this.setAttackState(ATTACK_NONE);
this.setDeltaMovement(Vec3.ZERO);
this.getNavigation().stop();
}
super.die(damageSource);
}
@Override
protected void tickDeath() {
this.deathTime++;
if (this.deathTime == 1) this.setAnimationState(ANIM_DEATH);
if (this.deathTime >= DEATH_ANIM_TICKS) {
this.remove(RemovalReason.KILLED);
this.dropExperience(this);
}
}
// -------------------------------------------------------------------------
// Inner goal: only target players when spawned by Kraken in fly mode
// -------------------------------------------------------------------------
private static final class KrakenSummonTargetGoal extends NearestAttackableTargetGoal<Player> {
private final OctopusBombEntity octopus;
KrakenSummonTargetGoal(OctopusBombEntity octopus) {
super(octopus, Player.class, true);
this.octopus = octopus;
}
@Override
public boolean canUse() {
return this.octopus.spawnedByKraken && this.octopus.canFly() && super.canUse();
}
}
}
@@ -2,8 +2,13 @@ package com.craisinlord.antarchy.content.entity;
import com.craisinlord.antarchy.content.AntarchySoundEvents;
import net.minecraft.core.BlockPos;
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.sounds.SoundEvent;
import net.minecraft.util.RandomSource;
import net.minecraft.world.DifficultyInstance;
import net.minecraft.world.Difficulty;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.effect.MobEffectInstance;
@@ -13,6 +18,7 @@ import net.minecraft.world.entity.Entity;
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.goal.FloatGoal;
@@ -39,6 +45,11 @@ import org.jetbrains.annotations.Nullable;
public class ScorpionEntity extends Monster implements GeoEntity {
private static final byte ATTACK_ANIM_EVENT = 4;
private static final EntityDataAccessor<Integer> TEXTURE_VARIANT =
SynchedEntityData.defineId(ScorpionEntity.class, EntityDataSerializers.INT);
private static final String TEXTURE_VARIANT_KEY = "TextureVariant";
public static final int BLUE_VARIANT = 0;
public static final int GREEN_VARIANT = 1;
private static final int ATTACK_ANIM_TICKS = 12;
private static final int ATTACK_HIT_TICK = 6;
@@ -82,6 +93,12 @@ public class ScorpionEntity extends Monster implements GeoEntity {
&& Monster.checkMonsterSpawnRules(entityType, level, spawnReason, pos, random);
}
@Override
protected void defineSynchedData(SynchedEntityData.Builder builder) {
super.defineSynchedData(builder);
builder.define(TEXTURE_VARIANT, BLUE_VARIANT);
}
@Override
protected void registerGoals() {
this.goalSelector.addGoal(0, new FloatGoal(this));
@@ -93,6 +110,13 @@ public class ScorpionEntity extends Monster implements GeoEntity {
this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true));
}
@Nullable
@Override
public SpawnGroupData finalizeSpawn(ServerLevelAccessor level, DifficultyInstance difficulty, MobSpawnType spawnType, @Nullable SpawnGroupData spawnGroupData) {
this.assignRandomTextureVariant();
return super.finalizeSpawn(level, difficulty, spawnType, spawnGroupData);
}
@Override
public void tick() {
super.tick();
@@ -208,6 +232,22 @@ public class ScorpionEntity extends Monster implements GeoEntity {
return this.geoCache;
}
@Override
public void addAdditionalSaveData(CompoundTag tag) {
super.addAdditionalSaveData(tag);
tag.putInt(TEXTURE_VARIANT_KEY, this.getTextureVariant());
}
@Override
public void readAdditionalSaveData(CompoundTag tag) {
super.readAdditionalSaveData(tag);
if (tag.contains(TEXTURE_VARIANT_KEY)) {
this.setTextureVariant(tag.getInt(TEXTURE_VARIANT_KEY));
} else {
this.assignRandomTextureVariant();
}
}
@Override
protected SoundEvent getAmbientSound() {
return AntarchySoundEvents.SCORPION_AMBIENT.get();
@@ -228,6 +268,18 @@ public class ScorpionEntity extends Monster implements GeoEntity {
return 0.6F;
}
public int getTextureVariant() {
return this.entityData.get(TEXTURE_VARIANT);
}
public void setTextureVariant(int variant) {
this.entityData.set(TEXTURE_VARIANT, variant == GREEN_VARIANT ? GREEN_VARIANT : BLUE_VARIANT);
}
private void assignRandomTextureVariant() {
this.setTextureVariant(this.random.nextBoolean() ? BLUE_VARIANT : GREEN_VARIANT);
}
private final class ScorpionMeleeAttackGoal extends MeleeAttackGoal {
private ScorpionMeleeAttackGoal() {
super(ScorpionEntity.this, 1.15D, true);
@@ -0,0 +1,265 @@
package com.craisinlord.antarchy.content.entity;
import com.craisinlord.antarchy.Antarchy;
import net.minecraft.core.BlockPos;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.util.RandomSource;
import net.minecraft.world.Difficulty;
import net.minecraft.world.damagesource.DamageSource;
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.ai.attributes.AttributeSupplier;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.ai.goal.FloatGoal;
import net.minecraft.world.entity.ai.goal.Goal;
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.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.state.BlockState;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.phys.Vec3;
import software.bernie.geckolib.animatable.GeoEntity;
import software.bernie.geckolib.animatable.instance.AnimatableInstanceCache;
import software.bernie.geckolib.animation.AnimatableManager;
import software.bernie.geckolib.animation.AnimationController;
import software.bernie.geckolib.animation.AnimationState;
import software.bernie.geckolib.animation.PlayState;
import software.bernie.geckolib.animation.RawAnimation;
import software.bernie.geckolib.util.GeckoLibUtil;
import java.util.EnumSet;
public class SpitBugEntity extends Monster implements GeoEntity {
private static final EntityDataAccessor<Integer> SPIT_ANIM_TICKS =
SynchedEntityData.defineId(SpitBugEntity.class, EntityDataSerializers.INT);
private static final RawAnimation IDLE_ANIM = RawAnimation.begin().thenLoop("idle");
private static final RawAnimation WALK_ANIM = RawAnimation.begin().thenLoop("walk");
private static final RawAnimation SPIT_ANIM = RawAnimation.begin().thenPlay("spit");
private static final double MAX_HEALTH = 50.0D;
private static final double MOVEMENT_SPEED = 0.23D;
private static final double ATTACK_DAMAGE = 5.0D;
private static final double FOLLOW_RANGE = 24.0D;
private static final double SPIT_RANGE_SQR = 14.0D * 14.0D;
private static final double MELEE_RANGE_SQR = 3.5D * 3.5D;
private static final int SPIT_COOLDOWN_TICKS = 55;
private static final int SPIT_ANIMATION_TICKS = 18;
private static final int SPIT_RELEASE_TICKS = 8;
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
private int spitCooldownTicks;
private int spitAnimationTicks;
private int spitReleaseTicks;
public SpitBugEntity(EntityType<? extends SpitBugEntity> entityType, Level level) {
super(entityType, level);
this.xpReward = 10;
}
public static AttributeSupplier.Builder createAttributes() {
return Mob.createMobAttributes()
.add(Attributes.MAX_HEALTH, MAX_HEALTH)
.add(Attributes.MOVEMENT_SPEED, MOVEMENT_SPEED)
.add(Attributes.ATTACK_DAMAGE, ATTACK_DAMAGE)
.add(Attributes.FOLLOW_RANGE, FOLLOW_RANGE)
.add(Attributes.ARMOR, 4.0D)
.add(Attributes.KNOCKBACK_RESISTANCE, 0.2D);
}
public static boolean canSpawn(EntityType<SpitBugEntity> entityType, ServerLevelAccessor level, MobSpawnType spawnReason, BlockPos pos, RandomSource random) {
if (spawnReason == MobSpawnType.SPAWN_EGG || spawnReason == MobSpawnType.SPAWNER || spawnReason == MobSpawnType.COMMAND) {
return true;
}
return level.getDifficulty() != Difficulty.PEACEFUL
&& level.isEmptyBlock(pos)
&& level.isEmptyBlock(pos.above())
&& Monster.checkMonsterSpawnRules(entityType, level, spawnReason, pos, random);
}
@Override
protected void defineSynchedData(SynchedEntityData.Builder builder) {
super.defineSynchedData(builder);
builder.define(SPIT_ANIM_TICKS, 0);
}
@Override
protected void registerGoals() {
this.goalSelector.addGoal(0, new FloatGoal(this));
this.goalSelector.addGoal(1, new SpitAttackGoal());
this.goalSelector.addGoal(2, new MeleeAttackGoal(this, 1.0D, false));
this.goalSelector.addGoal(3, new WaterAvoidingRandomStrollGoal(this, 0.9D));
this.goalSelector.addGoal(4, new LookAtPlayerGoal(this, Player.class, 12.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 void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
controllers.add(new AnimationController<>(this, "main_controller", 2, this::mainAnimController));
}
@Override
public AnimatableInstanceCache getAnimatableInstanceCache() {
return this.geoCache;
}
@Override
public void tick() {
super.tick();
if (this.level().isClientSide) {
return;
}
if (this.spitCooldownTicks > 0) {
this.spitCooldownTicks--;
}
if (this.spitAnimationTicks > 0) {
this.spitAnimationTicks--;
this.entityData.set(SPIT_ANIM_TICKS, this.spitAnimationTicks);
if (this.spitReleaseTicks > 0 && --this.spitReleaseTicks == 0) {
this.fireSpitProjectile();
}
}
}
@Override
public boolean doHurtTarget(net.minecraft.world.entity.Entity target) {
boolean hurt = super.doHurtTarget(target);
if (hurt) {
this.playSound(SoundEvents.SPIDER_HURT, 0.7F, 0.8F + this.random.nextFloat() * 0.2F);
}
return hurt;
}
@Override
protected SoundEvent getAmbientSound() {
return SoundEvents.SPIDER_AMBIENT;
}
@Override
protected SoundEvent getHurtSound(DamageSource damageSource) {
return SoundEvents.SPIDER_HURT;
}
@Override
protected SoundEvent getDeathSound() {
return SoundEvents.SPIDER_DEATH;
}
@Override
protected void playStepSound(BlockPos pos, BlockState blockState) {
this.playSound(SoundEvents.SPIDER_STEP, 0.18F, 0.95F);
}
private PlayState mainAnimController(AnimationState<SpitBugEntity> state) {
if (this.entityData.get(SPIT_ANIM_TICKS) > 0) {
return state.setAndContinue(SPIT_ANIM);
}
return state.setAndContinue(state.isMoving() ? WALK_ANIM : IDLE_ANIM);
}
private boolean canSpitAt(LivingEntity target) {
return this.spitCooldownTicks <= 0
&& this.spitAnimationTicks <= 0
&& this.hasLineOfSight(target)
&& this.distanceToSqr(target) <= SPIT_RANGE_SQR
&& this.distanceToSqr(target) > MELEE_RANGE_SQR;
}
private void startSpitAttack() {
this.spitCooldownTicks = SPIT_COOLDOWN_TICKS;
this.spitAnimationTicks = SPIT_ANIMATION_TICKS;
this.spitReleaseTicks = SPIT_RELEASE_TICKS;
this.entityData.set(SPIT_ANIM_TICKS, this.spitAnimationTicks);
}
@SuppressWarnings("unchecked")
private void fireSpitProjectile() {
if (!(this.level() instanceof ServerLevel serverLevel)) {
return;
}
LivingEntity target = this.getTarget();
if (target == null || !target.isAlive()) {
return;
}
EntityType<SpitBugProjectileEntity> projectileType = (EntityType<SpitBugProjectileEntity>) BuiltInRegistries.ENTITY_TYPE.getOptional(
ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "spit_bug_projectile")
).orElse(null);
if (projectileType == null) {
return;
}
SpitBugProjectileEntity projectile = new SpitBugProjectileEntity(projectileType, this, serverLevel);
Vec3 origin = this.position()
.add(0.0D, this.getBbHeight() * 0.55D, 0.0D)
.add(this.getViewVector(0.0F).scale(1.35D));
Vec3 aimPoint = target.getEyePosition().add(target.getDeltaMovement().scale(0.45D));
Vec3 direction = aimPoint.subtract(origin).normalize();
projectile.setPos(origin.x, origin.y, origin.z);
projectile.shoot(direction.x, direction.y, direction.z, 1.05F, 0.0F);
serverLevel.addFreshEntity(projectile);
this.playSound(SoundEvents.SLIME_ATTACK, 1.0F, 0.85F + this.random.nextFloat() * 0.15F);
}
private final class SpitAttackGoal extends Goal {
SpitAttackGoal() {
this.setFlags(EnumSet.of(Flag.MOVE, Flag.LOOK));
}
@Override
public boolean canUse() {
LivingEntity target = SpitBugEntity.this.getTarget();
return target != null && target.isAlive();
}
@Override
public boolean canContinueToUse() {
return this.canUse();
}
@Override
public void tick() {
LivingEntity target = SpitBugEntity.this.getTarget();
if (target == null) {
return;
}
SpitBugEntity.this.getLookControl().setLookAt(target, 30.0F, 30.0F);
if (SpitBugEntity.this.canSpitAt(target)) {
SpitBugEntity.this.getNavigation().stop();
SpitBugEntity.this.startSpitAttack();
return;
}
if (SpitBugEntity.this.distanceToSqr(target) > 36.0D || !SpitBugEntity.this.hasLineOfSight(target)) {
SpitBugEntity.this.getNavigation().moveTo(target, 1.0D);
} else {
SpitBugEntity.this.getNavigation().stop();
}
}
}
}
@@ -0,0 +1,172 @@
package com.craisinlord.antarchy.content.entity;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.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.projectile.ThrowableItemProjectile;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.EntityHitResult;
import net.minecraft.world.phys.Vec3;
public class SpitBugProjectileEntity extends ThrowableItemProjectile {
private static final EntityDataAccessor<Boolean> LINGERING =
SynchedEntityData.defineId(SpitBugProjectileEntity.class, EntityDataSerializers.BOOLEAN);
private static final int MAX_FLIGHT_TICKS = 100;
private static final int LINGER_DURATION_TICKS = 300;
private static final int DIRECT_HIT_DAMAGE = 8;
private static final float LINGER_RADIUS = 1.65F;
private boolean lingering;
private int lingeringTicks;
public SpitBugProjectileEntity(EntityType<? extends SpitBugProjectileEntity> entityType, Level level) {
super(entityType, level);
}
public SpitBugProjectileEntity(EntityType<? extends SpitBugProjectileEntity> entityType, LivingEntity owner, Level level) {
super(entityType, owner, level);
}
@Override
protected void defineSynchedData(SynchedEntityData.Builder builder) {
super.defineSynchedData(builder);
builder.define(LINGERING, false);
}
@Override
protected Item getDefaultItem() {
return Items.SLIME_BALL;
}
@Override
protected double getDefaultGravity() {
return 0.03D;
}
@Override
public boolean canBeCollidedWith() {
return false;
}
@Override
public boolean isAttackable() {
return false;
}
public boolean isLingering() {
return this.entityData.get(LINGERING);
}
public float getRenderScale(float partialTick) {
if (!this.isLingering()) {
return 0.65F;
}
float progress = Math.min(1.0F, (this.lingeringTicks + partialTick) / 8.0F);
return 0.6F + progress * 0.5F;
}
@Override
public void tick() {
if (this.isLingering()) {
this.tickLingering();
return;
}
super.tick();
if (this.tickCount > MAX_FLIGHT_TICKS) {
this.discard();
return;
}
if (this.level().isClientSide) {
this.level().addParticle(ParticleTypes.ITEM_SLIME, this.getX(), this.getY(), this.getZ(), 0.0D, 0.0D, 0.0D);
}
}
private void tickLingering() {
this.lingeringTicks++;
this.setDeltaMovement(Vec3.ZERO);
if (this.level().isClientSide) {
double radius = 0.4D + this.random.nextDouble() * 0.45D;
this.level().addParticle(
ParticleTypes.ITEM_SLIME,
this.getX() + (this.random.nextDouble() - 0.5D) * radius,
this.getY() + 0.04D,
this.getZ() + (this.random.nextDouble() - 0.5D) * radius,
0.0D,
0.0D,
0.0D
);
return;
}
if (this.lingeringTicks % 4 == 0) {
this.applyLingeringSpit();
}
if (this.lingeringTicks >= LINGER_DURATION_TICKS) {
this.discard();
}
}
private void applyLingeringSpit() {
for (LivingEntity target : this.level().getEntitiesOfClass(
LivingEntity.class,
this.getBoundingBox().inflate(LINGER_RADIUS, 0.6D, LINGER_RADIUS),
entity -> entity.isAlive() && entity != this.getOwner()
)) {
target.addEffect(new MobEffectInstance(MobEffects.MOVEMENT_SLOWDOWN, 30, 3, false, true, true));
target.makeStuckInBlock(Blocks.HONEY_BLOCK.defaultBlockState(), new Vec3(0.2D, 0.0D, 0.2D));
}
if (this.level() instanceof ServerLevel serverLevel) {
serverLevel.sendParticles(ParticleTypes.ITEM_SLIME, this.getX(), this.getY() + 0.05D, this.getZ(), 3, 0.45D, 0.02D, 0.45D, 0.0D);
}
}
@Override
protected void onHitEntity(EntityHitResult result) {
if (!this.level().isClientSide && result.getEntity() instanceof LivingEntity livingTarget) {
Entity owner = this.getOwner();
if (owner instanceof LivingEntity livingOwner) {
livingTarget.hurt(this.damageSources().mobProjectile(this, livingOwner), DIRECT_HIT_DAMAGE);
} else {
livingTarget.hurt(this.damageSources().magic(), DIRECT_HIT_DAMAGE);
}
livingTarget.addEffect(new MobEffectInstance(MobEffects.MOVEMENT_SLOWDOWN, 80, 3, false, true, true));
livingTarget.makeStuckInBlock(Blocks.HONEY_BLOCK.defaultBlockState(), new Vec3(0.25D, 0.0D, 0.25D));
}
this.beginLingering(result.getLocation());
}
@Override
protected void onHitBlock(BlockHitResult result) {
this.beginLingering(result.getLocation());
}
private void beginLingering(Vec3 location) {
if (this.isLingering()) {
return;
}
this.lingering = true;
this.entityData.set(LINGERING, true);
this.lingeringTicks = 0;
this.noPhysics = true;
this.setPos(location.x, location.y + 0.05D, location.z);
this.setDeltaMovement(Vec3.ZERO);
}
}
@@ -0,0 +1,140 @@
package com.craisinlord.antarchy.content.entity;
import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.content.StinkyBehavior;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.RandomSource;
import net.minecraft.world.Difficulty;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.AgeableMob;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.MobSpawnType;
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.ai.goal.FloatGoal;
import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal;
import net.minecraft.world.entity.ai.goal.PanicGoal;
import net.minecraft.world.entity.ai.goal.RandomLookAroundGoal;
import net.minecraft.world.entity.ai.goal.WaterAvoidingRandomStrollGoal;
import net.minecraft.world.entity.animal.Animal;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.ServerLevelAccessor;
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 StinkBugEntity extends Animal implements GeoEntity {
private static final String MAIN_CONTROLLER = "main_controller";
private static final String FART_CONTROLLER = "fart_controller";
private static final String FART_TRIGGER = "fart";
private static final RawAnimation IDLE_ANIM = RawAnimation.begin().thenLoop("idle");
private static final RawAnimation WALK_ANIM = RawAnimation.begin().thenLoop("walk");
private static final RawAnimation FART_ANIM = RawAnimation.begin().thenPlay("fart");
private static final int FART_ANIMATION_TICKS = 30;
private static final int FART_RELEASE_TICKS = 6;
private static final int STINK_BURST_PARTICLES = 32;
private static final double STINK_BURST_RADIUS = 8.0D;
private static final int STINK_BURST_DURATION_TICKS = 1200;
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
private int fartAnimationTicks;
private boolean pendingFartBurst;
public StinkBugEntity(EntityType<? extends StinkBugEntity> entityType, Level level) {
super(entityType, level);
this.xpReward = 2;
}
public static AttributeSupplier.Builder createAttributes() {
return Mob.createMobAttributes()
.add(Attributes.MAX_HEALTH, 6.0D)
.add(Attributes.MOVEMENT_SPEED, 0.24D)
.add(Attributes.FOLLOW_RANGE, 8.0D);
}
public static boolean canSpawn(EntityType<StinkBugEntity> entityType, ServerLevelAccessor level, MobSpawnType spawnReason, BlockPos pos, RandomSource random) {
if (spawnReason == MobSpawnType.SPAWN_EGG || spawnReason == MobSpawnType.SPAWNER || spawnReason == MobSpawnType.COMMAND) {
return true;
}
return level.getDifficulty() != Difficulty.PEACEFUL
&& Animal.checkAnimalSpawnRules(entityType, level, spawnReason, pos, random);
}
@Override
protected void registerGoals() {
this.goalSelector.addGoal(0, new FloatGoal(this));
this.goalSelector.addGoal(1, new PanicGoal(this, 1.4D));
this.goalSelector.addGoal(2, new WaterAvoidingRandomStrollGoal(this, 0.9D));
this.goalSelector.addGoal(3, new LookAtPlayerGoal(this, Player.class, 6.0F));
this.goalSelector.addGoal(4, new RandomLookAroundGoal(this));
}
@Override
public void tick() {
super.tick();
if (this.fartAnimationTicks > 0) {
this.fartAnimationTicks--;
if (this.fartAnimationTicks == FART_RELEASE_TICKS && this.pendingFartBurst && !this.level().isClientSide()) {
StinkyBehavior.emitBurst(this, STINK_BURST_PARTICLES);
StinkyBehavior.applyBurstStinkyEffect(this, STINK_BURST_RADIUS, STINK_BURST_DURATION_TICKS);
this.pendingFartBurst = false;
}
}
}
@Override
public boolean hurt(DamageSource source, float amount) {
boolean hurt = super.hurt(source, amount);
if (hurt && !this.pendingFartBurst) {
this.triggerFartAnimation();
this.pendingFartBurst = true;
}
return hurt;
}
@Override
public boolean isFood(ItemStack stack) {
return false;
}
@Override
@Nullable
public AgeableMob getBreedOffspring(ServerLevel level, AgeableMob otherParent) {
return AntarchyObjects.STINK_BUG.get().create(level);
}
@Override
public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
controllers.add(new AnimationController<>(this, MAIN_CONTROLLER, 2, this::mainAnimController));
controllers.add(new AnimationController<>(this, FART_CONTROLLER, 0, state -> PlayState.STOP)
.triggerableAnim(FART_TRIGGER, FART_ANIM));
}
@Override
public AnimatableInstanceCache getAnimatableInstanceCache() {
return this.geoCache;
}
private <T extends StinkBugEntity> PlayState mainAnimController(AnimationState<T> animationState) {
animationState.setAnimation(animationState.isMoving() ? WALK_ANIM : IDLE_ANIM);
return PlayState.CONTINUE;
}
private void triggerFartAnimation() {
this.fartAnimationTicks = FART_ANIMATION_TICKS;
if (!this.level().isClientSide()) {
this.triggerAnim(FART_CONTROLLER, FART_TRIGGER);
}
}
}
@@ -2,6 +2,8 @@ package com.craisinlord.antarchy.content.entity;
import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.content.AntarchySoundEvents;
import com.craisinlord.antarchy.content.damage.AntarchyDamageSources;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
@@ -15,6 +17,7 @@ import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.world.BossEvent;
import net.minecraft.world.Difficulty;
import net.minecraft.tags.DamageTypeTags;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
@@ -25,6 +28,7 @@ import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.ai.goal.Goal;
import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal;
import net.minecraft.world.entity.ai.goal.RandomLookAroundGoal;
import net.minecraft.world.entity.ai.goal.MoveTowardsTargetGoal;
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;
@@ -53,38 +57,56 @@ import java.util.UUID;
public class ToreterrorEntity extends Monster implements GeoEntity {
private static final EntityDataAccessor<Integer> ANIMATION_STATE = SynchedEntityData.defineId(ToreterrorEntity.class, EntityDataSerializers.INT);
private static final EntityDataAccessor<Boolean> PHASE_2 = SynchedEntityData.defineId(ToreterrorEntity.class, EntityDataSerializers.BOOLEAN);
private static final EntityDataAccessor<Boolean> JUMP_SHAKING = SynchedEntityData.defineId(ToreterrorEntity.class, EntityDataSerializers.BOOLEAN);
private static final int ANIM_IDLE = 0;
private static final int ANIM_WALK = 1;
private static final int ANIM_SHOOT = 2;
private static final int ANIM_JUMP_ATTACK = 3;
private static final int ANIM_SPIN = 4;
private static final int ANIM_DEATH = 5;
private static final int ANIM_SPIN_START = 4;
private static final int ANIM_SPIN_LOOP = 5;
private static final int ANIM_SPIN_END = 6;
private static final int ANIM_DEATH = 7;
private static final int ANIM_RAPID_SHOOT = 8;
private static final int ATTACK_RAPID_FIRE = 0;
private static final int ATTACK_BOMBERS = 1;
private static final int DEATH_TICKS = 60;
private static final int SHOOT_TOTAL_TICKS = 80;
private static final int SHOOT_FIRE_START_TICK = 12;
private static final int SHOOT_FIRE_END_TICK = 68;
private static final int SHOOT_FIRE_INTERVAL = 8;
private static final int SHOOT_TOTAL_TICKS = 75;
private static final int RAPID_FIRE_INTERVAL = 4;
private static final int RAPID_FIRE_END_TICK = 60;
private static final int SHOOT_BOMBER_TICK = 12;
private static final int SHOOT_BOMBER_SECOND_TICK = 32;
private static final int JUMP_TOTAL_TICKS = 40;
private static final int JUMP_LAUNCH_TICK = 10;
private static final int SPIN_TOTAL_TICKS = 40;
private static final int SPIN_START_TICK = 3;
private static final int SPIN_END_TICK = 22;
private static final int JUMP_SHAKE_TICKS = 12;
private static final int SPIN_START_TOTAL_TICKS = 5;
private static final int SPIN_LOOP_MIN_TICKS = 200;
private static final int SPIN_LOOP_MAX_TICKS = 300;
private static final int SPIN_END_TOTAL_TICKS = 10;
private static final int SPIN_HIT_INTERVAL = 5;
private static final int SPIN_STUCK_FAIL_LIMIT = 10;
private static final double RANGED_MIN_RANGE_SQR = 9.0D;
private static final double CLOSE_RANGE_SQR = 64.0D;
private static final double JUMP_RANGE_SQR = 400.0D;
private static final double RANGED_MAX_RANGE_SQR = 625.0D;
private static final double MELEE_RANGE_SQR = 9.0D;
private static final double SPIN_RADIUS = 4.5D;
private static final double SPIN_TRIGGER_RANGE_SQR = 225.0D;
private static final double SPIN_LEASH_RANGE_SQR = 225.0D;
private static final double SPIN_RADIUS = 2.5D;
private static final double SPIN_MOVE_SPEED = 0.65D;
private static final double SPIN_ANIMATION_SPEED = 0.49D;
private static final double JUMP_SHOCKWAVE_RADIUS = 6.0D;
private static final RawAnimation IDLE_ANIM = RawAnimation.begin().thenLoop("Idle");
private static final RawAnimation WALK_ANIM = RawAnimation.begin().thenLoop("walk");
private static final RawAnimation SHOOT_ANIM = RawAnimation.begin().thenPlay("shoot").thenLoop("Idle");
private static final RawAnimation RAPID_SHOOT_ANIM = RawAnimation.begin().thenLoop("shoot");
private static final RawAnimation JUMP_ANIM = RawAnimation.begin().thenPlay("jump_attack").thenLoop("Idle");
private static final RawAnimation SPIN_ANIM = RawAnimation.begin().thenPlay("spin_attack").thenLoop("Idle");
private static final RawAnimation SPIN_START_ANIM = RawAnimation.begin().thenPlay("spin_attack_start");
private static final RawAnimation SPIN_LOOP_ANIM = RawAnimation.begin().thenLoop("spin_attack");
private static final RawAnimation SPIN_END_ANIM = RawAnimation.begin().thenPlay("spin_attack_end");
private static final RawAnimation DEATH_ANIM = RawAnimation.begin().thenPlayAndHold("death");
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
@@ -92,15 +114,26 @@ public class ToreterrorEntity extends Monster implements GeoEntity {
private int shootAnimTicks;
private int jumpAnimTicks;
private int spinAnimTicks;
private int spinStartTicks;
private int spinLoopTicks;
private int spinEndTicks;
private int spinLoopTotalTicks;
private int shootCooldown;
private int jumpCooldown;
private int spinCooldown;
private boolean jumpLaunched;
private boolean jumpAirborne;
private boolean shockwaveApplied;
private boolean shootUseWaterBombs;
private int jumpShakeTicks;
private int shootAttackType;
private boolean shootBomberFired;
private int shootNextFireElapsed;
private boolean shootBomberFiredSecond;
private int rapidFireNextTick;
private Vec3 spinDirection = Vec3.ZERO;
private int spinStrafeSign = 1;
private int spinStuckTicks;
private Vec3 spinProgressAnchor = Vec3.ZERO;
private int spinProgressTicks;
private final Map<UUID, Integer> spinHitCooldowns = new HashMap<>();
public ToreterrorEntity(EntityType<? extends ToreterrorEntity> entityType, Level level) {
@@ -112,10 +145,11 @@ public class ToreterrorEntity extends Monster implements GeoEntity {
return Mob.createMobAttributes()
.add(Attributes.MAX_HEALTH, AntarchySettings.toreterrorHealth())
.add(Attributes.ATTACK_DAMAGE, 8.0D)
.add(Attributes.MOVEMENT_SPEED, 0.25D)
.add(Attributes.MOVEMENT_SPEED, 0.325D)
.add(Attributes.FOLLOW_RANGE, 48.0D)
.add(Attributes.KNOCKBACK_RESISTANCE, 0.85D)
.add(Attributes.ARMOR, 10.0D);
.add(Attributes.ARMOR, 16.0D)
.add(Attributes.ARMOR_TOUGHNESS, 4.0D);
}
public static boolean canSpawn(EntityType<ToreterrorEntity> entityType, ServerLevelAccessor level, MobSpawnType spawnReason, BlockPos pos, net.minecraft.util.RandomSource random) {
@@ -127,16 +161,20 @@ public class ToreterrorEntity extends Monster implements GeoEntity {
protected void defineSynchedData(SynchedEntityData.Builder builder) {
super.defineSynchedData(builder);
builder.define(ANIMATION_STATE, ANIM_IDLE);
builder.define(PHASE_2, false);
builder.define(JUMP_SHAKING, false);
}
@Override
protected void registerGoals() {
this.goalSelector.addGoal(0, new ToreterrorFloatGoal());
this.goalSelector.addGoal(1, new ToreterrorRangedGoal());
this.goalSelector.addGoal(2, new ToreterrorJumpGoal());
this.goalSelector.addGoal(3, new ToreterrorSpinGoal());
this.goalSelector.addGoal(4, new MoveTowardsTargetGoal(this, 1.0D, 64.0F));
this.goalSelector.addGoal(5, new LookAtPlayerGoal(this, Player.class, 16.0F));
this.goalSelector.addGoal(6, new RandomLookAroundGoal(this));
this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 0.6D));
this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 0.5D));
this.targetSelector.addGoal(1, new HurtByTargetGoal(this).setAlertOthers());
this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, this::canTargetEntity));
}
@@ -148,11 +186,18 @@ public class ToreterrorEntity extends Monster implements GeoEntity {
private PlayState mainAnimController(AnimationState<ToreterrorEntity> state) {
int animState = this.getAnimationState();
state.getController().setAnimationSpeed(1.0D);
return switch (animState) {
case ANIM_WALK -> state.setAndContinue(WALK_ANIM);
case ANIM_SHOOT -> state.setAndContinue(SHOOT_ANIM);
case ANIM_RAPID_SHOOT -> state.setAndContinue(RAPID_SHOOT_ANIM);
case ANIM_JUMP_ATTACK -> state.setAndContinue(JUMP_ANIM);
case ANIM_SPIN -> state.setAndContinue(SPIN_ANIM);
case ANIM_SPIN_START -> state.setAndContinue(SPIN_START_ANIM);
case ANIM_SPIN_LOOP -> {
state.getController().setAnimationSpeed(SPIN_ANIMATION_SPEED);
yield state.setAndContinue(SPIN_LOOP_ANIM);
}
case ANIM_SPIN_END -> state.setAndContinue(SPIN_END_ANIM);
case ANIM_DEATH -> state.setAndContinue(DEATH_ANIM);
default -> state.setAndContinue(IDLE_ANIM);
};
@@ -166,11 +211,27 @@ public class ToreterrorEntity extends Monster implements GeoEntity {
@Override
public void tick() {
super.tick();
if (this.level().isClientSide) return;
if (this.level().isClientSide) {
if (this.isJumpShaking()) {
com.craisinlord.antarchy.content.client.CameraShakeClientState.register(
this,
com.craisinlord.antarchy.content.client.CameraShakeClientState.TORETERROR_RANGE,
com.craisinlord.antarchy.content.client.CameraShakeClientState.TORETERROR_STRENGTH,
this::isJumpShaking
);
}
return;
}
if (this.shootCooldown > 0) this.shootCooldown--;
if (this.jumpCooldown > 0) this.jumpCooldown--;
if (this.spinCooldown > 0) this.spinCooldown--;
if (this.jumpShakeTicks > 0) {
this.jumpShakeTicks--;
if (this.jumpShakeTicks <= 0) {
this.entityData.set(JUMP_SHAKING, false);
}
}
this.spinHitCooldowns.replaceAll((uuid, cd) -> cd - 1);
this.spinHitCooldowns.entrySet().removeIf(e -> e.getValue() <= 0);
@@ -184,8 +245,8 @@ public class ToreterrorEntity extends Monster implements GeoEntity {
this.tickShootAnimation();
} else if (this.jumpAnimTicks > 0) {
this.tickJumpAnimation();
} else if (this.spinAnimTicks > 0) {
this.tickSpinAnimation();
} else if (this.isSpinSequenceActive()) {
this.tickSpinSequence();
} else {
this.updateIdleAnimation();
}
@@ -193,6 +254,55 @@ public class ToreterrorEntity extends Monster implements GeoEntity {
this.bossEvent.setProgress(this.getHealth() / this.getMaxHealth());
}
@Override
public boolean hurt(DamageSource source, float amount) {
if (source.is(net.minecraft.world.damagesource.DamageTypes.DROWN)) return false;
if (source.is(DamageTypeTags.IS_PROJECTILE)) {
amount *= (float) AntarchySettings.toreterrorProjectileDamageMultiplier();
}
if (!this.isPhase2()) {
float halfHealth = this.getMaxHealth() * 0.5F;
if (this.getHealth() - amount <= halfHealth) {
float capDamage = this.getHealth() - halfHealth;
if (capDamage <= 0.0F) {
this.enterPhase2();
return false;
}
boolean result = super.hurt(source, capDamage);
if (result) this.enterPhase2();
return result;
}
}
return super.hurt(source, amount);
}
private void enterPhase2() {
this.entityData.set(PHASE_2, true);
}
public boolean isPhase2() {
return this.entityData.get(PHASE_2);
}
public boolean isJumpShaking() {
return this.entityData.get(JUMP_SHAKING);
}
@Override
protected net.minecraft.sounds.SoundEvent getAmbientSound() {
return AntarchySoundEvents.TORETERROR_IDLE.get();
}
@Override
protected net.minecraft.sounds.SoundEvent getHurtSound(DamageSource damageSource) {
return AntarchySoundEvents.TORETERROR_HURT.get();
}
@Override
protected net.minecraft.sounds.SoundEvent getDeathSound() {
return AntarchySoundEvents.TORETERROR_DEATH.get();
}
@Override
public void startSeenByPlayer(ServerPlayer player) {
super.startSeenByPlayer(player);
@@ -243,7 +353,7 @@ public class ToreterrorEntity extends Monster implements GeoEntity {
this.setAnimationState(ANIM_DEATH);
this.shootAnimTicks = 0;
this.jumpAnimTicks = 0;
this.spinAnimTicks = 0;
this.stopSpinSequence();
this.setDeltaMovement(Vec3.ZERO);
this.getNavigation().stop();
}
@@ -279,27 +389,44 @@ public class ToreterrorEntity extends Monster implements GeoEntity {
}
private boolean isInAttackAnimation() {
return this.shootAnimTicks > 0 || this.jumpAnimTicks > 0 || this.spinAnimTicks > 0;
return this.shootAnimTicks > 0 || this.jumpAnimTicks > 0 || this.isSpinSequenceActive();
}
private boolean isSpinSequenceActive() {
return this.spinStartTicks > 0 || this.spinLoopTicks > 0 || this.spinEndTicks > 0;
}
private void tickShootAnimation() {
this.setAnimationState(ANIM_SHOOT);
int elapsed = SHOOT_TOTAL_TICKS - this.shootAnimTicks;
if (this.shootUseWaterBombs) {
if (elapsed >= this.shootNextFireElapsed && this.shootNextFireElapsed <= SHOOT_FIRE_END_TICK) {
this.shootNextFireElapsed += SHOOT_FIRE_INTERVAL;
this.fireWaterBomb();
switch (this.shootAttackType) {
case ATTACK_RAPID_FIRE -> {
this.setAnimationState(ANIM_RAPID_SHOOT);
if (elapsed >= this.rapidFireNextTick && elapsed < RAPID_FIRE_END_TICK) {
this.rapidFireNextTick += RAPID_FIRE_INTERVAL;
this.fireRapidBomb();
}
}
} else {
if (!this.shootBomberFired && elapsed >= SHOOT_BOMBER_TICK) {
this.shootBomberFired = true;
this.fireBomberBurst();
default -> {
this.setAnimationState(ANIM_SHOOT);
if (!this.shootBomberFired && elapsed >= SHOOT_BOMBER_TICK) {
this.shootBomberFired = true;
this.fireBomberBurst();
}
if (this.isPhase2() && !this.shootBomberFiredSecond && elapsed >= SHOOT_BOMBER_SECOND_TICK) {
this.shootBomberFiredSecond = true;
this.fireBomberBurst();
}
}
}
this.getNavigation().stop();
this.setDeltaMovement(this.getDeltaMovement().scale(0.85D));
if (this.getTarget() != null) {
this.getLookControl().setLookAt(this.getTarget(), 30.0F, 20.0F);
Vec3 toTarget = this.getTarget().position().subtract(this.position());
float yaw = (float) (net.minecraft.util.Mth.atan2(toTarget.z, toTarget.x) * (180.0D / Math.PI)) - 90.0F;
this.setYRot(yaw);
this.yBodyRot = yaw;
this.yHeadRot = yaw;
this.setYHeadRot(yaw);
}
this.shootAnimTicks--;
if (this.shootAnimTicks <= 0) {
@@ -315,13 +442,28 @@ public class ToreterrorEntity extends Monster implements GeoEntity {
LivingEntity target = this.getTarget();
if (target != null) {
Vec3 toTarget = target.position().subtract(this.position()).normalize();
this.setDeltaMovement(toTarget.x * 0.6D, 0.85D, toTarget.z * 0.6D);
this.setDeltaMovement(toTarget.x * 0.6D, 1.15D, toTarget.z * 0.6D);
this.hasImpulse = true;
}
}
if (this.jumpLaunched && this.onGround() && elapsed > JUMP_LAUNCH_TICK + 3 && !this.shockwaveApplied) {
this.shockwaveApplied = true;
this.applyJumpShockwave();
if (this.jumpLaunched && !this.onGround()) {
this.jumpAirborne = true;
}
if (this.jumpLaunched && this.jumpAirborne && !this.shockwaveApplied) {
if (this.onGround()) {
this.shockwaveApplied = true;
this.playSound(AntarchySoundEvents.TORETERROR_JUMP_LAND.get(), 1.2F, 0.9F + this.random.nextFloat() * 0.15F);
this.applyJumpShockwave();
this.jumpShakeTicks = JUMP_SHAKE_TICKS;
this.entityData.set(JUMP_SHAKING, true);
} else if (this.jumpAnimTicks <= 10) {
Vec3 vel = this.getDeltaMovement();
this.setDeltaMovement(vel.x * 0.5D, Math.min(vel.y, -1.5D), vel.z * 0.5D);
this.hasImpulse = true;
if (this.jumpAnimTicks <= 2) {
this.jumpAnimTicks = 2;
}
}
}
this.jumpAnimTicks--;
if (this.jumpAnimTicks <= 0) {
@@ -329,35 +471,270 @@ public class ToreterrorEntity extends Monster implements GeoEntity {
}
}
private void tickSpinAnimation() {
this.setAnimationState(ANIM_SPIN);
int elapsed = SPIN_TOTAL_TICKS - this.spinAnimTicks;
private void tickSpinSequence() {
this.getNavigation().stop();
this.setDeltaMovement(this.getDeltaMovement().multiply(0.7D, 1.0D, 0.7D));
if (elapsed >= SPIN_START_TICK && elapsed <= SPIN_END_TICK && elapsed % SPIN_HIT_INTERVAL == 0) {
if (this.spinStartTicks > 0) {
this.tickSpinStart();
return;
}
if (this.spinLoopTicks > 0) {
this.tickSpinLoop();
return;
}
this.tickSpinEnd();
}
private void tickSpinStart() {
this.setAnimationState(ANIM_SPIN_START);
this.setDeltaMovement(this.getDeltaMovement().scale(0.75D));
LivingEntity target = this.getTarget();
if (target != null) {
Vec3 towardTarget = horizontalDirectionTo(target.position());
if (towardTarget.lengthSqr() > 1.0E-4D) {
this.spinDirection = towardTarget;
this.lookInDirection(towardTarget);
}
}
this.spinStartTicks--;
}
private void tickSpinLoop() {
this.setAnimationState(ANIM_SPIN_LOOP);
LivingEntity target = this.getTarget();
if (target == null || !target.isAlive()) {
this.beginSpinEnd();
return;
}
this.updateSpinDirection(target);
if (!this.moveDuringSpin()) {
this.spinStuckTicks++;
if (this.spinStuckTicks >= SPIN_STUCK_FAIL_LIMIT) {
this.beginSpinEnd();
return;
}
} else {
this.spinStuckTicks = 0;
}
this.spinProgressTicks++;
if (this.spinProgressTicks >= 20) {
if (this.position().distanceToSqr(this.spinProgressAnchor) < 4.0D) {
this.beginSpinEnd();
return;
}
this.spinProgressAnchor = this.position();
this.spinProgressTicks = 0;
}
int elapsed = this.spinLoopTotalTicks - this.spinLoopTicks;
if (elapsed % SPIN_HIT_INTERVAL == 0) {
this.applySpinDamage();
}
this.spinAnimTicks--;
if (this.spinAnimTicks <= 0) {
this.spinCooldown = 160 + this.random.nextInt(80);
this.lookInDirection(this.spinDirection);
this.spinLoopTicks--;
if (this.spinLoopTicks <= 0) {
this.beginSpinEnd();
}
}
private void tickSpinEnd() {
this.setAnimationState(ANIM_SPIN_END);
this.setDeltaMovement(this.getDeltaMovement().multiply(0.7D, 1.0D, 0.7D));
this.spinEndTicks--;
if (this.spinEndTicks <= 0) {
this.stopSpinSequence();
this.spinCooldown = 340 + this.random.nextInt(80);
this.spinHitCooldowns.clear();
}
}
private void fireWaterBomb() {
private void updateSpinDirection(LivingEntity target) {
Vec3 towardTarget = horizontalDirectionTo(target.position());
if (towardTarget.lengthSqr() <= 1.0E-4D) {
return;
}
if (this.distanceToSqr(target) > SPIN_LEASH_RANGE_SQR) {
this.spinDirection = towardTarget;
return;
}
Vec3 forward = this.spinDirection.lengthSqr() > 1.0E-4D ? this.spinDirection.normalize() : towardTarget;
Vec3 combined = forward.scale(0.99D).add(towardTarget.scale(0.01D));
this.spinDirection = combined.lengthSqr() > 1.0E-4D ? combined.normalize() : towardTarget;
}
private boolean moveDuringSpin() {
Vec3 direction = this.spinDirection.lengthSqr() > 1.0E-4D ? this.spinDirection.normalize() : Vec3.ZERO;
if (direction == Vec3.ZERO) {
return false;
}
if (this.trySpinMoves(direction, false)) {
return true;
}
this.spinStrafeSign *= -1;
Vec3 bounced = rotateHorizontal(direction, this.spinStrafeSign * 35.0D);
if (this.trySpinMoves(bounced, true)) {
return true;
}
Vec3 reversed = direction.scale(-1.0D);
if (this.trySpinMoves(reversed, true)) {
return true;
}
Vec3 reversedBounce = rotateHorizontal(reversed, this.spinStrafeSign * 25.0D);
return this.trySpinMoves(reversedBounce, true);
}
private boolean trySpinMoves(Vec3 baseDirection, boolean bounced) {
Vec3[] attempts = new Vec3[]{
baseDirection,
rotateHorizontal(baseDirection, 28.0D * this.spinStrafeSign),
rotateHorizontal(baseDirection, -28.0D * this.spinStrafeSign),
rotateHorizontal(baseDirection, 52.0D * this.spinStrafeSign),
rotateHorizontal(baseDirection, -52.0D * this.spinStrafeSign)
};
for (Vec3 attempt : attempts) {
if (this.trySpinMove(attempt, 0.0D, 0.0D) || this.trySpinMove(attempt, 1.0D, 0.42D) || this.trySpinMove(attempt, -1.0D, -0.28D)) {
this.spinDirection = attempt.normalize();
if (bounced) {
this.spinStrafeSign *= -1;
}
return true;
}
}
return false;
}
private boolean trySpinMove(Vec3 direction, double yOffset, double verticalVelocity) {
Vec3 horizontalMove = new Vec3(direction.x * SPIN_MOVE_SPEED, 0.0D, direction.z * SPIN_MOVE_SPEED);
AABB movedBox = this.getBoundingBox().move(horizontalMove.x, yOffset, horizontalMove.z);
if (!this.level().noCollision(this, movedBox)) {
return false;
}
if (!this.hasSpinSupport(movedBox)) {
return false;
}
this.setDeltaMovement(horizontalMove.x, verticalVelocity, horizontalMove.z);
this.hasImpulse = true;
return true;
}
private boolean hasSpinSupport(AABB box) {
double probeY = box.minY - 0.2D;
double[][] probes = new double[][]{
{box.getCenter().x, box.getCenter().z},
{box.minX + 0.25D, box.minZ + 0.25D},
{box.minX + 0.25D, box.maxZ - 0.25D},
{box.maxX - 0.25D, box.minZ + 0.25D},
{box.maxX - 0.25D, box.maxZ - 0.25D}
};
for (double[] probe : probes) {
BlockPos supportPos = BlockPos.containing(probe[0], probeY, probe[1]);
if (this.level().loadedAndEntityCanStandOnFace(supportPos, this, Direction.UP)) {
return true;
}
BlockPos lowerSupportPos = supportPos.below();
if (this.level().loadedAndEntityCanStandOnFace(lowerSupportPos, this, Direction.UP)) {
return true;
}
}
return false;
}
private Vec3 horizontalDirectionTo(Vec3 targetPos) {
Vec3 diff = targetPos.subtract(this.position());
Vec3 horizontal = new Vec3(diff.x, 0.0D, diff.z);
return horizontal.lengthSqr() > 1.0E-4D ? horizontal.normalize() : Vec3.ZERO;
}
private void lookInDirection(Vec3 direction) {
if (direction.lengthSqr() <= 1.0E-4D) {
return;
}
float yaw = (float) (net.minecraft.util.Mth.atan2(direction.z, direction.x) * (180.0D / Math.PI)) - 90.0F;
this.setYRot(yaw);
this.yBodyRot = yaw;
this.yHeadRot = yaw;
this.setYHeadRot(yaw);
}
private static Vec3 rotateHorizontal(Vec3 direction, double degrees) {
if (direction.lengthSqr() <= 1.0E-4D) {
return Vec3.ZERO;
}
double radians = Math.toRadians(degrees);
double cos = Math.cos(radians);
double sin = Math.sin(radians);
double x = direction.x * cos - direction.z * sin;
double z = direction.x * sin + direction.z * cos;
return new Vec3(x, 0.0D, z).normalize();
}
private void beginSpinEnd() {
this.spinStartTicks = 0;
this.spinLoopTicks = 0;
if (this.spinEndTicks <= 0) {
this.spinEndTicks = SPIN_END_TOTAL_TICKS;
}
}
private void stopSpinSequence() {
this.spinStartTicks = 0;
this.spinLoopTicks = 0;
this.spinEndTicks = 0;
this.spinLoopTotalTicks = 0;
this.spinDirection = Vec3.ZERO;
this.spinStrafeSign = 1;
this.spinStuckTicks = 0;
this.spinProgressAnchor = Vec3.ZERO;
this.spinProgressTicks = 0;
}
private Vec3 calcLaunchVelocity(Vec3 from, Vec3 to, double minSpeed) {
Vec3 diff = to.subtract(from);
double dh = Math.sqrt(diff.x * diff.x + diff.z * diff.z);
double dy = diff.y;
double gravity = AntarchySettings.waterBombGravity();
// Choose travel time based on horizontal distance at minSpeed, minimum 8 ticks
double t = Math.max(8.0, dh / minSpeed);
double vx = diff.x / t;
double vz = diff.z / t;
// Compensate for gravity accumulating over t ticks
double vy = dy / t + 0.5 * gravity * t;
return new Vec3(vx, vy, vz);
}
private void fireRapidBomb() {
if (!(this.level() instanceof ServerLevel serverLevel)) return;
LivingEntity target = this.getTarget();
if (target == null) return;
WaterBombEntity bomb = new WaterBombEntity(this.level(), this);
bomb.moveTo(this.getX(), this.getEyeY(), this.getZ(), this.getYRot(), 0);
bomb.shootFromRotation(this, this.getXRot(), this.getYRot(), 0.0F, 1.4F, 1.5F);
serverLevel.addFreshEntity(bomb);
this.playSound(AntarchySoundEvents.SQUIDZOOKA_FIRE.get(), 0.8F, 0.9F + this.random.nextFloat() * 0.2F);
Vec3 from = new Vec3(this.getX(), this.getEyeY(), this.getZ());
Vec3 to = new Vec3(target.getX(), target.getEyeY(), target.getZ());
Vec3 baseVel = calcLaunchVelocity(from, to, 1.5);
int count = this.isPhase2() ? 2 : 1;
for (int i = 0; i < count; i++) {
double spread = count > 1 ? (i - 0.5) * 0.15 : 0.0;
Vec3 vel = new Vec3(baseVel.x + spread, baseVel.y, baseVel.z - spread);
WaterBombEntity bomb = new WaterBombEntity(this.level(), this);
bomb.setPos(from.x, from.y, from.z);
bomb.setDeltaMovement(vel);
bomb.hasImpulse = true;
serverLevel.addFreshEntity(bomb);
}
}
private void fireBomberBurst() {
if (!(this.level() instanceof ServerLevel serverLevel)) return;
LivingEntity target = this.getTarget();
if (target == null) return;
this.playSound(AntarchySoundEvents.TORETERROR_BOMBER_FIRE.get(), 1.1F, 0.9F + this.random.nextFloat() * 0.15F);
int count = 3;
for (int i = 0; i < count; i++) {
BomberEntity bomber = AntarchyObjects.BOMBER.get().create(this.level());
@@ -371,6 +748,7 @@ public class ToreterrorEntity extends Monster implements GeoEntity {
bomber.setDeltaMovement(vx, vy, vz);
bomber.setTarget(target);
serverLevel.addFreshEntity(bomber);
bomber.primeFuse();
}
}
@@ -380,7 +758,7 @@ public class ToreterrorEntity extends Monster implements GeoEntity {
List<LivingEntity> victims = this.level().getEntitiesOfClass(LivingEntity.class, shockArea, this::canTargetEntity);
float damage = (float) AntarchySettings.toreterrorJumpAttackDamage();
for (LivingEntity victim : victims) {
victim.hurt(this.damageSources().mobAttack(this), damage);
victim.hurt(AntarchyDamageSources.toreterrorJump(serverLevel, this), damage);
Vec3 knockDir = victim.position().subtract(this.position()).normalize().add(0, 0.4D, 0);
double knockback = AntarchySettings.toreterrorJumpAttackKnockback();
victim.push(knockDir.x * knockback, 0.5D, knockDir.z * knockback);
@@ -393,14 +771,14 @@ public class ToreterrorEntity extends Monster implements GeoEntity {
private void applySpinDamage() {
if (!(this.level() instanceof ServerLevel)) return;
AABB spinArea = this.getBoundingBox().inflate(SPIN_RADIUS, 2.0D, SPIN_RADIUS);
AABB spinArea = this.getBoundingBox().inflate(SPIN_RADIUS, SPIN_RADIUS, SPIN_RADIUS);
List<LivingEntity> victims = this.level().getEntitiesOfClass(LivingEntity.class, spinArea, this::canTargetEntity);
float damage = (float) AntarchySettings.toreterrorSpinDamage();
double knockback = AntarchySettings.toreterrorSpinKnockback();
for (LivingEntity victim : victims) {
UUID id = victim.getUUID();
if (this.spinHitCooldowns.getOrDefault(id, 0) > 0) continue;
if (victim.hurt(this.damageSources().mobAttack(this), damage)) {
if (victim.hurt(AntarchyDamageSources.toreterrorSpin((ServerLevel) this.level(), this), damage)) {
this.spinHitCooldowns.put(id, 20);
Vec3 knockDir = victim.position().subtract(this.position()).normalize();
victim.push(knockDir.x * knockback, 0.35D, knockDir.z * knockback);
@@ -428,16 +806,19 @@ public class ToreterrorEntity extends Monster implements GeoEntity {
if (target == null || ToreterrorEntity.this.isInAttackAnimation()) return false;
if (ToreterrorEntity.this.shootCooldown > 0) return false;
double distSqr = ToreterrorEntity.this.distanceToSqr(target);
return distSqr >= RANGED_MIN_RANGE_SQR && distSqr <= RANGED_MAX_RANGE_SQR
return distSqr > CLOSE_RANGE_SQR && distSqr <= RANGED_MAX_RANGE_SQR
&& ToreterrorEntity.this.hasLineOfSight(target);
}
@Override
public void start() {
ToreterrorEntity.this.shootAnimTicks = SHOOT_TOTAL_TICKS;
ToreterrorEntity.this.shootUseWaterBombs = ToreterrorEntity.this.random.nextFloat() < (float) AntarchySettings.toreterrorRangedWaterBombChance();
ToreterrorEntity.this.shootNextFireElapsed = SHOOT_FIRE_START_TICK;
ToreterrorEntity.this.shootBomberFired = false;
ToreterrorEntity.this.shootBomberFiredSecond = false;
ToreterrorEntity.this.rapidFireNextTick = 0;
float roll = ToreterrorEntity.this.random.nextFloat();
if (roll < 0.5F) ToreterrorEntity.this.shootAttackType = ATTACK_RAPID_FIRE;
else ToreterrorEntity.this.shootAttackType = ATTACK_BOMBERS;
}
@Override
@@ -462,14 +843,16 @@ public class ToreterrorEntity extends Monster implements GeoEntity {
if (target == null || ToreterrorEntity.this.isInAttackAnimation()) return false;
if (ToreterrorEntity.this.jumpCooldown > 0) return false;
double distSqr = ToreterrorEntity.this.distanceToSqr(target);
return distSqr <= RANGED_MAX_RANGE_SQR && ToreterrorEntity.this.onGround();
return distSqr <= JUMP_RANGE_SQR && ToreterrorEntity.this.onGround();
}
@Override
public void start() {
ToreterrorEntity.this.jumpAnimTicks = JUMP_TOTAL_TICKS;
ToreterrorEntity.this.jumpLaunched = false;
ToreterrorEntity.this.jumpAirborne = false;
ToreterrorEntity.this.shockwaveApplied = false;
ToreterrorEntity.this.playSound(AntarchySoundEvents.TORETERROR_JUMP_UP.get(), 1.2F, 0.9F + ToreterrorEntity.this.random.nextFloat() * 0.15F);
}
@Override
@@ -483,6 +866,28 @@ public class ToreterrorEntity extends Monster implements GeoEntity {
}
}
private final class ToreterrorFloatGoal extends Goal {
ToreterrorFloatGoal() {
this.setFlags(EnumSet.of(Flag.JUMP));
}
@Override
public boolean canUse() {
return ToreterrorEntity.this.isInWater() && !ToreterrorEntity.this.onGround();
}
@Override
public boolean canContinueToUse() {
return canUse();
}
@Override
public void tick() {
Vec3 vel = ToreterrorEntity.this.getDeltaMovement();
ToreterrorEntity.this.setDeltaMovement(vel.x, Math.min(vel.y + 0.08D, 0.2D), vel.z);
}
}
private final class ToreterrorSpinGoal extends Goal {
ToreterrorSpinGoal() {
this.setFlags(EnumSet.of(Flag.MOVE));
@@ -493,22 +898,34 @@ public class ToreterrorEntity extends Monster implements GeoEntity {
LivingEntity target = ToreterrorEntity.this.getTarget();
if (target == null || ToreterrorEntity.this.isInAttackAnimation()) return false;
if (ToreterrorEntity.this.spinCooldown > 0) return false;
return ToreterrorEntity.this.distanceToSqr(target) <= MELEE_RANGE_SQR && ToreterrorEntity.this.onGround();
return ToreterrorEntity.this.distanceToSqr(target) <= SPIN_TRIGGER_RANGE_SQR && ToreterrorEntity.this.onGround();
}
@Override
public void start() {
ToreterrorEntity.this.spinAnimTicks = SPIN_TOTAL_TICKS;
LivingEntity target = ToreterrorEntity.this.getTarget();
ToreterrorEntity.this.spinStartTicks = SPIN_START_TOTAL_TICKS;
ToreterrorEntity.this.spinLoopTotalTicks = SPIN_LOOP_MIN_TICKS + ToreterrorEntity.this.random.nextInt(SPIN_LOOP_MAX_TICKS - SPIN_LOOP_MIN_TICKS + 1);
ToreterrorEntity.this.spinLoopTicks = ToreterrorEntity.this.spinLoopTotalTicks;
ToreterrorEntity.this.spinEndTicks = 0;
ToreterrorEntity.this.spinStuckTicks = 0;
ToreterrorEntity.this.spinProgressAnchor = ToreterrorEntity.this.position();
ToreterrorEntity.this.spinProgressTicks = 0;
ToreterrorEntity.this.spinStrafeSign = ToreterrorEntity.this.random.nextBoolean() ? 1 : -1;
ToreterrorEntity.this.spinHitCooldowns.clear();
ToreterrorEntity.this.spinDirection = target == null
? Vec3.ZERO
: ToreterrorEntity.this.horizontalDirectionTo(target.position());
}
@Override
public boolean canContinueToUse() {
return ToreterrorEntity.this.spinAnimTicks > 0;
return ToreterrorEntity.this.isSpinSequenceActive();
}
@Override
public void stop() {
ToreterrorEntity.this.spinAnimTicks = 0;
ToreterrorEntity.this.stopSpinSequence();
}
}
}
@@ -2,6 +2,7 @@ package com.craisinlord.antarchy.content.entity;
import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.content.damage.AntarchyDamageSources;
import net.minecraft.core.BlockPos;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.nbt.CompoundTag;
@@ -11,6 +12,8 @@ import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.projectile.ThrowableProjectile;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.BaseFireBlock;
import net.minecraft.world.level.block.CampfireBlock;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.LiquidBlock;
import net.minecraft.world.level.material.Fluids;
@@ -18,6 +21,8 @@ import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.EntityHitResult;
import net.minecraft.world.phys.Vec3;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import java.util.HashMap;
@@ -26,6 +31,8 @@ import java.util.UUID;
public class WaterBombEntity extends ThrowableProjectile {
private static final EntityDataAccessor<Boolean> HUGE = SynchedEntityData.defineId(WaterBombEntity.class, EntityDataSerializers.BOOLEAN);
private int lifetimeTicks;
private final Map<UUID, Integer> hitCooldowns = new HashMap<>();
@@ -39,8 +46,18 @@ public class WaterBombEntity extends ThrowableProjectile {
this.lifetimeTicks = AntarchySettings.waterBombLifetimeTicks();
}
public WaterBombEntity(Level level, LivingEntity owner, boolean huge) {
this(level, owner);
this.entityData.set(HUGE, huge);
}
@Override
protected void defineSynchedData(SynchedEntityData.Builder builder) {
builder.define(HUGE, false);
}
public boolean isHuge() {
return this.entityData.get(HUGE);
}
@Override
@@ -75,23 +92,40 @@ public class WaterBombEntity extends ThrowableProjectile {
UUID id = entity.getUUID();
if (this.hitCooldowns.getOrDefault(id, 0) > 0) return;
float damage = (float) AntarchySettings.waterBombDamage();
if (living.hurt(this.damageSources().thrown(this, this.getOwner()), damage)) {
float damage = (float) AntarchySettings.waterBombDamage() * (this.isHuge() ? 3.0F : 1.0F);
if (living.hurt(AntarchyDamageSources.waterSoaked(this.level(), this, this.getOwner()), damage)) {
this.hitCooldowns.put(id, 15);
Vec3 knockDir = living.position().subtract(this.position()).normalize();
double knockback = AntarchySettings.waterBombKnockback();
living.push(knockDir.x * knockback, 0.3D, knockDir.z * knockback);
living.hurtMarked = true;
}
living.clearFire();
}
@Override
protected void onHitBlock(BlockHitResult result) {
BlockPos hitPos = result.getBlockPos().relative(result.getDirection());
BlockPos blockPos = result.getBlockPos();
BlockPos hitPos = blockPos.relative(result.getDirection());
Level level = this.level();
if (level.isEmptyBlock(hitPos) && level.getBlockState(hitPos.below()).isSolid()) {
level.setBlock(hitPos, Blocks.WATER.defaultBlockState().setValue(LiquidBlock.LEVEL, 2), 3);
level.scheduleTick(hitPos, Fluids.WATER, 2);
if (level.getBlockState(blockPos).getBlock() instanceof CampfireBlock campfire && level.getBlockState(blockPos).getValue(CampfireBlock.LIT)) {
level.setBlock(blockPos, level.getBlockState(blockPos).setValue(CampfireBlock.LIT, false), 3);
this.discard();
return;
}
if (this.isHuge()) {
for (BlockPos pos : BlockPos.betweenClosed(hitPos.offset(-1, 0, -1), hitPos.offset(1, 0, 1))) {
if (level.getBlockState(pos).getBlock() instanceof BaseFireBlock) {
level.removeBlock(pos, false);
}
}
} else {
if (level.getBlockState(hitPos).getBlock() instanceof BaseFireBlock) {
level.removeBlock(hitPos, false);
} else if (level.isEmptyBlock(hitPos) && level.getBlockState(hitPos.below()).isSolid()) {
level.setBlock(hitPos, Blocks.WATER.defaultBlockState().setValue(LiquidBlock.LEVEL, 1), 3);
level.scheduleTick(hitPos, Fluids.WATER, 2);
}
}
this.discard();
}
@@ -100,11 +134,13 @@ public class WaterBombEntity extends ThrowableProjectile {
public void addAdditionalSaveData(CompoundTag tag) {
super.addAdditionalSaveData(tag);
tag.putInt("LifetimeTicks", this.lifetimeTicks);
tag.putBoolean("Huge", this.isHuge());
}
@Override
public void readAdditionalSaveData(CompoundTag tag) {
super.readAdditionalSaveData(tag);
this.lifetimeTicks = tag.contains("LifetimeTicks") ? tag.getInt("LifetimeTicks") : AntarchySettings.waterBombLifetimeTicks();
if (tag.contains("Huge")) this.entityData.set(HUGE, tag.getBoolean("Huge"));
}
}
@@ -1,29 +1,103 @@
package com.craisinlord.antarchy.content.entity.ant;
import com.craisinlord.antarchy.config.AntarchySettings;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceKey;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.PlayerRespawnLogic;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.OwnableEntity;
import net.minecraft.world.entity.TamableAnimal;
import net.minecraft.world.entity.animal.horse.AbstractHorse;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.vehicle.DismountHelper;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.portal.DimensionTransition;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
public final class AntTeleportHelper {
private static final double COMPANION_FOLLOW_RADIUS = 16.0D;
private AntTeleportHelper() {
}
public static void teleportPlayerWithCompanions(ServerPlayer player, ServerLevel destination, Vec3 destinationPos) {
Mob vehicle = player.getVehicle() instanceof Mob mob ? mob : null;
List<Mob> companions = collectCompanions(player);
player.teleportTo(destination, destinationPos.x, destinationPos.y, destinationPos.z, player.getYRot(), player.getXRot());
Mob movedVehicle = null;
for (Mob companion : companions) {
Mob moved = moveCompanion(companion, destination, destinationPos);
if (companion == vehicle) {
movedVehicle = moved;
}
}
if (movedVehicle != null) {
player.startRiding(movedVehicle, true);
}
}
private static List<Mob> collectCompanions(ServerPlayer player) {
List<Mob> companions = new ArrayList<>();
if (player.getVehicle() instanceof Mob vehicle) {
companions.add(vehicle);
}
for (Mob mob : player.serverLevel().getEntitiesOfClass(Mob.class, player.getBoundingBox().inflate(COMPANION_FOLLOW_RADIUS), mob -> isFollowingPet(mob, player))) {
if (!companions.contains(mob)) {
companions.add(mob);
}
}
return companions;
}
private static boolean isFollowingPet(Mob mob, ServerPlayer player) {
if (!mob.isAlive()) {
return false;
}
if (mob instanceof TamableAnimal tamable) {
return tamable.isTame() && player.getUUID().equals(tamable.getOwnerUUID()) && !tamable.isOrderedToSit();
}
if (mob instanceof AbstractHorse horse) {
return horse.isTamed() && player.getUUID().equals(horse.getOwnerUUID());
}
return mob instanceof OwnableEntity ownable && player.getUUID().equals(ownable.getOwnerUUID());
}
@Nullable
private static Mob moveCompanion(Mob companion, ServerLevel destination, Vec3 destinationPos) {
companion.stopRiding();
companion.ejectPassengers();
companion.getNavigation().stop();
if (companion.level() == destination) {
companion.teleportTo(destinationPos.x, destinationPos.y, destinationPos.z);
return companion;
}
Entity moved = companion.changeDimension(new DimensionTransition(destination, destinationPos, Vec3.ZERO, companion.getYRot(), companion.getXRot(), DimensionTransition.DO_NOTHING));
return moved instanceof Mob mob ? mob : null;
}
static InteractionResult handleInteraction(BaseAntEntity ant, Player player, ItemStack itemStack) {
if (ant.requiresActivationReagent() && !ant.isTeleportActivatedState()) {
if (!(player instanceof ServerPlayer serverPlayer)) {
@@ -59,7 +133,7 @@ public final class AntTeleportHelper {
}
Vec3 destinationPos = getDestinationPosition(serverPlayer, destination);
serverPlayer.teleportTo(destination, destinationPos.x, destinationPos.y, destinationPos.z, serverPlayer.getYRot(), serverPlayer.getXRot());
teleportPlayerWithCompanions(serverPlayer, destination, destinationPos);
serverPlayer.playSound(SoundEvents.ENDERMAN_TELEPORT, 1.0F, 1.0F);
return InteractionResult.CONSUME;
}
@@ -87,7 +161,7 @@ public final class AntTeleportHelper {
return destination;
}
// If the player's respawn is not in the overworld, return them to overworld spawn instead.
// If the player's respawn is not in the overworld, return them to overworld spawn instead
return player.server.overworld();
}
@@ -111,6 +185,11 @@ public final class AntTeleportHelper {
@Nullable
private static Vec3 findSafeArrivalPosition(ServerPlayer player, ServerLevel destination, BlockPos preferredPos) {
int[] yRange = getDimensionYRange(destination);
if (yRange != null) {
return findSafeArrivalPositionInYRange(player, destination, preferredPos, yRange[0], yRange[1]);
}
Set<BlockPos> candidates = new LinkedHashSet<>();
BlockPos adjustedPreferredPos = player.adjustSpawnLocation(destination, preferredPos);
addArrivalCandidate(candidates, preferredPos);
@@ -146,6 +225,40 @@ public final class AntTeleportHelper {
return null;
}
@Nullable
private static int[] getDimensionYRange(ServerLevel destination) {
ResourceKey<Level> dim = destination.dimension();
if (dim == AntarchySettings.termiteDestinationDimension()) {
return new int[]{100, 200};
}
if (dim == AntarchySettings.brownAntDestinationDimension()) {
return new int[]{75, 120};
}
return null;
}
@Nullable
private static Vec3 findSafeArrivalPositionInYRange(ServerPlayer player, ServerLevel destination, BlockPos preferredPos, int minY, int maxY) {
for (int radius = 0; radius <= 8; radius++) {
for (int xOff = -radius; xOff <= radius; xOff++) {
for (int zOff = -radius; zOff <= radius; zOff++) {
if (radius > 0 && Math.abs(xOff) != radius && Math.abs(zOff) != radius) {
continue;
}
int x = preferredPos.getX() + xOff;
int z = preferredPos.getZ() + zOff;
for (int y = maxY; y >= minY + 1; y--) {
Vec3 safePos = tryFindSafeDismount(player, destination, new BlockPos(x, y, z));
if (safePos != null && safePos.y >= minY && safePos.y <= maxY) {
return safePos;
}
}
}
}
}
return null;
}
private static void addArrivalCandidate(Set<BlockPos> candidates, @Nullable BlockPos pos) {
if (pos == null) {
return;
@@ -161,17 +274,26 @@ public final class AntTeleportHelper {
@Nullable
private static Vec3 tryFindSafeDismount(ServerPlayer player, ServerLevel destination, BlockPos candidate) {
Vec3 safePos = DismountHelper.findSafeDismountLocation(player.getType(), destination, candidate, true);
if (safePos != null) {
if (safePos != null && isValidArrivalPosition(destination, safePos)) {
return safePos;
}
for (Direction direction : Direction.Plane.HORIZONTAL) {
safePos = DismountHelper.findSafeDismountLocation(player.getType(), destination, candidate.relative(direction), true);
if (safePos != null) {
if (safePos != null && isValidArrivalPosition(destination, safePos)) {
return safePos;
}
}
return null;
}
private static boolean isValidArrivalPosition(ServerLevel destination, Vec3 safePos) {
if (destination.dimension() != AntarchySettings.termiteDestinationDimension()) {
return true;
}
BlockPos standPos = BlockPos.containing(safePos);
return !destination.getBlockState(standPos.below()).is(Blocks.BEDROCK);
}
}
@@ -1230,11 +1230,11 @@ public abstract class BaseAntEntity extends Animal implements GeoEntity {
}
private boolean shouldPlayAttackAnimation() {
if (!(this instanceof RedAntEntity)) {
return false;
}
return this.shouldUseBiteAnimation() && this.getBiteAnimationTicks() > 0;
}
return this.getBiteAnimationTicks() > 0;
protected boolean shouldUseBiteAnimation() {
return this instanceof RedAntEntity;
}
private int getBiteAnimationTicks() {
@@ -1245,6 +1245,10 @@ public abstract class BaseAntEntity extends Animal implements GeoEntity {
this.entityData.set(BITE_ANIM_TICKS, Math.max(0, ticks));
}
protected final void triggerBiteAnimation(int ticks) {
this.setBiteAnimationTicks(ticks);
}
private void syncAttribute(@Nullable AttributeInstance attributeInstance, double desiredValue, boolean rescaleCurrentHealth) {
if (attributeInstance == null || Math.abs(attributeInstance.getBaseValue() - desiredValue) < ATTRIBUTE_EPSILON) {
return;
@@ -1,9 +1,11 @@
package com.craisinlord.antarchy.content.entity.ant;
import com.craisinlord.antarchy.content.AntarchySoundEvents;
import com.craisinlord.antarchy.content.AntarchyTags;
import com.craisinlord.antarchy.config.AntarchySettings;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Vec3i;
import net.minecraft.resources.ResourceKey;
import net.minecraft.tags.TagKey;
import net.minecraft.world.entity.EntityType;
@@ -17,10 +19,22 @@ import org.jetbrains.annotations.Nullable;
import software.bernie.geckolib.animatable.GeoEntity;
public class TermiteEntity extends BaseAntEntity implements GeoEntity {
private static final int WOOD_SEARCH_INTERVAL_TICKS = 10;
private static final int WOOD_BITE_ANIMATION_TICKS = 8;
private static final int WOOD_SEARCH_INTERVAL_TICKS = 20;
private static final int WOOD_REPATH_INTERVAL_TICKS = 6;
private static final int WOOD_SEARCH_RADIUS_HORIZONTAL = 12;
private static final int WOOD_SEARCH_RADIUS_VERTICAL = 4;
private static final Vec3i[] IMMEDIATE_WOOD_OFFSETS = {
new Vec3i(0, 1, 0),
new Vec3i(0, 0, -1),
new Vec3i(0, 0, 1),
new Vec3i(1, 0, 0),
new Vec3i(-1, 0, 0),
new Vec3i(0, 1, -1),
new Vec3i(0, 1, 1),
new Vec3i(1, 1, 0),
new Vec3i(-1, 1, 0),
};
@Nullable
private BlockPos targetWoodPos;
private int nextWoodSearchTick;
@@ -79,13 +93,8 @@ public class TermiteEntity extends BaseAntEntity implements GeoEntity {
}
@Override
protected boolean canGroupWithNestmates() {
return false;
}
@Override
protected boolean canMarch() {
return false;
protected boolean shouldUseBiteAnimation() {
return true;
}
@Override
@@ -119,37 +128,25 @@ public class TermiteEntity extends BaseAntEntity implements GeoEntity {
}
private boolean tryEatNearbyWood() {
for (BlockPos targetPos : this.getImmediateWoodChecks()) {
BlockPos basePos = this.blockPosition();
for (Vec3i offset : IMMEDIATE_WOOD_OFFSETS) {
BlockPos targetPos = basePos.offset(offset);
BlockState targetState = this.level().getBlockState(targetPos);
if (!targetState.is(AntarchyTags.Blocks.TERMITE_FOODS)) {
continue;
}
SoundType soundType = targetState.getSoundType();
this.triggerBiteAnimation(WOOD_BITE_ANIMATION_TICKS);
this.level().destroyBlock(targetPos, false, this);
this.level().playSound(null, targetPos, soundType.getBreakSound(), this.getSoundSource(), 0.7F, 1.1F);
this.playSound(AntarchySoundEvents.ANT_BITE.get(), 0.35F, 0.95F + this.random.nextFloat() * 0.1F);
return true;
}
return false;
}
private BlockPos[] getImmediateWoodChecks() {
BlockPos basePos = this.blockPosition();
BlockPos abovePos = basePos.above();
return new BlockPos[] {
basePos.above(),
basePos.north(),
basePos.south(),
basePos.east(),
basePos.west(),
abovePos.north(),
abovePos.south(),
abovePos.east(),
abovePos.west()
};
}
@Nullable
private BlockPos getCurrentWoodTarget() {
if (this.targetWoodPos != null && this.isValidWoodTarget(this.targetWoodPos)) {
@@ -168,20 +165,52 @@ public class TermiteEntity extends BaseAntEntity implements GeoEntity {
@Nullable
private BlockPos findClosestWoodTarget() {
BlockPos origin = this.blockPosition();
int ox = origin.getX();
int oy = origin.getY();
int oz = origin.getZ();
BlockPos bestPos = null;
double bestDistance = Double.MAX_VALUE;
double bestDistSq = Double.MAX_VALUE;
BlockPos.MutableBlockPos candidate = new BlockPos.MutableBlockPos();
for (BlockPos pos : BlockPos.betweenClosed(
origin.offset(-WOOD_SEARCH_RADIUS_HORIZONTAL, -WOOD_SEARCH_RADIUS_VERTICAL, -WOOD_SEARCH_RADIUS_HORIZONTAL),
origin.offset(WOOD_SEARCH_RADIUS_HORIZONTAL, WOOD_SEARCH_RADIUS_VERTICAL + 2, WOOD_SEARCH_RADIUS_HORIZONTAL))) {
if (!this.isValidWoodTarget(pos)) {
continue;
for (int r = 0; r <= WOOD_SEARCH_RADIUS_HORIZONTAL; r++) {
if (bestPos != null && bestDistSq <= (double)(r * r)) {
break;
}
double distance = pos.distSqr(origin);
if (distance < bestDistance) {
bestDistance = distance;
bestPos = pos.immutable();
for (int dy = -WOOD_SEARCH_RADIUS_VERTICAL; dy <= WOOD_SEARCH_RADIUS_VERTICAL + 2; dy++) {
if (r == 0) {
candidate.set(ox, oy + dy, oz);
if (this.isValidWoodTarget(candidate)) {
double d = candidate.distSqr(origin);
if (d < bestDistSq) { bestDistSq = d; bestPos = candidate.immutable(); }
}
continue;
}
// North/South faces (dz = ±r)
for (int dx = -r; dx <= r; dx++) {
candidate.set(ox + dx, oy + dy, oz - r);
if (this.isValidWoodTarget(candidate)) {
double d = candidate.distSqr(origin);
if (d < bestDistSq) { bestDistSq = d; bestPos = candidate.immutable(); }
}
candidate.set(ox + dx, oy + dy, oz + r);
if (this.isValidWoodTarget(candidate)) {
double d = candidate.distSqr(origin);
if (d < bestDistSq) { bestDistSq = d; bestPos = candidate.immutable(); }
}
}
// East/West faces (dx = ±r, corners already covered above)
for (int dz = -r + 1; dz <= r - 1; dz++) {
candidate.set(ox - r, oy + dy, oz + dz);
if (this.isValidWoodTarget(candidate)) {
double d = candidate.distSqr(origin);
if (d < bestDistSq) { bestDistSq = d; bestPos = candidate.immutable(); }
}
candidate.set(ox + r, oy + dy, oz + dz);
if (this.isValidWoodTarget(candidate)) {
double d = candidate.distSqr(origin);
if (d < bestDistSq) { bestDistSq = d; bestPos = candidate.immutable(); }
}
}
}
}
@@ -215,9 +244,10 @@ public class TermiteEntity extends BaseAntEntity implements GeoEntity {
}
private boolean isWalkableStandPos(BlockPos pos) {
BlockState floorState = this.level().getBlockState(pos.below());
BlockPos floor = pos.below();
BlockState floorState = this.level().getBlockState(floor);
return this.level().getBlockState(pos).isAir()
&& this.level().getBlockState(pos.above()).isAir()
&& floorState.isFaceSturdy(this.level(), pos.below(), Direction.UP);
&& floorState.isFaceSturdy(this.level(), floor, Direction.UP);
}
}
@@ -604,7 +604,7 @@ public class BasiliskEntity extends Monster implements GeoEntity {
BasiliskEntity.this.getLookControl().setLookAt(target, 30.0F, 30.0F);
}
// super handles path recalc and cooldown timer — always run it
// super handles path recalc and cooldown timer
super.tick();
if (BasiliskEntity.this.hissChargeTimer > 0 || BasiliskEntity.this.attackAnimTicks > 0) {
@@ -83,6 +83,7 @@ public class BrutalflyEntity extends Monster implements GeoEntity {
private static final int SWIPE_TICKS = 18;
private static final float SWIPE_KNOCKBACK = 2.8F;
private static final int COCOON_HIT_THRESHOLD = 3;
private static final int COCOON_HIT_COOLDOWN_TICKS = 60;
private static final int COCOON_HIT_ANIM_TICKS = 30;
private static final int COCOON_HATCH_ANIM_TICKS = 40;
private static final int COCOON_IDLE_ANIM_TICKS = 70;
@@ -111,6 +112,7 @@ public class BrutalflyEntity extends Monster implements GeoEntity {
@Nullable
BlockPos anchorPos;
private int cocoonHits;
private int cocoonHitCooldownTicks;
private boolean isHatching;
private int cocoonAnimTicks;
private boolean pendingHatchAnim;
@@ -261,6 +263,10 @@ public class BrutalflyEntity extends Monster implements GeoEntity {
}
private void tickCocoon() {
if (this.cocoonHitCooldownTicks > 0) {
this.cocoonHitCooldownTicks--;
}
// Pin position to hang from anchor
if (this.anchorPos != null) {
this.setPos(this.anchorPos.getX() + 0.5, this.anchorPos.getY() - 1.5, this.anchorPos.getZ() + 0.5);
@@ -319,6 +325,8 @@ public class BrutalflyEntity extends Monster implements GeoEntity {
this.entityData.set(IS_COCOONED, false);
this.isHatching = false;
this.anchorPos = null;
this.cocoonHits = 0;
this.cocoonHitCooldownTicks = 0;
this.refreshDimensions();
this.setAnimationState(ANIM_FLY);
Player nearbyPlayer = this.level().getNearestPlayer(this, 48.0D);
@@ -344,6 +352,10 @@ public class BrutalflyEntity extends Monster implements GeoEntity {
this.anchorPos = anchor;
this.refreshDimensions();
if (cocooned) {
this.cocoonHits = 0;
this.cocoonHitCooldownTicks = 0;
this.isHatching = false;
this.pendingHatchAnim = false;
this.setAnimationState(ANIM_COCOON);
}
}
@@ -355,9 +367,10 @@ public class BrutalflyEntity extends Monster implements GeoEntity {
}
if (this.isCocooned() && !this.isHatching) {
if (this.level().isClientSide || this.hurtTime > 0) return false;
if (this.level().isClientSide || this.cocoonHitCooldownTicks > 0) return false;
this.hurtTime = this.hurtDuration;
this.cocoonHits++;
this.cocoonHitCooldownTicks = COCOON_HIT_COOLDOWN_TICKS;
if (this.cocoonHits >= COCOON_HIT_THRESHOLD) {
this.startHatching();
} else {
@@ -706,6 +719,7 @@ public class BrutalflyEntity extends Monster implements GeoEntity {
super.addAdditionalSaveData(tag);
tag.putBoolean("IsCocooned", this.isCocooned());
tag.putInt("CocoonHits", this.cocoonHits);
tag.putInt("CocoonHitCooldown", this.cocoonHitCooldownTicks);
if (this.anchorPos != null) {
tag.putInt("AnchorX", this.anchorPos.getX());
tag.putInt("AnchorY", this.anchorPos.getY());
@@ -723,6 +737,7 @@ public class BrutalflyEntity extends Monster implements GeoEntity {
}
this.setCocooned(true, anchor);
this.cocoonHits = tag.getInt("CocoonHits");
this.cocoonHitCooldownTicks = tag.getInt("CocoonHitCooldown");
}
}
@@ -55,6 +55,7 @@ import software.bernie.geckolib.animation.AnimationState;
import software.bernie.geckolib.util.GeckoLibUtil;
import java.util.EnumSet;
import java.util.Optional;
import java.util.UUID;
public class FlyingSquirrelEntity extends TamableAnimal implements GeoEntity {
@@ -71,6 +72,8 @@ public class FlyingSquirrelEntity extends TamableAnimal implements GeoEntity {
SynchedEntityData.defineId(FlyingSquirrelEntity.class, EntityDataSerializers.BOOLEAN);
private static final EntityDataAccessor<Integer> TEXTURE_VARIANT =
SynchedEntityData.defineId(FlyingSquirrelEntity.class, EntityDataSerializers.INT);
private static final EntityDataAccessor<Optional<UUID>> SHOULDER_PLAYER_DATA =
SynchedEntityData.defineId(FlyingSquirrelEntity.class, EntityDataSerializers.OPTIONAL_UUID);
private static final String PICKUP_ANIM_TICKS_KEY = "PickupAnimTicks";
private static final String TEXTURE_VARIANT_KEY = "TextureVariant";
@@ -227,6 +230,7 @@ public class FlyingSquirrelEntity extends TamableAnimal implements GeoEntity {
builder.define(CLIMBING, false);
builder.define(PICKING_UP_NUT, false);
builder.define(TEXTURE_VARIANT, TEXTURE_VARIANT_REGULAR);
builder.define(SHOULDER_PLAYER_DATA, Optional.empty());
}
@Override
@@ -376,13 +380,19 @@ public class FlyingSquirrelEntity extends TamableAnimal implements GeoEntity {
}
if (this.isOnShoulder()) {
if (!(this.getVehicle() instanceof Player shoulderPlayer)
|| !shoulderPlayer.isAlive()) {
Player shoulderPlayer = this.getShoulderPlayer();
if (shoulderPlayer == null || !shoulderPlayer.isAlive()) {
this.dismountShoulder();
} else if (this.shoulderSneakGraceTicks <= 0 && shoulderPlayer.isShiftKeyDown()) {
this.glideOffShoulder();
} else {
Vec3 shoulderPos = this.getShoulderRidePos(shoulderPlayer);
this.setPos(shoulderPos.x, shoulderPos.y, shoulderPos.z);
this.setDeltaMovement(Vec3.ZERO);
this.setYRot(shoulderPlayer.getYHeadRot());
this.setXRot(shoulderPlayer.getXRot());
this.yBodyRot = shoulderPlayer.getYHeadRot();
this.yHeadRot = shoulderPlayer.getYHeadRot();
this.fallDistance = 0.0F;
}
this.setNoGravity(true);
@@ -434,22 +444,25 @@ public class FlyingSquirrelEntity extends TamableAnimal implements GeoEntity {
this.tickIdleStallLogging(horizontalSpeed);
}
if (this.isOnShoulder() && this.level().isClientSide) {
Player shoulderPlayer = this.getShoulderPlayer();
if (shoulderPlayer != null) {
Vec3 shoulderPos = this.getShoulderRidePos(shoulderPlayer);
this.setPos(shoulderPos.x, shoulderPos.y, shoulderPos.z);
this.setDeltaMovement(Vec3.ZERO);
this.setYRot(shoulderPlayer.getYHeadRot());
this.setXRot(shoulderPlayer.getXRot());
this.yBodyRot = shoulderPlayer.getYHeadRot();
this.yHeadRot = shoulderPlayer.getYHeadRot();
this.fallDistance = 0.0F;
}
}
this.setNoGravity(this.isGliding() || this.isClimbingTrunk() || this.isClimbingToShoulder() || this.isOnShoulder());
}
@Override
public void rideTick() {
super.rideTick();
if (this.getVehicle() instanceof Player player) {
Vec3 shoulderPos = this.getShoulderRidePos(player);
this.setPos(shoulderPos.x, shoulderPos.y, shoulderPos.z);
this.setDeltaMovement(Vec3.ZERO);
this.setYRot(player.getYHeadRot());
this.setXRot(player.getXRot());
this.yBodyRot = player.getYHeadRot();
this.yHeadRot = player.getYHeadRot();
this.fallDistance = 0.0F;
}
}
@Override
@@ -703,7 +716,14 @@ public class FlyingSquirrelEntity extends TamableAnimal implements GeoEntity {
}
public boolean isOnShoulder() {
return this.getVehicle() instanceof Player;
return this.entityData.get(SHOULDER_PLAYER_DATA).isPresent();
}
@Nullable
private Player getShoulderPlayer() {
return this.entityData.get(SHOULDER_PLAYER_DATA)
.map(uuid -> this.level().getPlayerByUUID(uuid))
.orElse(null);
}
public boolean isClimbingToShoulder() {
@@ -725,10 +745,9 @@ public class FlyingSquirrelEntity extends TamableAnimal implements GeoEntity {
}
private void dismountShoulder() {
Entity vehicle = this.getVehicle();
if (vehicle != null) {
this.stopRiding();
Vec3 dismountPos = vehicle.position().add(0.0D, 0.1D, 0.0D);
Player shoulderPlayer = this.getShoulderPlayer();
if (shoulderPlayer != null) {
Vec3 dismountPos = shoulderPlayer.position().add(0.0D, 0.1D, 0.0D);
this.setPos(dismountPos.x, dismountPos.y, dismountPos.z);
}
this.clearShoulderMountState();
@@ -737,6 +756,7 @@ public class FlyingSquirrelEntity extends TamableAnimal implements GeoEntity {
}
private void clearShoulderMountState() {
this.entityData.set(SHOULDER_PLAYER_DATA, Optional.empty());
this.shoulderPlayerId = null;
this.shoulderClimbStartPos = null;
this.climbingToShoulderTicks = 0;
@@ -746,16 +766,15 @@ public class FlyingSquirrelEntity extends TamableAnimal implements GeoEntity {
}
private void glideOffShoulder() {
Entity vehicle = this.getVehicle();
if (!(vehicle instanceof Player player)) {
Player player = this.getShoulderPlayer();
if (player == null) {
this.dismountShoulder();
return;
}
Vec3 launchPos = this.getShoulderRidePos(player);
this.stopRiding();
this.setPos(launchPos.x, launchPos.y, launchPos.z);
this.clearShoulderMountState();
this.setPos(launchPos.x, launchPos.y, launchPos.z);
this.fallDistance = 0.0F;
BlockPos glideTarget = this.findFallGlideTarget();
@@ -815,7 +834,7 @@ public class FlyingSquirrelEntity extends TamableAnimal implements GeoEntity {
this.shoulderPlayerId = null;
this.shoulderSneakGraceTicks = SHOULDER_SNEAK_GRACE_TICKS;
this.setClimbingTrunk(false);
this.startRiding(player, true);
this.entityData.set(SHOULDER_PLAYER_DATA, Optional.of(player.getUUID()));
this.setDeltaMovement(Vec3.ZERO);
this.fallDistance = 0.0F;
}
@@ -1118,11 +1137,16 @@ public class FlyingSquirrelEntity extends TamableAnimal implements GeoEntity {
}
public void collectNut(ItemEntity itemEntity) {
ItemStack itemStack = itemEntity.getItem();
if (itemStack.isEmpty()) {
itemEntity.discard();
return;
}
this.startPickupAnimation();
ItemStack particleStack = itemEntity.getItem().copy();
ItemStack particleStack = itemStack.copy();
particleStack.setCount(1);
ItemStack itemStack = itemEntity.getItem();
itemStack.shrink(1);
if (itemStack.isEmpty()) {
itemEntity.discard();
@@ -1648,6 +1672,11 @@ public class FlyingSquirrelEntity extends TamableAnimal implements GeoEntity {
@Override
public void tick() {
if (this.targetNut != null) {
if (!this.targetNut.isAlive() || this.targetNut.getItem().isEmpty()) {
this.targetNut = null;
return;
}
FlyingSquirrelEntity.this.getLookControl().setLookAt(this.targetNut, 30.0F, 30.0F);
if (--this.repathDelay <= 0 && !FlyingSquirrelEntity.this.isGliding()) {
@@ -5,8 +5,9 @@ import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.content.AntarchySoundEvents;
import com.craisinlord.antarchy.content.damage.AntarchyDamageSources;
import com.craisinlord.antarchy.content.entity.MissileSquidEntity;
import com.craisinlord.antarchy.content.entity.OctopusBombEntity;
import com.craisinlord.antarchy.content.damage.AntarchyDamageTypes;
import java.util.List;
import java.util.Objects;
import net.minecraft.core.BlockPos;
@@ -68,24 +69,28 @@ public class KrakenEntity extends Monster implements GeoEntity {
private static final String ATTACK_COOLDOWN_KEY = "AttackCooldown";
private static final String ACTION_TICKS_KEY = "ActionTicks";
private static final String PHASE_TRANSITION_TICKS_KEY = "PhaseTransitionTicks";
private static final String NEXT_ATTACK_INDEX_KEY = "NextAttackIndex";
private static final String SLAM_COOLDOWN_KEY = "SlamCooldown";
private static final String PHASE_SUMMON_COOLDOWN_KEY = "PhaseSummonCooldown";
private static final String LAST_ATTACK_STATE_KEY = "LastAttackState";
private static final String AGGRO_TRIGGERED_KEY = "AggroTriggered";
private static final String GRAB_TARGET_KEY = "GrabTarget";
private static final double BOSS_BAR_RANGE = 40.0D;
private static final int ATTACK_NONE = 0;
private static final int ATTACK_GRAB = 1;
private static final int ATTACK_SWIPE = 2;
private static final int ATTACK_SLAM = 3;
private static final int ANIM_IDLE = 0;
private static final int ANIM_IDLE_TWO = 1;
private static final int ANIM_SWIM = 2;
private static final int ANIM_SWIPE = 3;
private static final int ANIM_GRAB = 4;
private static final int ATTACK_SWING_LEFT = 2;
private static final int ATTACK_SWING_RIGHT = 3;
// Synced animation state values
private static final int ANIM_NONE = 0;
private static final int ANIM_CRY = 1;
private static final int ANIM_GRAB = 2;
private static final int ANIM_SWING_LEFT = 3;
private static final int ANIM_SWING_RIGHT = 4;
private static final int ANIM_DEATH = 5;
private static final int DEATH_ANIM_TICKS = 60;
private static final ResourceLocation PHASE_TWO_SPEED_ID = ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "kraken_phase_two_speed");
private static final ResourceLocation PHASE_TWO_FLYING_SPEED_ID = ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "kraken_phase_two_flying_speed");
private static final ResourceLocation PHASE_TWO_DAMAGE_ID = ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "kraken_phase_two_damage");
@@ -96,21 +101,21 @@ public class KrakenEntity extends Monster implements GeoEntity {
private static final AttributeModifier PHASE_TWO_DAMAGE = new AttributeModifier(PHASE_TWO_DAMAGE_ID, PHASE_TWO_DAMAGE_BONUS, AttributeModifier.Operation.ADD_VALUE);
private static final RawAnimation IDLE_ANIM = RawAnimation.begin().thenLoop("idle");
private static final RawAnimation SWIM_ANIM = RawAnimation.begin().thenLoop("swim");
private static final RawAnimation SWIPE_ANIM = RawAnimation.begin().thenPlay("Swipe");
private static final RawAnimation GRAB_ANIM = RawAnimation.begin().thenPlay("Grab");
private static final RawAnimation DEATH_ANIM = RawAnimation.begin().thenPlay("Death");
private static final RawAnimation CRY_ANIM = RawAnimation.begin().thenPlay("cry");
private static final RawAnimation GRAB_ANIM = RawAnimation.begin().thenPlay("grab");
private static final RawAnimation SWING_LEFT_ANIM = RawAnimation.begin().thenPlay("swing_left");
private static final RawAnimation SWING_RIGHT_ANIM = RawAnimation.begin().thenPlay("swing_right");
private static final RawAnimation DEATH_ANIM = RawAnimation.begin().thenPlay("death");
private final ServerBossEvent bossEvent = new ServerBossEvent(Component.literal("The Kraken"), BossEvent.BossBarColor.BLUE, BossEvent.BossBarOverlay.PROGRESS);
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
@Nullable
private Vec3 patrolTarget;
private int patrolRetargetTicks;
private int attackCooldown;
private int actionTicks;
private int phaseTransitionTicks;
private int nextAttackIndex;
private int slamCooldown;
private int phaseSummonCooldown;
private int lastAttackState;
private boolean aggroTriggered;
@@ -121,6 +126,7 @@ public class KrakenEntity extends Monster implements GeoEntity {
private int strafeRetargetTicks;
private float orbitDirection = 1.0F;
private boolean stormActive;
public KrakenEntity(EntityType<? extends Monster> entityType, Level level) {
super(entityType, level);
this.moveControl = new FlyingMoveControl(this, 20, true);
@@ -149,7 +155,7 @@ public class KrakenEntity extends Monster implements GeoEntity {
builder.define(ATTACK_STATE, ATTACK_NONE);
builder.define(ROARING, false);
builder.define(PHASE_TWO, false);
builder.define(ANIMATION_STATE, ANIM_IDLE);
builder.define(ANIMATION_STATE, ANIM_NONE);
}
@Override
@@ -165,17 +171,26 @@ public class KrakenEntity extends Monster implements GeoEntity {
@Override
public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
controllers.add(new AnimationController<>(this, "main_controller", 0, this::mainAnimController)
// idle_controller always loops idle; action_controller layers on top when active
controllers.add(new AnimationController<>(this, "idle_controller", 0, state -> {
if (this.getAnimationState() == ANIM_DEATH) {
return PlayState.STOP;
}
state.setAndContinue(IDLE_ANIM);
return PlayState.CONTINUE;
}).setSoundKeyframeHandler(new AutoPlayingSoundKeyframeHandler<>()));
controllers.add(new AnimationController<>(this, "action_controller", 0, this::actionAnimController)
.setSoundKeyframeHandler(new AutoPlayingSoundKeyframeHandler<>()));
}
private PlayState mainAnimController(AnimationState<KrakenEntity> state) {
private PlayState actionAnimController(AnimationState<KrakenEntity> state) {
return switch (this.getAnimationState()) {
case ANIM_CRY -> state.setAndContinue(CRY_ANIM);
case ANIM_GRAB -> state.setAndContinue(GRAB_ANIM);
case ANIM_SWIPE -> state.setAndContinue(SWIPE_ANIM);
case ANIM_SWIM -> state.setAndContinue(SWIM_ANIM);
case ANIM_SWING_LEFT -> state.setAndContinue(SWING_LEFT_ANIM);
case ANIM_SWING_RIGHT -> state.setAndContinue(SWING_RIGHT_ANIM);
case ANIM_DEATH -> state.setAndContinue(DEATH_ANIM);
default -> state.setAndContinue(IDLE_ANIM);
default -> PlayState.STOP;
};
}
@@ -221,9 +236,6 @@ public class KrakenEntity extends Monster implements GeoEntity {
if (this.attackCooldown > 0) {
this.attackCooldown--;
}
if (this.slamCooldown > 0) {
this.slamCooldown--;
}
if (this.phaseSummonCooldown > 0) {
this.phaseSummonCooldown--;
}
@@ -307,7 +319,8 @@ public class KrakenEntity extends Monster implements GeoEntity {
if (source.is(DamageTypeTags.IS_FIRE)
|| source.is(DamageTypes.LIGHTNING_BOLT)
|| source.is(AntarchyDamageTypes.KRAKEN_LIGHTNING)
|| source.getEntity() instanceof MissileSquidEntity) {
|| source.getEntity() instanceof MissileSquidEntity
|| source.getEntity() instanceof OctopusBombEntity) {
return false;
}
@@ -351,8 +364,6 @@ public class KrakenEntity extends Monster implements GeoEntity {
tag.putInt(ATTACK_COOLDOWN_KEY, this.attackCooldown);
tag.putInt(ACTION_TICKS_KEY, this.actionTicks);
tag.putInt(PHASE_TRANSITION_TICKS_KEY, this.phaseTransitionTicks);
tag.putInt(NEXT_ATTACK_INDEX_KEY, this.nextAttackIndex);
tag.putInt(SLAM_COOLDOWN_KEY, this.slamCooldown);
tag.putInt(PHASE_SUMMON_COOLDOWN_KEY, this.phaseSummonCooldown);
tag.putInt(LAST_ATTACK_STATE_KEY, this.lastAttackState);
tag.putBoolean(AGGRO_TRIGGERED_KEY, this.aggroTriggered);
@@ -374,8 +385,6 @@ public class KrakenEntity extends Monster implements GeoEntity {
this.attackCooldown = tag.getInt(ATTACK_COOLDOWN_KEY);
this.actionTicks = tag.getInt(ACTION_TICKS_KEY);
this.phaseTransitionTicks = tag.getInt(PHASE_TRANSITION_TICKS_KEY);
this.nextAttackIndex = tag.getInt(NEXT_ATTACK_INDEX_KEY);
this.slamCooldown = tag.getInt(SLAM_COOLDOWN_KEY);
this.phaseSummonCooldown = tag.getInt(PHASE_SUMMON_COOLDOWN_KEY);
this.lastAttackState = tag.contains(LAST_ATTACK_STATE_KEY) ? tag.getInt(LAST_ATTACK_STATE_KEY) : ATTACK_NONE;
this.aggroTriggered = tag.getBoolean(AGGRO_TRIGGERED_KEY);
@@ -384,7 +393,7 @@ public class KrakenEntity extends Monster implements GeoEntity {
this.entityData.set(ROARING, tag.getBoolean("Roaring"));
this.entityData.set(ATTACK_STATE, tag.getInt("AttackState"));
this.entityData.set(PHASE_TWO, tag.getBoolean("PhaseTwo"));
this.entityData.set(ANIMATION_STATE, tag.contains("AnimationState") ? tag.getInt("AnimationState") : ANIM_IDLE);
this.entityData.set(ANIMATION_STATE, tag.contains("AnimationState") ? tag.getInt("AnimationState") : ANIM_NONE);
this.lightningStrikeCooldown = tag.getInt("LightningStrikeCooldown");
this.lightningAmbientCooldown = tag.getInt("LightningAmbientCooldown");
this.stormActive = false;
@@ -394,6 +403,17 @@ public class KrakenEntity extends Monster implements GeoEntity {
this.updateAnimationState();
}
@Override
public void startSeenByPlayer(net.minecraft.server.level.ServerPlayer player) {
super.startSeenByPlayer(player);
}
@Override
public void stopSeenByPlayer(net.minecraft.server.level.ServerPlayer player) {
super.stopSeenByPlayer(player);
this.bossEvent.removePlayer(player);
}
@Override
public void remove(Entity.RemovalReason reason) {
this.clearKrakenStorm();
@@ -475,7 +495,7 @@ public class KrakenEntity extends Monster implements GeoEntity {
this.getLookControl().setLookAt(target, 25.0F, 20.0F);
if (!this.spawnedPhaseMinions && this.phaseTransitionTicks <= 40) {
this.spawnPhaseMinions(1 + this.random.nextInt(2), true, target);
this.spawnMinions(1 + this.random.nextInt(2), target);
this.spawnedPhaseMinions = true;
}
@@ -499,8 +519,8 @@ public class KrakenEntity extends Monster implements GeoEntity {
switch (this.getAttackState()) {
case ATTACK_GRAB -> this.tickGrabAttack(target);
case ATTACK_SWIPE -> this.tickSwipeAttack();
case ATTACK_SLAM -> this.tickSlamAttack();
case ATTACK_SWING_LEFT -> this.tickSwingAttack(false);
case ATTACK_SWING_RIGHT -> this.tickSwingAttack(true);
default -> this.finishAttack();
}
}
@@ -512,12 +532,9 @@ public class KrakenEntity extends Monster implements GeoEntity {
if (attackState == ATTACK_GRAB) {
this.actionTicks = 55;
this.playSound(AntarchySoundEvents.KRAKEN_ATTACK.get(), 1.45F, 0.9F);
} else if (attackState == ATTACK_SWIPE) {
} else if (attackState == ATTACK_SWING_LEFT || attackState == ATTACK_SWING_RIGHT) {
this.actionTicks = 40;
this.playSound(AntarchySoundEvents.KRAKEN_SPIN.get(), 1.3F, 0.95F);
} else if (attackState == ATTACK_SLAM) {
this.actionTicks = 34;
this.playSound(AntarchySoundEvents.KRAKEN_ATTACK.get(), 1.5F, 0.82F);
}
}
@@ -545,19 +562,10 @@ public class KrakenEntity extends Monster implements GeoEntity {
}
}
private void tickSwipeAttack() {
// swingRight=true means ATTACK_SWING_RIGHT, which knocks players to the Kraken's LEFT
private void tickSwingAttack(boolean swingRight) {
if (this.actionTicks == 10) {
this.performSwipe();
}
if (--this.actionTicks <= 0) {
this.finishAttack();
}
}
private void tickSlamAttack() {
if (this.actionTicks == 8) {
this.performTentacleSlam();
this.performSwing(swingRight);
}
if (--this.actionTicks <= 0) {
@@ -566,15 +574,12 @@ public class KrakenEntity extends Monster implements GeoEntity {
}
private void finishAttack() {
int finishedAttack = this.getAttackState();
int finished = this.getAttackState();
this.setAttackState(ATTACK_NONE);
this.actionTicks = 0;
this.attackCooldown = this.isPhaseTwo() ? 12 : 18;
this.grabbedTargetId = -1;
this.lastAttackState = finishedAttack;
if (finishedAttack == ATTACK_SLAM) {
this.slamCooldown = this.isPhaseTwo() ? 110 : 150;
}
this.lastAttackState = finished;
this.updateAnimationState();
}
@@ -614,96 +619,57 @@ public class KrakenEntity extends Monster implements GeoEntity {
}
private int chooseNextAttack(LivingEntity target) {
int[] attackRotation = new int[] {ATTACK_GRAB, ATTACK_SWIPE, ATTACK_SLAM};
int[] weightedAttacks = new int[attackRotation.length];
int[] weights = new int[attackRotation.length];
int eligibleCount = 0;
int totalWeight = 0;
double distSqr = this.distanceToSqr(target);
boolean canGrab = distSqr <= 64.0D;
boolean canSwing = distSqr <= 81.0D;
for (int attack : attackRotation) {
if (!this.canUseAttack(attack, target)) {
continue;
}
int weight = this.getAttackWeight(attack);
if (attack == this.lastAttackState) {
weight = Math.max(1, weight / 3);
}
if (weight <= 0) {
continue;
}
weightedAttacks[eligibleCount] = attack;
weights[eligibleCount] = weight;
totalWeight += weight;
eligibleCount++;
}
if (eligibleCount == 0) {
if (!canGrab && !canSwing) {
return ATTACK_NONE;
}
int roll = this.random.nextInt(totalWeight);
for (int i = 0; i < eligibleCount; i++) {
roll -= weights[i];
if (roll < 0) {
return weightedAttacks[i];
}
int grabWeight = canGrab ? (this.lastAttackState == ATTACK_GRAB ? 1 : 4) : 0;
int swingWeight = canSwing ? (this.isSwingAttack(this.lastAttackState) ? 1 : 5) : 0;
int total = grabWeight + swingWeight;
if (total == 0) {
return ATTACK_NONE;
}
return weightedAttacks[eligibleCount - 1];
}
private int getAttackWeight(int attackState) {
return switch (attackState) {
case ATTACK_GRAB -> 4;
case ATTACK_SWIPE -> 5;
case ATTACK_SLAM -> this.isPhaseTwo() ? 1 : 0;
default -> 0;
};
}
private void tickPhaseTwoPressure(LivingEntity target) {
if (!this.isPhaseTwo() || this.phaseSummonCooldown > 0) {
return;
int roll = this.random.nextInt(total);
if (roll < grabWeight) {
return ATTACK_GRAB;
}
if (this.countNearbyMissileSquids() >= 12) {
this.phaseSummonCooldown = 40;
return;
}
this.spawnPhaseMinions(1 + this.random.nextInt(2), true, target);
this.phaseSummonCooldown = 90 + this.random.nextInt(50);
return this.resolveSwingDirection(target);
}
private int countNearbyMissileSquids() {
return this.level().getEntitiesOfClass(
MissileSquidEntity.class,
this.getBoundingBox().inflate(28.0D),
squid -> squid.isAlive()
).size();
private boolean isSwingAttack(int attackState) {
return attackState == ATTACK_SWING_LEFT || attackState == ATTACK_SWING_RIGHT;
}
private boolean canUseAttack(int attackState, LivingEntity target) {
double distanceToTarget = this.distanceToSqr(target);
return switch (attackState) {
case ATTACK_GRAB -> distanceToTarget <= 64.0D;
case ATTACK_SWIPE -> distanceToTarget <= 81.0D && this.isTargetInFront(target, 0.1D);
case ATTACK_SLAM -> this.slamCooldown <= 0 && distanceToTarget <= 144.0D;
default -> false;
};
}
private boolean isTargetInFront(LivingEntity target, double minimumDot) {
// Determines left vs right based on which side the target is on relative to the Kraken.
// Kraken's right vector is forward x world-up. If target is on the Kraken's right side,
// use swing_right (which knocks them left). If on the left, use swing_left (knocks them right).
private int resolveSwingDirection(LivingEntity target) {
Vec3 forward = this.getViewVector(1.0F);
Vec3 toTarget = target.position().subtract(this.position()).normalize();
return forward.dot(toTarget) >= minimumDot;
Vec3 right = forward.cross(new Vec3(0, 1, 0)).normalize();
Vec3 toTarget = target.position().subtract(this.position());
return right.dot(toTarget) > 0 ? ATTACK_SWING_RIGHT : ATTACK_SWING_LEFT;
}
private void performSwipe() {
private void performSwing(boolean swingRight) {
AABB hitBox = this.getBoundingBox().inflate(7.0D, 3.0D, 7.0D);
List<Player> players = this.level().getEntitiesOfClass(Player.class, hitBox, player -> player.isAlive() && this.isTargetInFront(player, -0.15D));
List<Player> players = this.level().getEntitiesOfClass(Player.class, hitBox, LivingEntity::isAlive);
float damage = (float) this.getAttributeValue(Attributes.ATTACK_DAMAGE);
Vec3 forward = this.getViewVector(1.0F);
// swingRight knocks to Kraken's left (-right), swingLeft knocks to Kraken's right (+right)
Vec3 right = forward.cross(new Vec3(0, 1, 0)).normalize();
Vec3 knockDir = swingRight ? right.scale(-1) : right;
for (Player player : players) {
this.hurtAndKnockback(player, (float)this.getAttributeValue(Attributes.ATTACK_DAMAGE), 1.75D, 0.45D);
if (!(this.level() instanceof ServerLevel serverLevel)) continue;
if (!player.hurt(AntarchyDamageSources.krakenMauling(serverLevel, this), damage)) continue;
player.push(knockDir.x * 1.75D, 0.4D, knockDir.z * 1.75D);
player.hurtMarked = true;
}
if (this.level() instanceof ServerLevel serverLevel) {
@@ -711,19 +677,6 @@ public class KrakenEntity extends Monster implements GeoEntity {
}
}
private void performTentacleSlam() {
AABB hitBox = this.getBoundingBox().inflate(8.5D, 2.5D, 8.5D);
List<Player> players = this.level().getEntitiesOfClass(Player.class, hitBox, LivingEntity::isAlive);
float damage = (float)this.getAttributeValue(Attributes.ATTACK_DAMAGE) * 0.75F;
for (Player player : players) {
this.hurtAndKnockback(player, damage, 1.35D, 0.62D);
}
if (this.level() instanceof ServerLevel serverLevel) {
serverLevel.sendParticles(ParticleTypes.SPLASH, this.getX(), this.getY() + 0.5D, this.getZ(), 36, 1.8D, 0.5D, 1.8D, 0.12D);
}
}
private void holdGrabbedTarget(LivingEntity target) {
Vec3 anchor = this.position()
.add(this.getViewVector(1.0F).normalize().scale(this.getBbWidth() * 0.65D))
@@ -738,14 +691,6 @@ public class KrakenEntity extends Monster implements GeoEntity {
target.hurtMarked = true;
}
private void hurtAndKnockback(LivingEntity target, float damage, double horizontalStrength, double verticalStrength) {
if (!(this.level() instanceof ServerLevel serverLevel) || !target.hurt(AntarchyDamageSources.krakenMauling(serverLevel, this), damage)) {
return;
}
this.knockAway(target, horizontalStrength, verticalStrength);
}
private void knockAway(LivingEntity target, double horizontalStrength, double verticalStrength) {
Vec3 direction = target.position().subtract(this.position());
Vec3 horizontal = new Vec3(direction.x, 0.0D, direction.z);
@@ -771,7 +716,25 @@ public class KrakenEntity extends Monster implements GeoEntity {
return entity instanceof LivingEntity livingEntity ? livingEntity : null;
}
private void spawnPhaseMinions(int desiredCount, boolean launched, @Nullable LivingEntity currentTarget) {
private void tickPhaseTwoPressure(LivingEntity target) {
if (!this.isPhaseTwo() || this.phaseSummonCooldown > 0) {
return;
}
if (this.countNearbyMinions() >= 12) {
this.phaseSummonCooldown = 40;
return;
}
this.spawnMinions(1 + this.random.nextInt(2), target);
this.phaseSummonCooldown = 90 + this.random.nextInt(50);
}
private int countNearbyMinions() {
net.minecraft.world.phys.AABB range = this.getBoundingBox().inflate(28.0D);
return this.level().getEntitiesOfClass(OctopusBombEntity.class, range, OctopusBombEntity::isAlive).size()
+ this.level().getEntitiesOfClass(MissileSquidEntity.class, range, MissileSquidEntity::isAlive).size();
}
private void spawnMinions(int desiredCount, @Nullable LivingEntity currentTarget) {
if (!(this.level() instanceof ServerLevel serverLevel)) {
return;
}
@@ -779,21 +742,23 @@ public class KrakenEntity extends Monster implements GeoEntity {
int spawned = 0;
for (int attempt = 0; attempt < desiredCount * 4 && spawned < desiredCount; attempt++) {
Vec3 spawnPos = this.findPhaseMinionSpawnPos(currentTarget);
MissileSquidEntity missileSquid = AntarchyObjects.MISSILE_SQUID.get().create(serverLevel);
if (missileSquid == null) {
continue;
}
Vec3 spawnPos = this.findBombSpawnPos(currentTarget);
boolean spawnBomb = this.random.nextBoolean();
missileSquid.setCanFly(true);
missileSquid.moveTo(spawnPos.x, spawnPos.y, spawnPos.z, this.getYRot(), 0.0F);
serverLevel.addFreshEntity(missileSquid);
if (launched) {
missileSquid.launchAsProjectile(this, this.createPhaseMinionLaunchVelocity(spawnPos, currentTarget));
} else if (currentTarget != null) {
missileSquid.setTarget(currentTarget);
if (spawnBomb) {
OctopusBombEntity bomb = AntarchyObjects.OCTOPUS_BOMB.get().create(serverLevel);
if (bomb == null) continue;
bomb.moveTo(spawnPos.x, spawnPos.y, spawnPos.z, this.getYRot(), 0.0F);
serverLevel.addFreshEntity(bomb);
bomb.launchAsProjectile(this, this.createBombLaunchVelocity(spawnPos, currentTarget));
serverLevel.sendParticles(ParticleTypes.BUBBLE, bomb.getX(), bomb.getY() + 0.4D, bomb.getZ(), 14, 0.3D, 0.25D, 0.3D, 0.03D);
} else {
MissileSquidEntity squid = AntarchyObjects.MISSILE_SQUID.get().create(serverLevel);
if (squid == null) continue;
squid.moveTo(spawnPos.x, spawnPos.y, spawnPos.z, this.getYRot(), 0.0F);
serverLevel.addFreshEntity(squid);
serverLevel.sendParticles(ParticleTypes.BUBBLE, squid.getX(), squid.getY() + 0.4D, squid.getZ(), 14, 0.3D, 0.25D, 0.3D, 0.03D);
}
serverLevel.sendParticles(ParticleTypes.BUBBLE, missileSquid.getX(), missileSquid.getY() + 0.4D, missileSquid.getZ(), 14, 0.3D, 0.25D, 0.3D, 0.03D);
spawned++;
}
@@ -802,7 +767,7 @@ public class KrakenEntity extends Monster implements GeoEntity {
}
}
private Vec3 findPhaseMinionSpawnPos(@Nullable LivingEntity currentTarget) {
private Vec3 findBombSpawnPos(@Nullable LivingEntity currentTarget) {
Vec3 anchor = currentTarget != null ? currentTarget.position() : this.position();
for (int attempt = 0; attempt < 18; attempt++) {
double angle = this.random.nextDouble() * Mth.TWO_PI;
@@ -821,7 +786,7 @@ public class KrakenEntity extends Monster implements GeoEntity {
return this.position().add(0.0D, this.getBbHeight() * 0.6D, 0.0D);
}
private Vec3 createPhaseMinionLaunchVelocity(Vec3 spawnPos, @Nullable LivingEntity currentTarget) {
private Vec3 createBombLaunchVelocity(Vec3 spawnPos, @Nullable LivingEntity currentTarget) {
Vec3 aimPoint = currentTarget != null
? currentTarget.getEyePosition()
: this.position().add(this.getViewVector(1.0F).scale(10.0D));
@@ -1042,14 +1007,14 @@ public class KrakenEntity extends Monster implements GeoEntity {
private void updateSwimRotation() {
Vec3 velocity = this.getDeltaMovement();
if (velocity.horizontalDistanceSqr() > 1.0E-4D) {
float targetYaw = (float)(Mth.atan2(velocity.z, velocity.x) * (180.0D / Math.PI)) - 90.0F;
float targetYaw = (float) (Mth.atan2(velocity.z, velocity.x) * (180.0D / Math.PI)) - 90.0F;
this.setYRot(Mth.approachDegrees(this.getYRot(), targetYaw, 6.0F));
this.yBodyRot = this.getYRot();
this.yHeadRot = this.getYRot();
}
if (velocity.lengthSqr() > 1.0E-4D) {
float targetPitch = (float)(-(Mth.atan2(velocity.y, velocity.horizontalDistance()) * (180.0D / Math.PI)));
float targetPitch = (float) (-(Mth.atan2(velocity.y, velocity.horizontalDistance()) * (180.0D / Math.PI)));
this.setXRot(Mth.approachDegrees(this.getXRot(), targetPitch, 4.0F));
}
}
@@ -1089,8 +1054,13 @@ public class KrakenEntity extends Monster implements GeoEntity {
return;
}
if (this.phaseTransitionTicks > 0 || this.isRoaring()) {
this.setAnimationState(ANIM_IDLE);
if (this.isRoaring()) {
this.setAnimationState(ANIM_CRY);
return;
}
if (this.phaseTransitionTicks > 0) {
this.setAnimationState(ANIM_NONE);
return;
}
@@ -1099,22 +1069,18 @@ public class KrakenEntity extends Monster implements GeoEntity {
this.setAnimationState(ANIM_GRAB);
return;
}
case ATTACK_SWIPE, ATTACK_SLAM -> {
this.setAnimationState(ANIM_SWIPE);
case ATTACK_SWING_LEFT -> {
this.setAnimationState(ANIM_SWING_LEFT);
return;
}
case ATTACK_SWING_RIGHT -> {
this.setAnimationState(ANIM_SWING_RIGHT);
return;
}
default -> {
}
}
Vec3 velocity = this.getDeltaMovement();
double horizontalSpeedSqr = velocity.horizontalDistanceSqr();
double verticalSpeedSqr = velocity.y * velocity.y;
if (horizontalSpeedSqr > 0.03D && horizontalSpeedSqr > verticalSpeedSqr * 2.5D) {
this.setAnimationState(ANIM_SWIM);
return;
}
this.setAnimationState(this.isPhaseTwo() ? ANIM_IDLE_TWO : ANIM_IDLE);
this.setAnimationState(ANIM_NONE);
}
}
@@ -76,16 +76,13 @@ public class NightmareEntity extends Monster implements GeoEntity {
private static final int ANIM_ATTACK = 3;
private static final int ANIM_FLY_ATTACK = 4;
private static final int ANIM_ROAR = 5;
private static final int ANIM_DEATH = 6;
private static final int ANIM_TAKEOFF = 7;
private static final int ANIM_FLY_DAMAGED = 8;
private static final int ATTACK_TOTAL_TICKS = 18;
private static final int ATTACK_DAMAGE_TICK = 9;
private static final int INTRO_ROAR_TICKS = 32;
private static final int COMBAT_ROAR_TICKS = 24;
private static final int DEATH_TICKS = 36;
private static final int ATTACK_TOTAL_TICKS = 20;
private static final int ATTACK_DAMAGE_TICK = 10;
private static final int INTRO_ROAR_TICKS = 30;
private static final int COMBAT_ROAR_TICKS = 30;
private static final int DEATH_TICKS = 30;
private static final int TARGET_RESET_TICKS = 60;
private static final int MIN_AIRBORNE_TICKS_FOR_FLY_ANIM = 4;
private static final int DREAD_TICKS = 160;
private static final int WEAKNESS_TICKS = 100;
private static final int BLOCK_BREAK_TICKS = 10;
@@ -101,15 +98,12 @@ public class NightmareEntity extends Monster implements GeoEntity {
private static final double FLIGHT_DISENGAGE_RANGE_SQR = 36.0D;
private static final double ROAR_RETRY_DISTANCE_SQR = 400.0D;
private static final RawAnimation IDLE_ANIM = RawAnimation.begin().thenLoop("Idle");
private static final RawAnimation IDLE_ANIM = RawAnimation.begin().thenLoop("idle");
private static final RawAnimation WALK_ANIM = RawAnimation.begin().thenLoop("walk");
private static final RawAnimation TAKEOFF_ANIM = RawAnimation.begin().thenPlay("animation").thenLoop("fly");
private static final RawAnimation FLY_ANIM = RawAnimation.begin().thenLoop("fly");
private static final RawAnimation ATTACK_ANIM = RawAnimation.begin().thenPlay("attack").thenLoop("Idle");
private static final RawAnimation FLY_ATTACK_ANIM = RawAnimation.begin().thenPlay("fly_attack").thenLoop("fly");
private static final RawAnimation ROAR_ANIM = RawAnimation.begin().thenPlay("roar").thenLoop("Idle");
private static final RawAnimation DEATH_ANIM = RawAnimation.begin().thenPlayAndHold("death");
private static final RawAnimation ATTACK_ANIM = RawAnimation.begin().thenPlay("attack").thenLoop("idle");
private static final RawAnimation FLY_ATTACK_ANIM = RawAnimation.begin().thenPlay("attack2").thenLoop("fly");
private static final RawAnimation ROAR_ANIM = RawAnimation.begin().thenPlay("roar").thenLoop("idle");
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
private int attackCooldown;
@@ -179,18 +173,14 @@ public class NightmareEntity extends Monster implements GeoEntity {
int animState = this.getAnimationState();
state.getController().setAnimationSpeed(switch (animState) {
case ANIM_FLY -> 0.45D;
case ANIM_DEATH -> 0.6D;
default -> 1.0D;
});
return switch (animState) {
case ANIM_WALK -> state.setAndContinue(WALK_ANIM);
case ANIM_TAKEOFF -> state.setAndContinue(TAKEOFF_ANIM);
case ANIM_FLY -> state.setAndContinue(FLY_ANIM);
// case ANIM_FLY_DAMAGED -> state.setAndContinue(FLY_ANIM);
case ANIM_ATTACK -> state.setAndContinue(ATTACK_ANIM);
case ANIM_FLY_ATTACK -> state.setAndContinue(FLY_ATTACK_ANIM);
case ANIM_ROAR -> state.setAndContinue(ROAR_ANIM);
case ANIM_DEATH -> state.setAndContinue(DEATH_ANIM);
default -> state.setAndContinue(IDLE_ANIM);
};
}
@@ -216,6 +206,14 @@ public class NightmareEntity extends Monster implements GeoEntity {
if (this.level().isClientSide) {
this.tickClientParticles();
this.updateFlightRotation();
if (this.isRoaring()) {
com.craisinlord.antarchy.content.client.CameraShakeClientState.register(
this,
com.craisinlord.antarchy.content.client.CameraShakeClientState.NIGHTMARE_RANGE,
com.craisinlord.antarchy.content.client.CameraShakeClientState.NIGHTMARE_STRENGTH,
this::isRoaring
);
}
return;
}
@@ -249,7 +247,6 @@ public class NightmareEntity extends Monster implements GeoEntity {
}
if (this.isDeadOrDying()) {
this.setAnimationState(ANIM_DEATH);
this.updateFlightRotation();
return;
}
@@ -367,7 +364,6 @@ public class NightmareEntity extends Monster implements GeoEntity {
@Override
public void die(DamageSource damageSource) {
if (!this.level().isClientSide) {
this.setAnimationState(ANIM_DEATH);
this.attackAnimationTicks = 0;
this.attackCooldown = 0;
this.roarTicks = 0;
@@ -381,9 +377,6 @@ public class NightmareEntity extends Monster implements GeoEntity {
@Override
protected void tickDeath() {
this.deathTime++;
if (this.deathTime == 1) {
this.setAnimationState(ANIM_DEATH);
}
if (this.deathTime >= DEATH_TICKS) {
this.remove(RemovalReason.KILLED);
this.dropExperience(this);
@@ -641,7 +634,6 @@ public class NightmareEntity extends Monster implements GeoEntity {
private void updateAnimationState() {
if (this.isDeadOrDying()) {
this.setAnimationState(ANIM_DEATH);
this.groundMoveTicks = 0;
this.wingFlapCooldown = 0;
return;
@@ -653,23 +645,18 @@ public class NightmareEntity extends Monster implements GeoEntity {
return;
}
if (this.attackAnimationTicks > 0) {
this.setAnimationState(this.onGround() ? ANIM_ATTACK : ANIM_FLY_ATTACK);
this.setAnimationState(this.isNoGravity() || this.airborneTicks >= MIN_AIRBORNE_TICKS_FOR_FLY_ANIM ? ANIM_FLY_ATTACK : ANIM_ATTACK);
this.groundMoveTicks = 0;
this.tickWingFlapSound();
return;
}
if (this.flyingToTarget) {
this.groundMoveTicks = 0;
if (this.onGround() || this.airborneTicks <= 8) {
this.setAnimationState(ANIM_TAKEOFF);
this.wingFlapCooldown = 0;
} else {
this.setAnimationState(ANIM_FLY);
this.tickWingFlapSound();
}
this.setAnimationState(ANIM_FLY);
this.tickWingFlapSound();
return;
}
if (!this.onGround()) {
if (this.isNoGravity() || this.airborneTicks >= MIN_AIRBORNE_TICKS_FOR_FLY_ANIM) {
this.groundMoveTicks = 0;
this.setAnimationState(ANIM_FLY);
this.tickWingFlapSound();
@@ -0,0 +1,85 @@
package com.craisinlord.antarchy.content.fluid;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.AntarchyObjects;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.tags.EntityTypeTags;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.LiquidBlock;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.material.FlowingFluid;
import net.minecraft.world.level.material.Fluids;
public class BileLiquidBlock extends LiquidBlock {
private static final ResourceLocation BILE_ID = ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "bile");
private static final ResourceLocation FLOWING_BILE_ID = ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "flowing_bile");
private static final ResourceLocation BROODSTONE_ID = ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "broodstone");
private static final ResourceLocation MYRMITE_ID = ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "myrmite");
private static final int EFFECT_DURATION_TICKS = 100;
public BileLiquidBlock(FlowingFluid fluid, BlockBehaviour.Properties properties) {
super(fluid, properties);
}
@Override
protected void neighborChanged(BlockState state, Level level, BlockPos pos, Block neighborBlock, BlockPos neighborPos, boolean movedByPiston) {
super.neighborChanged(state, level, pos, neighborBlock, neighborPos, movedByPiston);
if (!level.isClientSide) {
reactWithWater(state, level, pos);
}
}
@Override
protected void onPlace(BlockState state, Level level, BlockPos pos, BlockState oldState, boolean movedByPiston) {
super.onPlace(state, level, pos, oldState, movedByPiston);
if (!level.isClientSide) {
reactWithWater(state, level, pos);
}
}
private void reactWithWater(BlockState state, Level level, BlockPos pos) {
for (Direction dir : Direction.values()) {
FluidState neighbor = level.getFluidState(pos.relative(dir));
if (neighbor.is(Fluids.WATER) || neighbor.is(Fluids.FLOWING_WATER)) {
boolean isSource = state.getFluidState().isSource();
ResourceLocation resultId = isSource ? BROODSTONE_ID : MYRMITE_ID;
Block resultBlock = BuiltInRegistries.BLOCK.getOptional(resultId).orElse(null);
if (resultBlock != null) {
level.setBlock(pos, resultBlock.defaultBlockState(), 3);
level.levelEvent(1501, pos, 0);
}
return;
}
}
}
@Override
public void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) {
super.entityInside(state, level, pos, entity);
if (entity instanceof LivingEntity livingEntity) {
if (!level.isClientSide) {
if (livingEntity.getType().is(EntityTypeTags.SENSITIVE_TO_BANE_OF_ARTHROPODS)) {
return;
}
livingEntity.addEffect(new MobEffectInstance(AntarchyObjects.STINKY_EFFECT.get(), EFFECT_DURATION_TICKS, 0, false, true, true));
livingEntity.addEffect(new MobEffectInstance(MobEffects.WEAKNESS, EFFECT_DURATION_TICKS, 0, false, true, true));
}
}
}
public static boolean isBile(FluidState fluidState) {
ResourceLocation fluidId = BuiltInRegistries.FLUID.getKey(fluidState.getType());
return BILE_ID.equals(fluidId) || FLOWING_BILE_ID.equals(fluidId);
}
}
@@ -62,6 +62,14 @@ public class BigBerthaItem extends SwordItem implements GeoItem {
private static final String SPIN_TICKS_TAG = "antarchy.big_bertha_spin_ticks";
private static final String SPIN_DIRECTION_X_TAG = "antarchy.big_bertha_spin_direction_x";
private static final String SPIN_DIRECTION_Z_TAG = "antarchy.big_bertha_spin_direction_z";
private static final String TORETERROR_JUMPING_TAG = "antarchy.toreterror_jumping";
private static final String TORETERROR_LAUNCHED_TAG = "antarchy.toreterror_launched";
private static final int TORETERROR_LANDING_RADIUS = 6;
private static final int TORETERROR_COOLDOWN_TICKS = 200;
private static final int TORETERROR_SHAKE_TICKS = 25;
/** Client-side only: how many ticks of camera shake remain from a Toreterror landing. */
public static int clientShakeTicks = 0;
private static final ResourceLocation ATTACK_RANGE_MODIFIER_ID = ResourceLocation.fromNamespaceAndPath("antarchy", "big_bertha_attack_range");
private final Tier tier;
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
@@ -116,6 +124,14 @@ public class BigBerthaItem extends SwordItem implements GeoItem {
return InteractionResultHolder.sidedSuccess(stack, level.isClientSide);
}
if (mode == BossMode.TORETERROR) {
if (player.getCooldowns().isOnCooldown(this)) {
return InteractionResultHolder.fail(stack);
}
startToreterrorJump(level, player, stack);
return InteractionResultHolder.sidedSuccess(stack, level.isClientSide);
}
if (mode != BossMode.MOLEVORE) {
return InteractionResultHolder.pass(stack);
}
@@ -144,7 +160,7 @@ public class BigBerthaItem extends SwordItem implements GeoItem {
case LUCID -> applyLucidHit(target, player, serverLevel);
case NIGHTMARE -> applyNightmareHit(target, serverLevel);
case KRAKEN -> applyKrakenHit(player, target, serverLevel);
case MOLEVORE -> {
case MOLEVORE, TORETERROR -> {
}
}
@@ -158,6 +174,32 @@ public class BigBerthaItem extends SwordItem implements GeoItem {
return;
}
// Toreterror jump tracking
if (isToreterrorJumping(stack) && player.getMainHandItem() == stack) {
player.fallDistance = 0.0F; // prevent fall damage during the crash-down
boolean launched = readInt(stack, TORETERROR_LAUNCHED_TAG) == 1;
if (!launched && !player.onGround()) {
CustomData.update(DataComponents.CUSTOM_DATA, stack, tag -> tag.putInt(TORETERROR_LAUNCHED_TAG, 1));
}
if (launched && player.onGround()) {
CustomData.update(DataComponents.CUSTOM_DATA, stack, tag -> {
tag.remove(TORETERROR_JUMPING_TAG);
tag.remove(TORETERROR_LAUNCHED_TAG);
});
if (!level.isClientSide && player instanceof ServerPlayer serverPlayer) {
applyToreterrorLanding((ServerLevel) level, serverPlayer);
}
if (level.isClientSide) {
clientShakeTicks = TORETERROR_SHAKE_TICKS;
}
}
}
// Tick client shake counter
if (level.isClientSide && clientShakeTicks > 0) {
clientShakeTicks--;
}
int spinTicks = getSpinTicks(stack);
if (spinTicks <= 0) {
return;
@@ -293,6 +335,15 @@ public class BigBerthaItem extends SwordItem implements GeoItem {
}
}
public boolean tryCycleModeWhileCoolingDown(Level level, Player player, ItemStack stack) {
if (player.getMainHandItem() != stack || !player.isShiftKeyDown() || !player.getCooldowns().isOnCooldown(this)) {
return false;
}
cycleMode(level, player, stack);
return true;
}
private void startMolevoreSpin(Level level, Player player, ItemStack stack) {
Vec3 direction = player.getLookAngle().multiply(1.0D, 0.0D, 1.0D);
if (direction.lengthSqr() < 1.0E-4D) {
@@ -317,6 +368,43 @@ public class BigBerthaItem extends SwordItem implements GeoItem {
}
}
private void startToreterrorJump(Level level, Player player, ItemStack stack) {
CustomData.update(DataComponents.CUSTOM_DATA, stack, tag -> {
tag.putInt(TORETERROR_JUMPING_TAG, 1);
tag.remove(TORETERROR_LAUNCHED_TAG);
});
player.setDeltaMovement(player.getDeltaMovement().x, 1.5D, player.getDeltaMovement().z);
player.hasImpulse = true;
player.fallDistance = 0.0F;
player.getCooldowns().addCooldown(this, TORETERROR_COOLDOWN_TICKS);
player.awardStat(Stats.ITEM_USED.get(this));
level.playSound(null, player.blockPosition(), AntarchySoundEvents.TORETERROR_JUMP_UP.get(), SoundSource.PLAYERS, 1.2F, 0.9F);
}
private void applyToreterrorLanding(ServerLevel level, ServerPlayer player) {
ItemStack heldStack = player.getMainHandItem();
float damage = (float) (player.getAttributeValue(Attributes.ATTACK_DAMAGE) * 2.0D);
AABB aoe = player.getBoundingBox().inflate(TORETERROR_LANDING_RADIUS, 3.0D, TORETERROR_LANDING_RADIUS);
for (LivingEntity victim : level.getEntitiesOfClass(LivingEntity.class, aoe,
e -> e.isAlive() && e != player && !e.isSpectator())) {
victim.hurt(player.damageSources().playerAttack(player), damage);
Vec3 knockDir = victim.position().subtract(player.position()).multiply(1.0D, 0.0D, 1.0D);
if (knockDir.lengthSqr() < 1.0E-4D) knockDir = player.getLookAngle().multiply(1.0D, 0.0D, 1.0D);
Vec3 normalized = knockDir.normalize();
victim.push(normalized.x * 2.5D, 0.6D, normalized.z * 2.5D);
victim.hurtMarked = true;
}
level.playSound(null, player.blockPosition(), AntarchySoundEvents.TORETERROR_JUMP_LAND.get(), SoundSource.PLAYERS, 1.2F, 0.9F);
level.sendParticles(ParticleTypes.EXPLOSION, player.getX(), player.getY() + 0.5D, player.getZ(), 4, 1.5D, 0.3D, 1.5D, 0.0D);
level.sendParticles(ParticleTypes.SPLASH, player.getX(), player.getY() + 0.5D, player.getZ(), 60, 3.0D, 0.5D, 3.0D, 0.2D);
level.sendParticles(ParticleTypes.CRIT, player.getX(), player.getY() + 0.5D, player.getZ(), 20, 2.0D, 0.5D, 2.0D, 0.1D);
heldStack.hurtAndBreak(1, player, EquipmentSlot.MAINHAND);
}
private static boolean isToreterrorJumping(ItemStack stack) {
return readInt(stack, TORETERROR_JUMPING_TAG) == 1;
}
private void tickMolevoreSpin(ServerLevel level, ServerPlayer player, ItemStack stack, int spinTicks) {
Vec3 direction = getStoredSpinDirection(stack);
double verticalBoost = player.onGround() ? 0.08D : Math.max(-0.08D, player.getDeltaMovement().y);
@@ -505,7 +593,8 @@ public class BigBerthaItem extends SwordItem implements GeoItem {
NIGHTMARE(1, "tooltip.antarchy.big_bertha.mode.nightmare", "message.antarchy.big_bertha.mode.nightmare"),
KRAKEN(2, "tooltip.antarchy.big_bertha.mode.kraken", "message.antarchy.big_bertha.mode.kraken"),
MOLEVORE(3, "tooltip.antarchy.big_bertha.mode.molevore", "message.antarchy.big_bertha.mode.molevore"),
LUCID(4, "tooltip.antarchy.big_bertha.mode.lucid", "message.antarchy.big_bertha.mode.lucid");
LUCID(4, "tooltip.antarchy.big_bertha.mode.lucid", "message.antarchy.big_bertha.mode.lucid"),
TORETERROR(5, "tooltip.antarchy.big_bertha.mode.toreterror", "message.antarchy.big_bertha.mode.toreterror");
private final int id;
private final String tooltipKey;
@@ -523,7 +612,8 @@ public class BigBerthaItem extends SwordItem implements GeoItem {
case LUCID -> NIGHTMARE;
case NIGHTMARE -> KRAKEN;
case KRAKEN -> MOLEVORE;
case MOLEVORE -> BASILISK;
case MOLEVORE -> TORETERROR;
case TORETERROR -> BASILISK;
};
}
@@ -0,0 +1,20 @@
package com.craisinlord.antarchy.content.item;
public final class JumpyBootsHelper {
public static final int CHARGE_TICKS_MAX = 30;
public static final int PRIME_WINDOW_TICKS = 15;
public static final int COOLDOWN_TICKS = 40;
public static final float MAX_VERTICAL_BOOST = 2.0F;
public static final float SPRINT_FORWARD_BOOST = 0.3F;
public static final long FALL_PROTECTION_TICKS = 120L;
public static final String FALL_PROTECTION_NBT_KEY = "antarchy:jumpy_boots_protection_until";
private JumpyBootsHelper() {
}
public static float verticalBoostFor(int chargeTicks) {
float ratio = Math.min(chargeTicks / (float) CHARGE_TICKS_MAX, 1.0F);
return ratio * MAX_VERTICAL_BOOST;
}
}
@@ -0,0 +1,33 @@
package com.craisinlord.antarchy.content.item;
import net.minecraft.ChatFormatting;
import net.minecraft.core.Holder;
import net.minecraft.network.chat.Component;
import net.minecraft.world.item.ArmorItem;
import net.minecraft.world.item.ArmorMaterial;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import java.util.List;
public final class JumpyBootsItem extends ArmorItem {
public JumpyBootsItem(Holder<ArmorMaterial> material, Properties properties) {
super(material, Type.BOOTS, properties);
}
public static boolean isWearingJumpyBoots(net.minecraft.world.entity.LivingEntity entity) {
return entity != null && entity.getItemBySlot(net.minecraft.world.entity.EquipmentSlot.FEET).getItem() instanceof JumpyBootsItem;
}
@Override
public boolean isEnchantable(ItemStack stack) {
return stack.getCount() == 1;
}
@Override
public void appendHoverText(ItemStack stack, Item.TooltipContext context, List<Component> tooltipComponents, TooltipFlag tooltipFlag) {
tooltipComponents.add(Component.translatable("tooltip.antarchy.jumpy_boots").withStyle(ChatFormatting.DARK_RED));
super.appendHoverText(stack, context, tooltipComponents, tooltipFlag);
}
}
@@ -1,9 +1,11 @@
package com.craisinlord.antarchy.content.item;
import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.client.NightmareSwordTooltipHelper;
import java.util.List;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.SwordItem;
@@ -21,6 +23,18 @@ public class NightmareSwordItem extends SwordItem {
this.attackSpeed = attackSpeed;
}
public static float calculateDamage(LivingEntity attacker) {
float baseDamage = (float) AntarchySettings.nightmareSwordBaseDamage();
float maxHealth = attacker.getMaxHealth();
if (maxHealth <= 0.0F) {
return baseDamage;
}
float missingFraction = (maxHealth - attacker.getHealth()) / maxHealth;
float scalingFactor = (float) AntarchySettings.nightmareSwordScalingFactor();
return baseDamage + missingFraction * scalingFactor * baseDamage;
}
@Override
public ItemAttributeModifiers getDefaultAttributeModifiers() {
return SwordItem.createAttributes(
@@ -38,6 +52,7 @@ public class NightmareSwordItem extends SwordItem {
@Override
public void appendHoverText(ItemStack stack, Item.TooltipContext context, List<Component> tooltipComponents, TooltipFlag tooltipFlag) {
tooltipComponents.add(Component.translatable("tooltip.antarchy.nightmare_sword").withStyle(ChatFormatting.DARK_RED));
tooltipComponents.add(NightmareSwordTooltipHelper.damageLine());
super.appendHoverText(stack, context, tooltipComponents, tooltipFlag);
}
}
@@ -1,12 +1,144 @@
package com.craisinlord.antarchy.content.item;
import java.util.List;
import java.util.Locale;
import com.craisinlord.antarchy.config.AntarchySettings;
import net.minecraft.ChatFormatting;
import net.minecraft.core.Holder;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.EquipmentSlotGroup;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.attributes.AttributeModifier;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.item.ArmorItem;
import net.minecraft.world.item.ArmorMaterial;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.item.component.ItemAttributeModifiers;
import net.minecraft.world.level.Level;
public class PrimordialArmorItem extends ArmorItem {
private final Type armorType;
public PrimordialArmorItem(Holder<ArmorMaterial> material, Type type, Properties properties) {
super(material, type, properties);
this.armorType = type;
}
@Override
public ItemAttributeModifiers getDefaultAttributeModifiers() {
String prefix = "primordial_" + this.armorType.getName();
ItemAttributeModifiers.Builder builder = ItemAttributeModifiers.builder()
.add(
Attributes.ARMOR,
new AttributeModifier(
ResourceLocation.withDefaultNamespace(prefix + "_armor"),
getDefense(),
AttributeModifier.Operation.ADD_VALUE
),
EquipmentSlotGroup.bySlot(this.armorType.getSlot())
)
.add(
Attributes.ARMOR_TOUGHNESS,
new AttributeModifier(
ResourceLocation.withDefaultNamespace(prefix + "_armor_toughness"),
getToughness(),
AttributeModifier.Operation.ADD_VALUE
),
EquipmentSlotGroup.bySlot(this.armorType.getSlot())
);
double knockbackResistance = getKnockbackResistance(this.armorType);
if (knockbackResistance > 0.0D) {
builder.add(
Attributes.KNOCKBACK_RESISTANCE,
new AttributeModifier(
ResourceLocation.withDefaultNamespace(prefix + "_knockback_resistance"),
knockbackResistance,
AttributeModifier.Operation.ADD_VALUE
),
EquipmentSlotGroup.bySlot(this.armorType.getSlot())
);
}
builder.add(
Attributes.ATTACK_KNOCKBACK,
new AttributeModifier(
ResourceLocation.withDefaultNamespace(prefix + "_attack_knockback"),
AntarchySettings.primordialArmorKnockbackPerPiece(),
AttributeModifier.Operation.ADD_VALUE
),
EquipmentSlotGroup.bySlot(this.armorType.getSlot())
);
return builder.build();
}
@Override
public int getDefense() {
return super.getDefense();
}
@Override
public float getToughness() {
return super.getToughness();
}
@Override
public boolean isEnchantable(ItemStack stack) {
return stack.getCount() == 1;
}
@Override
public void inventoryTick(ItemStack stack, Level level, Entity entity, int slotId, boolean isSelected) {
if (!level.isClientSide && this.armorType == Type.HELMET && entity instanceof LivingEntity living) {
if (living.getItemBySlot(EquipmentSlot.HEAD) == stack) {
living.addEffect(new MobEffectInstance(MobEffects.WATER_BREATHING, 40, 0, false, false, true));
}
}
super.inventoryTick(stack, level, entity, slotId, isSelected);
}
@Override
public void appendHoverText(ItemStack stack, Item.TooltipContext context, List<Component> tooltipComponents, TooltipFlag tooltipFlag) {
if (this.armorType == Type.HELMET) {
tooltipComponents.add(Component.translatable("tooltip.antarchy.primordial_helmet_water_breathing").withStyle(ChatFormatting.AQUA));
}
String knockbackDealt = String.format(Locale.ROOT, "%.1f", AntarchySettings.primordialArmorKnockbackPerPiece());
tooltipComponents.add(Component.translatable("tooltip.antarchy.primordial_armor_knockback_dealt", knockbackDealt).withStyle(ChatFormatting.YELLOW));
tooltipComponents.add(Component.translatable("tooltip.antarchy.primordial_armor_set_bonus").withStyle(ChatFormatting.DARK_GREEN));
super.appendHoverText(stack, context, tooltipComponents, tooltipFlag);
}
public static double getKnockbackDealtBonus(LivingEntity entity) {
int pieces = 0;
for (ItemStack armorStack : entity.getArmorSlots()) {
if (armorStack.getItem() instanceof PrimordialArmorItem) {
pieces++;
}
}
return pieces * AntarchySettings.primordialArmorKnockbackPerPiece();
}
public static boolean hasFullSet(LivingEntity entity) {
return entity.getItemBySlot(EquipmentSlot.HEAD).getItem() instanceof PrimordialArmorItem
&& entity.getItemBySlot(EquipmentSlot.CHEST).getItem() instanceof PrimordialArmorItem
&& entity.getItemBySlot(EquipmentSlot.LEGS).getItem() instanceof PrimordialArmorItem
&& entity.getItemBySlot(EquipmentSlot.FEET).getItem() instanceof PrimordialArmorItem;
}
private static double getKnockbackResistance(Type armorType) {
return switch (armorType) {
case HELMET -> 0.20D;
case CHESTPLATE, BODY -> 0.30D;
case LEGGINGS -> 0.30D;
case BOOTS -> 0.20D;
};
}
}
@@ -7,6 +7,7 @@ import com.craisinlord.antarchy.content.client.model.ResourceBackedGeoItemModel;
import com.craisinlord.antarchy.content.entity.MissileSquidEntity;
import java.util.function.Consumer;
import java.util.function.Predicate;
import net.minecraft.core.component.DataComponents;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
@@ -21,7 +22,11 @@ import net.minecraft.world.entity.projectile.Projectile;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.ProjectileWeaponItem;
import net.minecraft.world.item.UseAnim;
import net.minecraft.world.item.enchantment.Enchantments;
import net.minecraft.world.item.enchantment.ItemEnchantments;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec2;
import net.minecraft.world.phys.Vec3;
import software.bernie.geckolib.animatable.GeoItem;
import software.bernie.geckolib.animatable.client.GeoRenderProvider;
import software.bernie.geckolib.animatable.instance.AnimatableInstanceCache;
@@ -55,17 +60,10 @@ public class SquidzookaItem extends ProjectileWeaponItem implements GeoItem {
}
if (!level.isClientSide()) {
MissileSquidEntity projectileSquid = AntarchyObjects.MISSILE_SQUID.get().create(level);
if (projectileSquid != null) {
projectileSquid.moveTo(
player.getX(),
player.getEyeY() - 0.2D,
player.getZ(),
player.getYRot(),
player.getXRot()
);
projectileSquid.launchFromSquidzooka(player, (float) AntarchySettings.squidzookaLaunchVelocity());
level.addFreshEntity(projectileSquid);
fireSquid(level, player, 0.0F);
if (hasMultishot(itemStack)) {
fireSquid(level, player, -10.0F);
fireSquid(level, player, 10.0F);
}
spawnFireParticles((ServerLevel) level, player);
itemStack.hurtAndBreak(1, player, usedHand == InteractionHand.MAIN_HAND ? EquipmentSlot.MAINHAND : EquipmentSlot.OFFHAND);
@@ -115,6 +113,16 @@ public class SquidzookaItem extends ProjectileWeaponItem implements GeoItem {
projectile.shootFromRotation(livingEntity, livingEntity.getXRot(), livingEntity.getYRot(), angle, velocity, inaccuracy);
}
@Override
public boolean isEnchantable(ItemStack stack) {
return stack.getCount() == 1;
}
@Override
public int getEnchantmentValue() {
return 1;
}
@Override
public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
controllers.add(new AnimationController<>(this, FIRE_CONTROLLER, state -> PlayState.STOP)
@@ -147,6 +155,32 @@ public class SquidzookaItem extends ProjectileWeaponItem implements GeoItem {
triggerAnim(livingEntity, animatableId, FIRE_CONTROLLER, FIRE_ANIMATION);
}
private static void fireSquid(Level level, Player player, float yawOffset) {
MissileSquidEntity projectileSquid = AntarchyObjects.MISSILE_SQUID.get().create(level);
if (projectileSquid == null) {
return;
}
float yaw = player.getYRot() + yawOffset;
Vec3 direction = Vec3.directionFromRotation(new Vec2(player.getXRot(), yaw));
projectileSquid.moveTo(
player.getX(),
player.getEyeY() - 0.2D,
player.getZ(),
yaw,
player.getXRot()
);
projectileSquid.launchAsProjectile(player, direction.scale(AntarchySettings.squidzookaLaunchVelocity()).add(player.getDeltaMovement()));
level.addFreshEntity(projectileSquid);
}
private static boolean hasMultishot(ItemStack stack) {
return stack.getOrDefault(DataComponents.ENCHANTMENTS, ItemEnchantments.EMPTY)
.keySet()
.stream()
.anyMatch(holder -> holder.is(Enchantments.MULTISHOT));
}
private static void spawnFireParticles(ServerLevel level, Player player) {
double lookX = player.getLookAngle().x;
double lookY = player.getLookAngle().y;
@@ -0,0 +1,17 @@
package com.craisinlord.antarchy.content.network;
import com.craisinlord.antarchy.Antarchy;
import io.netty.buffer.ByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
public record BigBerthaModeCyclePayload() implements CustomPacketPayload {
public static final Type<BigBerthaModeCyclePayload> TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "big_bertha_mode_cycle"));
public static final StreamCodec<ByteBuf, BigBerthaModeCyclePayload> STREAM_CODEC = StreamCodec.unit(new BigBerthaModeCyclePayload());
@Override
public Type<BigBerthaModeCyclePayload> type() {
return TYPE;
}
}
@@ -0,0 +1,23 @@
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 DorrieChargeJumpPayload(boolean pressing) implements CustomPacketPayload {
public static final Type<DorrieChargeJumpPayload> TYPE =
new Type<>(ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "dorrie_charge_jump_input"));
public static final StreamCodec<ByteBuf, DorrieChargeJumpPayload> STREAM_CODEC =
StreamCodec.composite(
ByteBufCodecs.BOOL, DorrieChargeJumpPayload::pressing,
DorrieChargeJumpPayload::new
);
@Override
public Type<DorrieChargeJumpPayload> type() {
return TYPE;
}
}
@@ -0,0 +1,23 @@
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 DorrieJumpInputPayload(boolean pressing) implements CustomPacketPayload {
public static final Type<DorrieJumpInputPayload> TYPE =
new Type<>(ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "dorrie_jump_input"));
public static final StreamCodec<ByteBuf, DorrieJumpInputPayload> STREAM_CODEC =
StreamCodec.composite(
ByteBufCodecs.BOOL, DorrieJumpInputPayload::pressing,
DorrieJumpInputPayload::new
);
@Override
public Type<DorrieJumpInputPayload> type() {
return TYPE;
}
}
@@ -0,0 +1,20 @@
package com.craisinlord.antarchy.content.network;
import com.craisinlord.antarchy.Antarchy;
import io.netty.buffer.ByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
public record HerculesBeetleImpactShakePayload(int durationTicks) implements CustomPacketPayload {
public static final Type<HerculesBeetleImpactShakePayload> TYPE =
new Type<>(ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "hercules_beetle_impact_shake"));
public static final StreamCodec<ByteBuf, HerculesBeetleImpactShakePayload> STREAM_CODEC =
ByteBufCodecs.VAR_INT.map(HerculesBeetleImpactShakePayload::new, HerculesBeetleImpactShakePayload::durationTicks);
@Override
public Type<HerculesBeetleImpactShakePayload> type() {
return TYPE;
}
}
@@ -0,0 +1,22 @@
package com.craisinlord.antarchy.content.network;
import java.util.function.BiConsumer;
import net.minecraft.server.level.ServerPlayer;
public final class HerculesBeetleImpactShakeSync {
private static final BiConsumer<ServerPlayer, Integer> NOOP = (player, ticks) -> {
};
private static volatile BiConsumer<ServerPlayer, Integer> sink = NOOP;
private HerculesBeetleImpactShakeSync() {
}
public static void setSink(BiConsumer<ServerPlayer, Integer> sink) {
HerculesBeetleImpactShakeSync.sink = sink == null ? NOOP : sink;
}
public static void send(ServerPlayer player, int ticks) {
sink.accept(player, ticks);
}
}
@@ -0,0 +1,23 @@
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 HerculesBeetleJumpInputPayload(boolean pressing) implements CustomPacketPayload {
public static final Type<HerculesBeetleJumpInputPayload> TYPE =
new Type<>(ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "hercules_beetle_jump_input"));
public static final StreamCodec<ByteBuf, HerculesBeetleJumpInputPayload> STREAM_CODEC =
StreamCodec.composite(
ByteBufCodecs.BOOL, HerculesBeetleJumpInputPayload::pressing,
HerculesBeetleJumpInputPayload::new
);
@Override
public Type<HerculesBeetleJumpInputPayload> type() {
return TYPE;
}
}
Loaded 100 of 598 files, more files were not shown because too many files have changed in this diff. Show more