starts to add coelacanth and marine AI
This commit is contained in:
@@ -115,6 +115,7 @@ public class CommonClientClass {
|
|||||||
EntityRendererRegistry.register(ModEntities.CHILESAURUS, ChilesaurusRenderer::new);
|
EntityRendererRegistry.register(ModEntities.CHILESAURUS, ChilesaurusRenderer::new);
|
||||||
EntityRendererRegistry.register(ModEntities.MUSSASAURUS, MussasaurusRenderer::new);
|
EntityRendererRegistry.register(ModEntities.MUSSASAURUS, MussasaurusRenderer::new);
|
||||||
EntityRendererRegistry.register(ModEntities.THESCELOSAURUS, ThescelosaurusRenderer::new);
|
EntityRendererRegistry.register(ModEntities.THESCELOSAURUS, ThescelosaurusRenderer::new);
|
||||||
|
EntityRendererRegistry.register(ModEntities.COELACANTH, CoelacanthRenderer::new);
|
||||||
|
|
||||||
if (Platform.isFabric()) {
|
if (Platform.isFabric()) {
|
||||||
registerSpawnEggColors();
|
registerSpawnEggColors();
|
||||||
|
|||||||
@@ -351,6 +351,10 @@ public class ModEntities {
|
|||||||
ENTITIES.register("suchomimus", () -> EntityType.Builder.of(SuchomimusEntity::new, MobCategory.CREATURE)
|
ENTITIES.register("suchomimus", () -> EntityType.Builder.of(SuchomimusEntity::new, MobCategory.CREATURE)
|
||||||
.sized(1.3f, 1.8f).build("suchomimus"));
|
.sized(1.3f, 1.8f).build("suchomimus"));
|
||||||
|
|
||||||
|
public static final RegistrySupplier<EntityType<CoelacanthEntity>> COELACANTH =
|
||||||
|
ENTITIES.register("coelacanth", () -> EntityType.Builder.of(CoelacanthEntity::new, MobCategory.CREATURE)
|
||||||
|
.sized(1.3f, 1.8f).build("coelacanth"));
|
||||||
|
|
||||||
public static void registerAttributes() {
|
public static void registerAttributes() {
|
||||||
EntityAttributeRegistry.register(APATOSAURUS, ApatosaurusEntity::createAttributes);
|
EntityAttributeRegistry.register(APATOSAURUS, ApatosaurusEntity::createAttributes);
|
||||||
EntityAttributeRegistry.register(ALBERTOSAURUS, AlbertosaurusEntity::createAttributes);
|
EntityAttributeRegistry.register(ALBERTOSAURUS, AlbertosaurusEntity::createAttributes);
|
||||||
@@ -432,6 +436,7 @@ public class ModEntities {
|
|||||||
EntityAttributeRegistry.register(CHILESAURUS, ChilesaurusEntity::createAttributes);
|
EntityAttributeRegistry.register(CHILESAURUS, ChilesaurusEntity::createAttributes);
|
||||||
EntityAttributeRegistry.register(MUSSASAURUS, MussasaurusEntity::createAttributes);
|
EntityAttributeRegistry.register(MUSSASAURUS, MussasaurusEntity::createAttributes);
|
||||||
EntityAttributeRegistry.register(THESCELOSAURUS, ThescelosaurusEntity::createAttributes);
|
EntityAttributeRegistry.register(THESCELOSAURUS, ThescelosaurusEntity::createAttributes);
|
||||||
|
EntityAttributeRegistry.register(COELACANTH, CoelacanthEntity::createAttributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void registerSpawnPlacements() {
|
public static void registerSpawnPlacements() {
|
||||||
|
|||||||
@@ -70,6 +70,10 @@ public class DinoAIController {
|
|||||||
private static final int HERBIVORE_BROWSE_VERTICAL_RANGE = 2;
|
private static final int HERBIVORE_BROWSE_VERTICAL_RANGE = 2;
|
||||||
private static final float HERBIVORE_SELF_FEED_HUNGER_THRESHOLD = 0.75f;
|
private static final float HERBIVORE_SELF_FEED_HUNGER_THRESHOLD = 0.75f;
|
||||||
private static final float HERBIVORE_SELF_FEED_REPLENISHMENT_MULTIPLIER = 0.50f;
|
private static final float HERBIVORE_SELF_FEED_REPLENISHMENT_MULTIPLIER = 0.50f;
|
||||||
|
private static final double MARINE_ROAM_SPEED_MULTIPLIER = 1.0D;
|
||||||
|
private static final double MARINE_FLEE_SPEED_MULTIPLIER = 1.6D;
|
||||||
|
private static final int MARINE_ROAM_RANGE = 12;
|
||||||
|
private static final int MARINE_ROAM_VERTICAL_RANGE = 6;
|
||||||
|
|
||||||
|
|
||||||
private final DinoEntityBase dino;
|
private final DinoEntityBase dino;
|
||||||
@@ -175,6 +179,10 @@ public class DinoAIController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (dino.isMarine()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (handleWaterMovementHelper(null)) {
|
if (handleWaterMovementHelper(null)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -266,6 +274,20 @@ public class DinoAIController {
|
|||||||
float hungerDecay = jrConfig.hungerConsumption ? config.hungerDecay() * VITAL_DECAY_MULTIPLIER : 0.0f;
|
float hungerDecay = jrConfig.hungerConsumption ? config.hungerDecay() * VITAL_DECAY_MULTIPLIER : 0.0f;
|
||||||
float thirstDecay = jrConfig.waterConsumption ? config.thirstDecay() * VITAL_DECAY_MULTIPLIER : 0.0f;
|
float thirstDecay = jrConfig.waterConsumption ? config.thirstDecay() * VITAL_DECAY_MULTIPLIER : 0.0f;
|
||||||
|
|
||||||
|
if (dino.isMarine()) {
|
||||||
|
thirstDecay = 0.0f;
|
||||||
|
if (dino.dinoData != null) {
|
||||||
|
dino.dinoData.setThirst(config.maxThirst());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dino.isSimpleFish()) {
|
||||||
|
hungerDecay = 0.0f;
|
||||||
|
if (dino.dinoData != null) {
|
||||||
|
dino.dinoData.setHunger(config.maxHunger());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (currentState == State.SLEEPING) {
|
if (currentState == State.SLEEPING) {
|
||||||
hungerDecay *= 0.5f;
|
hungerDecay *= 0.5f;
|
||||||
thirstDecay *= 0.5f;
|
thirstDecay *= 0.5f;
|
||||||
@@ -381,7 +403,7 @@ public class DinoAIController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 5. Hunt check
|
// 5. Hunt check
|
||||||
if ((currentState == State.IDLE || currentState == State.ROAMING || currentState == State.TERRITORIAL_ROAMING) && dino.isCarnivore()) {
|
if ((currentState == State.IDLE || currentState == State.ROAMING || currentState == State.TERRITORIAL_ROAMING) && dino.isCarnivore() && !dino.isSimpleFish()) {
|
||||||
JRConfig jrConfig = JRConfigManager.get();
|
JRConfig jrConfig = JRConfigManager.get();
|
||||||
boolean hungerConsumptionEnabled = jrConfig.hungerConsumption;
|
boolean hungerConsumptionEnabled = jrConfig.hungerConsumption;
|
||||||
boolean waterConsumptionEnabled = jrConfig.waterConsumption;
|
boolean waterConsumptionEnabled = jrConfig.waterConsumption;
|
||||||
@@ -407,11 +429,30 @@ public class DinoAIController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 6. Water check
|
// 6. Water check
|
||||||
if ((currentState == State.IDLE || currentState == State.ROAMING || currentState == State.TERRITORIAL_ROAMING)) {
|
if (!dino.isMarine() && (currentState == State.IDLE || currentState == State.ROAMING || currentState == State.TERRITORIAL_ROAMING)) {
|
||||||
if (dino.dinoData != null && dino.dinoData.getThirst() < 50 && waterTarget == null) {
|
if (dino.dinoData != null && dino.dinoData.getThirst() < 50 && waterTarget == null) {
|
||||||
if (stateTimer % 10 == 0) findWater();
|
if (stateTimer % 10 == 0) findWater();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (dino.isMarine()) {
|
||||||
|
if (dino.isInWater()) {
|
||||||
|
// Reset air supply when submerged
|
||||||
|
dino.setAirSupply(dino.getMaxAirSupply());
|
||||||
|
} else {
|
||||||
|
// Vanilla automatically adds +4 air every tick on land.
|
||||||
|
// Subtracting 5 counters it for a perfect net loss of -1 per tick.
|
||||||
|
dino.setAirSupply(dino.getAirSupply() - 5);
|
||||||
|
|
||||||
|
// When air runs out (-20 is the vanilla grace period before taking damage)
|
||||||
|
if (dino.getAirSupply() <= -20) {
|
||||||
|
// Setting to 0 means it will take another 20 ticks to hit -20 again,
|
||||||
|
// resulting in exactly 1 tick of damage per second, just like vanilla.
|
||||||
|
dino.setAirSupply(0);
|
||||||
|
dino.hurt(dino.damageSources().dryOut(), 2.0F);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void findWater() {
|
private void findWater() {
|
||||||
@@ -783,6 +824,11 @@ public class DinoAIController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (dino.isMarine()) {
|
||||||
|
transitionTo(State.ROAMING);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
float territoriality = 0.0f;
|
float territoriality = 0.0f;
|
||||||
if (dino.dinoData != null) {
|
if (dino.dinoData != null) {
|
||||||
territoriality = dino.dinoData.getTerritoriality();
|
territoriality = dino.dinoData.getTerritoriality();
|
||||||
@@ -1055,6 +1101,16 @@ public class DinoAIController {
|
|||||||
private void findAndSetRoamTarget() {
|
private void findAndSetRoamTarget() {
|
||||||
this.roamTarget = null;
|
this.roamTarget = null;
|
||||||
|
|
||||||
|
if (dino.isMarine()) {
|
||||||
|
Vec3 marinePos = getMarineRoamPos();
|
||||||
|
if (marinePos != null) {
|
||||||
|
if (dino.getNavigation().moveTo(marinePos.x, marinePos.y, marinePos.z, getRoamSpeed() * MARINE_ROAM_SPEED_MULTIPLIER)) {
|
||||||
|
this.roamTarget = marinePos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return; // Prevent falling through to ground/flying logic
|
||||||
|
}
|
||||||
|
|
||||||
// Grounded flyers should walk to nearby grounded targets instead of taking off immediately.
|
// Grounded flyers should walk to nearby grounded targets instead of taking off immediately.
|
||||||
if (dino instanceof FlyingAnimal && dino.onGround()) {
|
if (dino instanceof FlyingAnimal && dino.onGround()) {
|
||||||
Vec3 groundPos = getNearbyGroundRoamPosForFlyer();
|
Vec3 groundPos = getNearbyGroundRoamPosForFlyer();
|
||||||
@@ -1262,6 +1318,10 @@ public class DinoAIController {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (dino.isSimpleFish()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (dino.tickCount % HERBIVORE_SELF_FEED_INTERVAL != 0 || dino.getRandom().nextFloat() > HERBIVORE_SELF_FEED_CHANCE) {
|
if (dino.tickCount % HERBIVORE_SELF_FEED_INTERVAL != 0 || dino.getRandom().nextFloat() > HERBIVORE_SELF_FEED_CHANCE) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1586,21 +1646,66 @@ public class DinoAIController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void tickFleeing() {
|
private void tickFleeing() {
|
||||||
if (attackTarget == null) {
|
if (attackTarget == null) {
|
||||||
transitionTo(State.IDLE);
|
transitionTo(State.IDLE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dino.getNavigation().isDone() || stateTimer % 10 == 0) {
|
if (dino.getNavigation().isDone() || stateTimer % 10 == 0) {
|
||||||
Vec3 awayDir = DefaultRandomPos.getPosAway(dino, 16, 7, attackTarget.position());
|
Vec3 awayDir;
|
||||||
if (awayDir != null) {
|
|
||||||
dino.getNavigation().moveTo(awayDir.x, awayDir.y, awayDir.z, dino.getAIConfig().runSpeed() * 1.2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dino.distanceToSqr(attackTarget) > 48 * 48) {
|
if (dino.isMarine()) {
|
||||||
transitionTo(State.IDLE);
|
awayDir = getMarineFleePos(attackTarget.position());
|
||||||
}
|
} else {
|
||||||
}
|
awayDir = DefaultRandomPos.getPosAway(dino, 16, 7, attackTarget.position());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (awayDir != null) {
|
||||||
|
double speed = dino.isMarine() ? dino.getAIConfig().runSpeed() * MARINE_FLEE_SPEED_MULTIPLIER : dino.getAIConfig().runSpeed() * 1.2;
|
||||||
|
dino.getNavigation().moveTo(awayDir.x, awayDir.y, awayDir.z, speed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dino.distanceToSqr(attackTarget) > 48 * 48) {
|
||||||
|
transitionTo(State.IDLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Vec3 getMarineRoamPos() {
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
BlockPos randomPos = dino.blockPosition().offset(
|
||||||
|
dino.getRandom().nextInt(MARINE_ROAM_RANGE * 2) - MARINE_ROAM_RANGE,
|
||||||
|
dino.getRandom().nextInt(MARINE_ROAM_VERTICAL_RANGE * 2) - MARINE_ROAM_VERTICAL_RANGE,
|
||||||
|
dino.getRandom().nextInt(MARINE_ROAM_RANGE * 2) - MARINE_ROAM_RANGE
|
||||||
|
);
|
||||||
|
|
||||||
|
if (dino.level().getFluidState(randomPos).is(FluidTags.WATER)) {
|
||||||
|
return Vec3.atCenterOf(randomPos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Vec3 getMarineFleePos(Vec3 threatPos) {
|
||||||
|
// Calculate a vector exactly opposite to the threat
|
||||||
|
Vec3 awayVector = dino.position().subtract(threatPos).normalize().scale(12);
|
||||||
|
BlockPos targetPos = BlockPos.containing(dino.position().add(awayVector));
|
||||||
|
|
||||||
|
// Try to find a valid water block in that general panic direction
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
BlockPos offsetPos = targetPos.offset(
|
||||||
|
dino.getRandom().nextInt(4) - 2,
|
||||||
|
dino.getRandom().nextInt(4) - 2,
|
||||||
|
dino.getRandom().nextInt(4) - 2
|
||||||
|
);
|
||||||
|
|
||||||
|
if (dino.level().getFluidState(offsetPos).is(FluidTags.WATER)) {
|
||||||
|
return Vec3.atCenterOf(offsetPos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: Just swim erratically if trapped
|
||||||
|
return getMarineRoamPos();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -107,7 +107,10 @@ public abstract class DinoEntityBase extends Animal {
|
|||||||
public abstract boolean isCarnivore();
|
public abstract boolean isCarnivore();
|
||||||
public abstract boolean isMarine();
|
public abstract boolean isMarine();
|
||||||
public abstract boolean isAmphibious();
|
public abstract boolean isAmphibious();
|
||||||
public abstract Block getEggBlock(); // New hook for breeding
|
public boolean isSimpleFish() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
public abstract Block getEggBlock();
|
||||||
public abstract DinoAIConfig getAIConfig();
|
public abstract DinoAIConfig getAIConfig();
|
||||||
|
|
||||||
public record DinoAIConfig(double walkSpeed, double runSpeed, double attackReach,
|
public record DinoAIConfig(double walkSpeed, double runSpeed, double attackReach,
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ public interface IDinoData {
|
|||||||
Type getType();
|
Type getType();
|
||||||
void setType(Type type);
|
void setType(Type type);
|
||||||
|
|
||||||
enum Group { THEROPOD, THYREOPHORAN, CERAPOD, SAUROPOD, ORNITHOPOD, AMPHIBIAN, ARCHOSAUROMORPH, PLEURODIRE, PTEROSAUR, REPTILIOMORPH, SQUAMATE }
|
enum Group { THEROPOD, THYREOPHORAN, CERAPOD, SAUROPOD, ORNITHOPOD, AMPHIBIAN, ARCHOSAUROMORPH, PLEURODIRE, PTEROSAUR, REPTILIOMORPH, SQUAMATE, PISCINE }
|
||||||
Group getGroup();
|
Group getGroup();
|
||||||
void setGroup(Group group);
|
void setGroup(Group group);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
package net.cmr.jurassicrevived.entity.client;
|
||||||
|
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
import net.cmr.jurassicrevived.Constants;
|
||||||
|
import net.cmr.jurassicrevived.entity.custom.CoelacanthEntity;
|
||||||
|
import net.minecraft.Util;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.minecraft.util.Mth;
|
||||||
|
/*? if <=1.20.1 {*/
|
||||||
|
import software.bernie.geckolib.core.animation.AnimationState;
|
||||||
|
/*?} else {*/
|
||||||
|
/*import software.bernie.geckolib.animation.AnimationState;
|
||||||
|
*//*?}*/
|
||||||
|
import software.bernie.geckolib.cache.object.GeoBone;
|
||||||
|
import software.bernie.geckolib.constant.DataTickets;
|
||||||
|
import software.bernie.geckolib.model.GeoModel;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class CoelacanthModel extends GeoModel<CoelacanthEntity> {
|
||||||
|
|
||||||
|
private static final Map<CoelacanthVariant, ResourceLocation> LOCATION_BY_VARIANT =
|
||||||
|
Util.make(Maps.newEnumMap(CoelacanthVariant.class), map -> {
|
||||||
|
map.put(CoelacanthVariant.MALE, Constants.rl("textures/entity/coelacanth.png"));
|
||||||
|
map.put(CoelacanthVariant.FEMALE, Constants.rl("textures/entity/coelacanth_female.png"));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Model-local "currently applied" offsets; cleared before each entity render
|
||||||
|
private float[] appliedYaw = null;
|
||||||
|
private float[] appliedRoll = null;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResourceLocation getModelResource(CoelacanthEntity animatable) {
|
||||||
|
return Constants.rl("geo/coelacanth.geo.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResourceLocation getTextureResource(CoelacanthEntity animatable) {
|
||||||
|
return LOCATION_BY_VARIANT.get(animatable.getVariant());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResourceLocation getAnimationResource(CoelacanthEntity animatable) {
|
||||||
|
return Constants.rl("animations/coelacanth.animation.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCustomAnimations(CoelacanthEntity entity, long id, AnimationState<CoelacanthEntity> state) {
|
||||||
|
super.setCustomAnimations(entity, id, state);
|
||||||
|
|
||||||
|
String[] tailBones = { "tail1", "tail2", "tail3", "tail4", "tail5", "tail6" };
|
||||||
|
int n = tailBones.length;
|
||||||
|
|
||||||
|
if (appliedYaw == null || appliedYaw.length != n) {
|
||||||
|
appliedYaw = new float[n];
|
||||||
|
appliedRoll = new float[n];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1) Clear previous offsets (from the last entity rendered with this model instance)
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
if (appliedYaw[i] == 0.0f && appliedRoll[i] == 0.0f) continue;
|
||||||
|
GeoBone bone = (GeoBone) getAnimationProcessor().getBone(tailBones[i]);
|
||||||
|
if (bone == null) continue;
|
||||||
|
if (appliedYaw[i] != 0.0f) bone.setRotY(bone.getRotY() - appliedYaw[i]);
|
||||||
|
if (appliedRoll[i] != 0.0f) bone.setRotZ(bone.getRotZ() - appliedRoll[i]);
|
||||||
|
appliedYaw[i] = 0.0f;
|
||||||
|
appliedRoll[i] = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) Interpolated sway for extra smoothness between ticks
|
||||||
|
float sway = entity.getTailSwayOffset(state.getPartialTick()); // [-1, 1]
|
||||||
|
|
||||||
|
// Tuning
|
||||||
|
float maxYawDeg = 22.0f; // increased max sweep
|
||||||
|
float swayGain = 1.35f; // amplifies overall power
|
||||||
|
float rollFraction = 0.40f; // slightly stronger roll for heft
|
||||||
|
|
||||||
|
float deg2rad = (float)Math.PI / 180f;
|
||||||
|
|
||||||
|
// Direction: positive sway (left turn) -> tail swings right (negative yaw)
|
||||||
|
// Flip the sign here if the sway feels inverted
|
||||||
|
float baseYaw = sway * maxYawDeg * deg2rad;
|
||||||
|
float baseRoll = -baseYaw * rollFraction;
|
||||||
|
|
||||||
|
float[] weights = { 1.00f, 0.78f, 0.58f, 0.42f, 0.30f, 0.22f };
|
||||||
|
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
GeoBone bone = (GeoBone) getAnimationProcessor().getBone(tailBones[i]);
|
||||||
|
if (bone == null) continue;
|
||||||
|
|
||||||
|
float w = weights[i];
|
||||||
|
float yaw = baseYaw * w;
|
||||||
|
float roll = baseRoll * w;
|
||||||
|
|
||||||
|
// OVERRIDE animations on Y/Z only: keep the model's predefined X bend intact
|
||||||
|
// Do NOT reset rotX here, so the upward bend stays
|
||||||
|
bone.setRotY(yaw);
|
||||||
|
bone.setRotZ(roll);
|
||||||
|
|
||||||
|
appliedYaw[i] = yaw;
|
||||||
|
appliedRoll[i] = roll;
|
||||||
|
}
|
||||||
|
|
||||||
|
GeoBone head = (GeoBone) getAnimationProcessor().getBone("body1");
|
||||||
|
|
||||||
|
if (head != null) {
|
||||||
|
var entityData = state.getData(DataTickets.ENTITY_MODEL_DATA);
|
||||||
|
float clampedYawDeg = Mth.clamp(entityData.netHeadYaw(), -20.0f, 20.0f);
|
||||||
|
head.setRotY(clampedYawDeg * Mth.DEG_TO_RAD);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package net.cmr.jurassicrevived.entity.client;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
|
import net.cmr.jurassicrevived.entity.custom.CoelacanthEntity;
|
||||||
|
import net.minecraft.client.renderer.entity.EntityRendererProvider;
|
||||||
|
import software.bernie.geckolib.cache.object.BakedGeoModel;
|
||||||
|
import software.bernie.geckolib.renderer.GeoEntityRenderer;
|
||||||
|
|
||||||
|
|
||||||
|
public class CoelacanthRenderer extends GeoEntityRenderer<CoelacanthEntity> {
|
||||||
|
public CoelacanthRenderer(EntityRendererProvider.Context renderManager) {
|
||||||
|
super(renderManager, new CoelacanthModel());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void scaleModelForRender(float widthScale, float heightScale, PoseStack poseStack, CoelacanthEntity animatable, BakedGeoModel model, boolean isReRender, float partialTick, int packedLight, int packedOverlay) {
|
||||||
|
super.scaleModelForRender(widthScale, heightScale, poseStack, animatable, model, isReRender, partialTick, packedLight, packedOverlay);
|
||||||
|
float scale = animatable.getTotalModelScale();
|
||||||
|
poseStack.scale(scale, scale, scale);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package net.cmr.jurassicrevived.entity.client;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Comparator;
|
||||||
|
|
||||||
|
public enum CoelacanthVariant {
|
||||||
|
MALE(0),
|
||||||
|
FEMALE(1);
|
||||||
|
|
||||||
|
private static final CoelacanthVariant[] BY_ID = Arrays.stream(values()).sorted(
|
||||||
|
Comparator.comparingInt(CoelacanthVariant::getId)).toArray(CoelacanthVariant[]::new);
|
||||||
|
|
||||||
|
private final int id;
|
||||||
|
|
||||||
|
CoelacanthVariant(int id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CoelacanthVariant byId(int id) {
|
||||||
|
return BY_ID[id % BY_ID.length];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,401 @@
|
|||||||
|
package net.cmr.jurassicrevived.entity.custom;
|
||||||
|
|
||||||
|
import net.cmr.jurassicrevived.Constants;
|
||||||
|
import net.cmr.jurassicrevived.block.ModBlocks;
|
||||||
|
import net.cmr.jurassicrevived.entity.ModEntities;
|
||||||
|
import net.cmr.jurassicrevived.entity.ai.DinoData;
|
||||||
|
import net.cmr.jurassicrevived.entity.ai.DinoEntityBase;
|
||||||
|
import net.cmr.jurassicrevived.entity.ai.IDinoData;
|
||||||
|
import net.cmr.jurassicrevived.entity.client.CoelacanthVariant;
|
||||||
|
import net.cmr.jurassicrevived.item.ModItems;
|
||||||
|
import net.cmr.jurassicrevived.sound.ModSounds;
|
||||||
|
import net.minecraft.Util;
|
||||||
|
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.util.Mth;
|
||||||
|
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.attributes.DefaultAttributes;
|
||||||
|
import net.minecraft.world.entity.ai.control.SmoothSwimmingMoveControl;
|
||||||
|
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.item.ItemStack;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
import net.minecraft.world.level.ServerLevelAccessor;
|
||||||
|
import net.minecraft.world.level.block.Block;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import software.bernie.geckolib.animatable.GeoEntity;
|
||||||
|
/*? if <=1.20.1 {*/
|
||||||
|
import software.bernie.geckolib.core.animatable.instance.AnimatableInstanceCache;
|
||||||
|
import software.bernie.geckolib.core.animatable.instance.SingletonAnimatableInstanceCache;
|
||||||
|
import software.bernie.geckolib.core.animation.AnimatableManager;
|
||||||
|
import software.bernie.geckolib.core.animation.Animation;
|
||||||
|
import software.bernie.geckolib.core.animation.AnimationController;
|
||||||
|
import software.bernie.geckolib.core.animation.RawAnimation;
|
||||||
|
import software.bernie.geckolib.core.object.PlayState;
|
||||||
|
/*?} else {*/
|
||||||
|
/*import software.bernie.geckolib.animatable.instance.AnimatableInstanceCache;
|
||||||
|
import software.bernie.geckolib.animatable.instance.SingletonAnimatableInstanceCache;
|
||||||
|
import software.bernie.geckolib.animation.*;
|
||||||
|
*//*?}*/
|
||||||
|
|
||||||
|
public class CoelacanthEntity extends DinoEntityBase implements GeoEntity {
|
||||||
|
private AnimatableInstanceCache cache = new SingletonAnimatableInstanceCache(this);
|
||||||
|
|
||||||
|
public static final int BABY_TO_ADULT_AGE_TICKS = 28800;
|
||||||
|
private static final float ANIMAL_SCALE = 1.0F;
|
||||||
|
private static final float MIN_ANIMAL_SCALE = !Constants.DEBUG_SIZES ? (ANIMAL_SCALE - 0.2F) : ANIMAL_SCALE;
|
||||||
|
private static final float MAX_ANIMAL_SCALE = !Constants.DEBUG_SIZES ? (ANIMAL_SCALE + 0.2F) : ANIMAL_SCALE;
|
||||||
|
|
||||||
|
private float lastDimensionsScale = 1.0F;
|
||||||
|
|
||||||
|
private static final EntityDataAccessor<Integer> VARIANT =
|
||||||
|
SynchedEntityData.defineId(CoelacanthEntity.class, EntityDataSerializers.INT);
|
||||||
|
private static final EntityDataAccessor<Integer> DATA_SYNCED_AGE =
|
||||||
|
SynchedEntityData.defineId(CoelacanthEntity.class, EntityDataSerializers.INT);
|
||||||
|
private static final EntityDataAccessor<Float> DATA_ANIMAL_SCALE =
|
||||||
|
SynchedEntityData.defineId(CoelacanthEntity.class, EntityDataSerializers.FLOAT);
|
||||||
|
|
||||||
|
// Procedural tail sway state (client-side use for rendering)
|
||||||
|
private float tailSwayOffset; // Smoothed offset in range roughly [-1, 1]
|
||||||
|
private float tailSwayVelocity; // Internal velocity for spring-damper
|
||||||
|
private float tailSwayPrev; // Previous frame value for interpolation
|
||||||
|
|
||||||
|
public CoelacanthEntity(EntityType<? extends Animal> pEntityType, Level pLevel) {
|
||||||
|
super(pEntityType, pLevel);
|
||||||
|
|
||||||
|
this.dinoData = new DinoData(
|
||||||
|
getAIConfig().maxHunger(),
|
||||||
|
getAIConfig().maxThirst(),
|
||||||
|
IDinoData.Mood.NEUTRAL,
|
||||||
|
IDinoData.Aggression.TERRITORIAL,
|
||||||
|
0.75f,
|
||||||
|
IDinoData.DietaryClassification.CARNIVORE,
|
||||||
|
IDinoData.Type.MARINE,
|
||||||
|
IDinoData.Group.PISCINE,
|
||||||
|
IDinoData.BirthType.EGG_LAYING,
|
||||||
|
IDinoData.ActivityPattern.CATHEMERAL
|
||||||
|
);
|
||||||
|
|
||||||
|
// Replaces the default move control with 3D swimming mechanics
|
||||||
|
this.moveControl = new SmoothSwimmingMoveControl(this, 85, 10, 0.02F, 0.1F, true);
|
||||||
|
|
||||||
|
// Tells the pathfinder that water is perfectly safe to navigate through
|
||||||
|
/*? if <=1.20.1 {*/
|
||||||
|
this.setPathfindingMalus(net.minecraft.world.level.pathfinder.BlockPathTypes.WATER, 0.0F);
|
||||||
|
/*?} else {*/
|
||||||
|
/*this.setPathfindingMalus(net.minecraft.world.level.pathfinder.PathType.WATER, 0.0F);
|
||||||
|
*//*?}*/
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected PathNavigation createNavigation(Level pLevel) {
|
||||||
|
return new WaterBoundPathNavigation(this, pLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
//@Override
|
||||||
|
//public ItemStack getPickResult() {
|
||||||
|
// return new ItemStack(ModItems.COELACANTH_SPAWN_EGG.get());
|
||||||
|
//}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCarnivore() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isMarine() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAmphibious() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSimpleFish() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Block getEggBlock() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DinoAIConfig getAIConfig() {
|
||||||
|
return new DinoAIConfig(0.3D, 1.1D, 1.5D, 100, 100, 0.05f, 0.1f, 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBaby(boolean baby) {
|
||||||
|
this.setAge(baby ? -BABY_TO_ADULT_AGE_TICKS : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AttributeSupplier.Builder createAttributes() {
|
||||||
|
return Animal.createLivingAttributes()
|
||||||
|
.add(Attributes.MAX_HEALTH, 30D)
|
||||||
|
.add(Attributes.MOVEMENT_SPEED, 0.3D)
|
||||||
|
.add(Attributes.ARMOR, 0D)
|
||||||
|
.add(Attributes.FOLLOW_RANGE, 32D)
|
||||||
|
.add(Attributes.ATTACK_DAMAGE, 8D)
|
||||||
|
.add(Attributes.KNOCKBACK_RESISTANCE, 0.3D)
|
||||||
|
.add(Attributes.ATTACK_KNOCKBACK, 0D);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public AgeableMob getBreedOffspring(ServerLevel pLevel, AgeableMob pOtherParent) {
|
||||||
|
AgeableMob child = ModEntities.COELACANTH.get().create(pLevel);
|
||||||
|
if (child instanceof CoelacanthEntity baby) {
|
||||||
|
CoelacanthVariant randomVariant = Util.getRandom(CoelacanthVariant.values(), this.random);
|
||||||
|
baby.setVariant(randomVariant);
|
||||||
|
baby.setBaby(true);
|
||||||
|
baby.setAnimalScale(Mth.nextFloat(this.random, MIN_ANIMAL_SCALE, MAX_ANIMAL_SCALE));
|
||||||
|
}
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean doHurtTarget(Entity target) {
|
||||||
|
boolean hit = super.doHurtTarget(target);
|
||||||
|
if (!level().isClientSide && hit && target instanceof LivingEntity) {
|
||||||
|
if (this.level() instanceof ServerLevel serverLevel) {
|
||||||
|
this.triggerAnim("attackController", "attack");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hit;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
|
||||||
|
controllers.add(new AnimationController<>(this, "SwimController", 5, state -> {
|
||||||
|
// 1. Highest Priority: Out of water flapping
|
||||||
|
if (!this.isInWater()) {
|
||||||
|
return state.setAndContinue(RawAnimation.begin().then("anim.coelacanth.flop", Animation.LoopType.LOOP));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Aquatic movement: Just swim
|
||||||
|
return state.setAndContinue(RawAnimation.begin().then("anim.coelacanth.swim", Animation.LoopType.LOOP));
|
||||||
|
}));
|
||||||
|
|
||||||
|
controllers.add(new AnimationController<>(this, "attackController", 5, state -> PlayState.STOP)
|
||||||
|
.triggerableAnim("attack", RawAnimation.begin().then("anim.coelacanth.attack", Animation.LoopType.PLAY_ONCE)));
|
||||||
|
|
||||||
|
controllers.add(new AnimationController<>(this, "mouthController", 5, state -> PlayState.STOP)
|
||||||
|
.triggerableAnim("mouth", RawAnimation.begin().then("anim.coelacanth.mouth", Animation.LoopType.PLAY_ONCE)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private float getSignedTurnDelta() {
|
||||||
|
// Only consider the body (torso) rotation so head look does not affect tail sway
|
||||||
|
return Mth.wrapDegrees(this.yBodyRot - this.yBodyRotO);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int mouthAnimCooldown = 0;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void tick() {
|
||||||
|
super.tick();
|
||||||
|
|
||||||
|
if (!level().isClientSide) {
|
||||||
|
this.entityData.set(DATA_SYNCED_AGE, this.getAge());
|
||||||
|
var maxHealthAttr = getAttribute(Attributes.MAX_HEALTH);
|
||||||
|
if (maxHealthAttr != null) {
|
||||||
|
double baseAdult = DefaultAttributes.getSupplier((EntityType<? extends LivingEntity>) this.getType()).getValue(Attributes.MAX_HEALTH);
|
||||||
|
double desired = this.isBaby() ? baseAdult * 0.10D : baseAdult;
|
||||||
|
if (maxHealthAttr.getBaseValue() != desired) {
|
||||||
|
double oldMax = maxHealthAttr.getBaseValue();
|
||||||
|
double healthRatio = this.getHealth() / (float) oldMax;
|
||||||
|
maxHealthAttr.setBaseValue(desired);
|
||||||
|
this.setHealth((float) (desired * Mth.clamp(healthRatio, 0.0F, 1.0F)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateDynamicDimensions();
|
||||||
|
|
||||||
|
if (!level().isClientSide) {
|
||||||
|
if (mouthAnimCooldown > 0) {
|
||||||
|
mouthAnimCooldown--;
|
||||||
|
} else {
|
||||||
|
this.triggerAnim("mouthController", "mouth");
|
||||||
|
// 30s–60s in ticks
|
||||||
|
mouthAnimCooldown = this.random.nextInt(1200 - 600 + 1) + 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (level().isClientSide) {
|
||||||
|
// Capture previous for smooth interpolation between ticks
|
||||||
|
this.tailSwayPrev = this.tailSwayOffset;
|
||||||
|
updateProceduralTailSway();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateProceduralTailSway() {
|
||||||
|
// Turn input derived from rotation deltas; works even when standing still and turning
|
||||||
|
float turnDegrees = getSignedTurnDelta();
|
||||||
|
|
||||||
|
// Deadzone to ignore tiny jitter so the tail can return to center cleanly
|
||||||
|
float deadzoneDeg = 0.6f; // smaller deadzone for more responsiveness
|
||||||
|
float turnInput = 0.0f;
|
||||||
|
if (Math.abs(turnDegrees) >= deadzoneDeg) {
|
||||||
|
// Higher sensitivity so small in-place turns still affect the model
|
||||||
|
turnInput = Mth.clamp(turnDegrees / 15.0f, -1.0f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Target offset: keep intuitive sign (positive input -> positive sway)
|
||||||
|
float target = turnInput;
|
||||||
|
|
||||||
|
// One-pole low-pass (no bounce). Larger alpha => snappier and less "stiff".
|
||||||
|
float alpha = 0.24f; // try 0.20–0.30 to taste
|
||||||
|
|
||||||
|
tailSwayOffset += (target - tailSwayOffset) * alpha;
|
||||||
|
|
||||||
|
// Snap tiny residuals to zero so it visibly settles
|
||||||
|
if (Math.abs(tailSwayOffset) < 0.003f) {
|
||||||
|
tailSwayOffset = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No oscillation velocity retained
|
||||||
|
tailSwayVelocity = 0.0f;
|
||||||
|
|
||||||
|
tailSwayOffset = Mth.clamp(tailSwayOffset, -1.5f, 1.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expose to the model for bone rotation
|
||||||
|
public float getTailSwayOffset() {
|
||||||
|
return tailSwayOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interpolated sway for smooth rendering between ticks
|
||||||
|
public float getTailSwayOffset(float partialTick) {
|
||||||
|
return Mth.lerp(Mth.clamp(partialTick, 0.0f, 1.0f), tailSwayPrev, tailSwayOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*? if <=1.20.1 {*/
|
||||||
|
@Override
|
||||||
|
protected void defineSynchedData() {
|
||||||
|
super.defineSynchedData();
|
||||||
|
this.entityData.define(VARIANT, 0);
|
||||||
|
this.entityData.define(DATA_SYNCED_AGE, 0);
|
||||||
|
this.entityData.define(DATA_ANIMAL_SCALE, 1.0F);
|
||||||
|
}
|
||||||
|
/*?} else {*/
|
||||||
|
/*@Override
|
||||||
|
protected void defineSynchedData(SynchedEntityData.Builder pBuilder) {
|
||||||
|
super.defineSynchedData(pBuilder);
|
||||||
|
pBuilder.define(VARIANT, 0);
|
||||||
|
pBuilder.define(DATA_SYNCED_AGE, 0);
|
||||||
|
pBuilder.define(DATA_ANIMAL_SCALE, 1.0F);
|
||||||
|
}
|
||||||
|
*//*?}*/
|
||||||
|
|
||||||
|
public int getSyncedAge() {
|
||||||
|
return this.entityData.get(DATA_SYNCED_AGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getAnimalScale() {
|
||||||
|
return this.entityData.get(DATA_ANIMAL_SCALE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setAnimalScale(float animalScale) {
|
||||||
|
this.entityData.set(DATA_ANIMAL_SCALE, animalScale);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getGrowthScale() {
|
||||||
|
if (!this.isBaby()) {
|
||||||
|
return 1.0F;
|
||||||
|
}
|
||||||
|
|
||||||
|
int age = this.level().isClientSide ? this.getSyncedAge() : this.getAge();
|
||||||
|
float growthProgress = Mth.clamp((BABY_TO_ADULT_AGE_TICKS + age) / (float) BABY_TO_ADULT_AGE_TICKS, 0.0F, 1.0F);
|
||||||
|
return Mth.lerp(growthProgress, 0.2F, 1.0F);
|
||||||
|
}
|
||||||
|
public float getTotalModelScale() {
|
||||||
|
return this.getAnimalScale() * this.getGrowthScale();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateDynamicDimensions() {
|
||||||
|
float dimensionsScale = this.getTotalModelScale();
|
||||||
|
if (Math.abs(dimensionsScale - this.lastDimensionsScale) > 0.01F) {
|
||||||
|
this.lastDimensionsScale = dimensionsScale;
|
||||||
|
this.refreshDimensions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getDinoScale() {
|
||||||
|
return this.getTotalModelScale();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTypeVariant() {
|
||||||
|
return this.entityData.get(VARIANT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CoelacanthVariant getVariant() {
|
||||||
|
return CoelacanthVariant.byId(this.getTypeVariant() & 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setVariant(CoelacanthVariant variant) {
|
||||||
|
this.entityData.set(VARIANT, variant.getId() & 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canMate(Animal other) {
|
||||||
|
if (!super.canMate(other)) return false;
|
||||||
|
if (!(other instanceof CoelacanthEntity that)) return false;
|
||||||
|
return this.getVariant() != that.getVariant();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void addAdditionalSaveData(CompoundTag pCompound) {
|
||||||
|
super.addAdditionalSaveData(pCompound);
|
||||||
|
pCompound.putInt("Variant", this.getTypeVariant());
|
||||||
|
pCompound.putFloat("AnimalScale", this.getAnimalScale());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readAdditionalSaveData(CompoundTag pCompound) {
|
||||||
|
super.readAdditionalSaveData(pCompound);
|
||||||
|
this.entityData.set(VARIANT, pCompound.getInt("Variant"));
|
||||||
|
if (pCompound.contains("AnimalScale")) {
|
||||||
|
this.setAnimalScale(pCompound.getFloat("AnimalScale"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*? if <=1.20.1 {*/
|
||||||
|
@Override
|
||||||
|
public SpawnGroupData finalizeSpawn(ServerLevelAccessor pLevel, DifficultyInstance pDifficulty, MobSpawnType pReason, @Nullable SpawnGroupData pSpawnData, @Nullable CompoundTag pDataTag) {
|
||||||
|
CoelacanthVariant variant = Util.getRandom(CoelacanthVariant.values(), this.random);
|
||||||
|
this.setVariant(variant);
|
||||||
|
this.setAnimalScale(Mth.nextFloat(this.random, MIN_ANIMAL_SCALE, MAX_ANIMAL_SCALE));
|
||||||
|
return super.finalizeSpawn(pLevel, pDifficulty, pReason, pSpawnData, pDataTag);
|
||||||
|
}
|
||||||
|
/*?} else {*/
|
||||||
|
/*@Override
|
||||||
|
public SpawnGroupData finalizeSpawn(ServerLevelAccessor level, DifficultyInstance difficulty, MobSpawnType spawnType, @Nullable SpawnGroupData spawnGroupData) {
|
||||||
|
CoelacanthVariant variant = Util.getRandom(CoelacanthVariant.values(), this.random);
|
||||||
|
this.setVariant(variant);
|
||||||
|
this.setAnimalScale(Mth.nextFloat(this.random, MIN_ANIMAL_SCALE, MAX_ANIMAL_SCALE));
|
||||||
|
return super.finalizeSpawn(level, difficulty, spawnType, spawnGroupData);
|
||||||
|
}
|
||||||
|
*//*?}*/
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AnimatableInstanceCache getAnimatableInstanceCache() {
|
||||||
|
return cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getFluidJumpThreshold() {
|
||||||
|
return this.getEyeHeight();
|
||||||
|
}
|
||||||
|
}
|
||||||
+1120
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,202 @@
|
|||||||
|
{
|
||||||
|
"format_version": "1.12.0",
|
||||||
|
"minecraft:geometry": [
|
||||||
|
{
|
||||||
|
"description": {
|
||||||
|
"identifier": "geometry.coelacanth",
|
||||||
|
"texture_width": 64,
|
||||||
|
"texture_height": 64,
|
||||||
|
"visible_bounds_width": 4,
|
||||||
|
"visible_bounds_height": 3.5,
|
||||||
|
"visible_bounds_offset": [0, 1.25, 0]
|
||||||
|
},
|
||||||
|
"bones": [
|
||||||
|
{
|
||||||
|
"name": "root",
|
||||||
|
"pivot": [0, 24, 0]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BodySection1",
|
||||||
|
"parent": "root",
|
||||||
|
"pivot": [0, 23, 0.3],
|
||||||
|
"cubes": [
|
||||||
|
{"origin": [-2.5, 16, 0.3], "size": [5, 7, 5], "uv": [5, 29]}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BodySection2",
|
||||||
|
"parent": "BodySection1",
|
||||||
|
"pivot": [0, 23, 4.5],
|
||||||
|
"cubes": [
|
||||||
|
{"origin": [-2, 16, 4.5], "size": [4, 7, 4], "uv": [45, 18]}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BodySection3",
|
||||||
|
"parent": "BodySection2",
|
||||||
|
"pivot": [0, 23, 7.7],
|
||||||
|
"cubes": [
|
||||||
|
{"origin": [-1.5, 16, 7.7], "size": [3, 7, 3], "uv": [30, 17]}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TailSection1",
|
||||||
|
"parent": "BodySection3",
|
||||||
|
"pivot": [0, 22.6, 9.9],
|
||||||
|
"cubes": [
|
||||||
|
{"origin": [-1, 16.6, 9.9], "size": [2, 6, 3], "uv": [17, 16]}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TailSection2",
|
||||||
|
"parent": "TailSection1",
|
||||||
|
"pivot": [0, 22.1, 11.9],
|
||||||
|
"rotation": [0.09, 0, 0],
|
||||||
|
"cubes": [
|
||||||
|
{"origin": [-1, 17.1, 11.9], "size": [2, 5, 3], "uv": [6, 19]}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TailSection3",
|
||||||
|
"parent": "TailSection2",
|
||||||
|
"pivot": [0, 19.6, 12.1],
|
||||||
|
"rotation": [45, 0, 0],
|
||||||
|
"cubes": [
|
||||||
|
{"origin": [-0.5, 15.6, 12.1], "size": [1, 4, 4], "uv": [53, 9]}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Tailfin",
|
||||||
|
"parent": "TailSection3",
|
||||||
|
"pivot": [0, 19.6, 12.1],
|
||||||
|
"cubes": [
|
||||||
|
{"origin": [0, 11.6, 12.1], "size": [0, 8, 8], "uv": [21, -7]}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Dorsalfin2",
|
||||||
|
"parent": "TailSection2",
|
||||||
|
"pivot": [0, 22.1, 10.4],
|
||||||
|
"rotation": [52.08903, 0, 0],
|
||||||
|
"cubes": [
|
||||||
|
{"origin": [0, 19.1, 10.4], "size": [0, 3, 6], "uv": [1, 0]}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Assfin",
|
||||||
|
"parent": "TailSection1",
|
||||||
|
"pivot": [0, 17.4, 10.6],
|
||||||
|
"rotation": [-58.87, 0, 0],
|
||||||
|
"cubes": [
|
||||||
|
{"origin": [0, 17.4, 10.6], "size": [0, 3, 6], "uv": [1, 4]}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Leftbottomfin",
|
||||||
|
"parent": "BodySection2",
|
||||||
|
"pivot": [0, 16, 4.1],
|
||||||
|
"rotation": [0, -63.67, -90],
|
||||||
|
"cubes": [
|
||||||
|
{"origin": [0, 15, 4.1], "size": [4, 2, 5], "uv": [45, 1]}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Dorsalfin",
|
||||||
|
"parent": "BodySection1",
|
||||||
|
"pivot": [0, 22.8, 0.1],
|
||||||
|
"rotation": [51.73, 0, 0],
|
||||||
|
"cubes": [
|
||||||
|
{"origin": [0, 18.8, 0.1], "size": [0, 4, 6], "uv": [1, -5]}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Neck",
|
||||||
|
"parent": "BodySection1",
|
||||||
|
"pivot": [0, 19.8, -2.4],
|
||||||
|
"cubes": [
|
||||||
|
{"origin": [-2, 16.1, -3.4], "size": [4, 3, 4], "uv": [47, 34]}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Mainhead",
|
||||||
|
"parent": "Neck",
|
||||||
|
"pivot": [0, 18.8, -3.52],
|
||||||
|
"rotation": [42.06, 0, 0],
|
||||||
|
"cubes": [
|
||||||
|
{"origin": [-2.5, 18.8, -6.52], "size": [5, 3, 3], "uv": [22, 48]}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Lowerjawrear",
|
||||||
|
"parent": "Mainhead",
|
||||||
|
"pivot": [0, 18.5, -2.52],
|
||||||
|
"rotation": [-44.87, 0, 0],
|
||||||
|
"cubes": [
|
||||||
|
{"origin": [-2, 15.5, -5.52], "size": [4, 3, 3], "uv": [45, 55]}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Lowerjawfront",
|
||||||
|
"parent": "Lowerjawrear",
|
||||||
|
"pivot": [0, 15.9, -5.22],
|
||||||
|
"rotation": [-37.79953, 0, 0],
|
||||||
|
"cubes": [
|
||||||
|
{"origin": [-2, 15.4, -8.22], "size": [4, 1, 3], "uv": [49, 50]}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Rightgill2",
|
||||||
|
"parent": "Mainhead",
|
||||||
|
"pivot": [1.7, 17.8, -4.62],
|
||||||
|
"cubes": [
|
||||||
|
{"origin": [1.7, 17.8, -4.62], "size": [1, 3, 3], "uv": [22, 55]}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Rightgill",
|
||||||
|
"parent": "Mainhead",
|
||||||
|
"pivot": [-1.7, 17.8, -4.62],
|
||||||
|
"cubes": [
|
||||||
|
{"origin": [-2.7, 17.8, -4.62], "size": [1, 3, 3], "uv": [22, 55], "mirror": true}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Neckback1",
|
||||||
|
"parent": "Neck",
|
||||||
|
"pivot": [0, 19.8, -5.4],
|
||||||
|
"rotation": [34.77266, 0, 0],
|
||||||
|
"cubes": [
|
||||||
|
{"origin": [-2, 17.8, -5.4], "size": [4, 3, 3], "uv": [7, 48]}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Neckback2",
|
||||||
|
"parent": "Neckback1",
|
||||||
|
"pivot": [0, 20.8, -2.4],
|
||||||
|
"rotation": [-24.91, 0, 0],
|
||||||
|
"cubes": [
|
||||||
|
{"origin": [-2.5, 16.8, -2.4], "size": [5, 4, 4], "uv": [28, 32]}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "RightFrontFlipper",
|
||||||
|
"parent": "Neck",
|
||||||
|
"pivot": [1.5, 20, -2.8],
|
||||||
|
"rotation": [21.22, -2.61, 90],
|
||||||
|
"cubes": [
|
||||||
|
{"origin": [2, 20, -2.8], "size": [3, 0, 7], "uv": [7, 1]}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LeftFrontFlipper",
|
||||||
|
"parent": "Neck",
|
||||||
|
"pivot": [-1.5, 20, -2.8],
|
||||||
|
"rotation": [21.22, 2.61, -90],
|
||||||
|
"cubes": [
|
||||||
|
{"origin": [-4.5, 20, -2.8], "size": [3, 0, 7], "uv": [7, 1], "mirror": true}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 9.9 KiB |
Reference in New Issue
Block a user