From 7bab99d63020d3c7d724bc0e9f3097420d3b43f8 Mon Sep 17 00:00:00 2001 From: MysticKoko <132279944+MysticKoko@users.noreply.github.com> Date: Wed, 27 Nov 2024 03:35:03 +0200 Subject: [PATCH] Reintroduced the hunger system to fish and sharks --- .../entity/fish/HybridAquaticFishEntity.kt | 74 +---- .../entity/shark/HybridAquaticSharkEntity.kt | 262 +++++++++++------- 2 files changed, 177 insertions(+), 159 deletions(-) diff --git a/src/main/kotlin/dev/hybridlabs/aquatic/entity/fish/HybridAquaticFishEntity.kt b/src/main/kotlin/dev/hybridlabs/aquatic/entity/fish/HybridAquaticFishEntity.kt index 406da3d0..69340a87 100644 --- a/src/main/kotlin/dev/hybridlabs/aquatic/entity/fish/HybridAquaticFishEntity.kt +++ b/src/main/kotlin/dev/hybridlabs/aquatic/entity/fish/HybridAquaticFishEntity.kt @@ -5,12 +5,14 @@ import dev.hybridlabs.aquatic.entity.fish.HybridAquaticFishEntity.VariantCollisi import dev.hybridlabs.aquatic.entity.fish.HybridAquaticFishEntity.VariantCollisionRules.ExclusionStatus.INCLUSIVE import dev.hybridlabs.aquatic.entity.fish.ray.HybridAquaticRayEntity import dev.hybridlabs.aquatic.entity.shark.HybridAquaticSharkEntity -import dev.hybridlabs.aquatic.tag.HybridAquaticEntityTags import net.minecraft.block.Blocks import net.minecraft.entity.* import net.minecraft.entity.ai.control.AquaticMoveControl import net.minecraft.entity.ai.control.YawAdjustingLookControl -import net.minecraft.entity.ai.goal.* +import net.minecraft.entity.ai.goal.ActiveTargetGoal +import net.minecraft.entity.ai.goal.EscapeDangerGoal +import net.minecraft.entity.ai.goal.MeleeAttackGoal +import net.minecraft.entity.ai.goal.SwimAroundGoal import net.minecraft.entity.ai.pathing.EntityNavigation import net.minecraft.entity.ai.pathing.PathNodeType import net.minecraft.entity.ai.pathing.SwimNavigation @@ -18,11 +20,8 @@ import net.minecraft.entity.damage.DamageSource import net.minecraft.entity.data.DataTracker import net.minecraft.entity.data.TrackedData import net.minecraft.entity.data.TrackedDataHandlerRegistry -import net.minecraft.entity.mob.GuardianEntity import net.minecraft.entity.mob.WaterCreatureEntity -import net.minecraft.entity.player.PlayerEntity import net.minecraft.nbt.NbtCompound -import net.minecraft.particle.ParticleTypes import net.minecraft.registry.tag.FluidTags import net.minecraft.registry.tag.TagKey import net.minecraft.sound.SoundEvent @@ -61,9 +60,8 @@ open class HybridAquaticFishEntity( super.initGoals() goalSelector.add(0, EscapeDangerGoal(this, 1.25)) goalSelector.add(4, SwimAroundGoal(this, 1.0, 10)) - goalSelector.add(1, AttackGoal(this)) - goalSelector.add(5, FleeEntityGoal(this, GuardianEntity::class.java, 8.0f, 1.0, 1.0)) - goalSelector.add(5, FleeEntityGoal(this, PlayerEntity::class.java, 8.0f, 1.0, 1.0)) + goalSelector.add(1, FishAttackGoal(this)) + targetSelector.add(3, ActiveTargetGoal(this, LivingEntity::class.java, 10, true, true) { entity: LivingEntity -> prey.any { preyType -> entity.type.isIn(preyType) } && hunger < MAX_HUNGER / 4 }) } override fun initDataTracker() { @@ -143,26 +141,6 @@ open class HybridAquaticFishEntity( damage(this.damageSources.dryOut(), 1.0f) } } - if (world.isClient && isTouchingWater && isAttacking) { - val rotationVec = getRotationVec(0.0f) - val offsetY = 0.0f - random.nextFloat() - - for (i in 0..1) { - val particleX = x - rotationVec.x * 0.1 - val particleY = y - rotationVec.y * -0.1 - val particleZ = z - rotationVec.z * offsetY - - world.addParticle( - ParticleTypes.DOLPHIN, - particleX, - particleY, - particleZ, - 0.0, - 0.0, - 0.0 - ) - } - } } override fun tickMovement() { @@ -194,25 +172,6 @@ open class HybridAquaticFishEntity( } } - private fun getHungerValue(entityType: EntityType<*>): Int { - if (entityType.isIn(HybridAquaticEntityTags.CRUSTACEAN)) - return 150 - if (entityType.isIn(HybridAquaticEntityTags.JELLYFISH)) - return 150 - if (entityType.isIn(HybridAquaticEntityTags.SMALL_PREY)) - return 300 - else if (entityType.isIn(HybridAquaticEntityTags.MEDIUM_PREY)) - return 600 - else if (entityType.isIn(HybridAquaticEntityTags.LARGE_PREY)) - return 1200 - - return 0 - } - - open fun eatFish(entityType: EntityType<*>) { - hunger += getHungerValue(entityType) - } - override fun tickWaterBreathingAir(air: Int) {} private fun getMaxMoistness(): Int { @@ -385,7 +344,7 @@ open class HybridAquaticFishEntity( return 0.0 } - internal class AttackGoal(private val fish: HybridAquaticFishEntity) : MeleeAttackGoal(fish, 1.0,true) { + internal class FishAttackGoal(private val fish: HybridAquaticFishEntity) : MeleeAttackGoal(fish, 1.0,true) { override fun canStart(): Boolean { return !fish.fromFishingNet && super.canStart() } @@ -399,23 +358,10 @@ open class HybridAquaticFishEntity( fish.attemptAttack = true if (target.health <= 0) - fish.eatFish(target.type) + fish.hunger = MAX_HUNGER + fish.health = fish.maxHealth } } - - override fun getSquaredMaxAttackDistance(entity: LivingEntity): Double { - return (1.25f + entity.width).toDouble() - } - - override fun start() { - super.start() - fish.attemptAttack = false - } - - override fun stop() { - super.stop() - fish.attemptAttack = false - } } companion object { @@ -426,7 +372,7 @@ open class HybridAquaticFishEntity( val VARIANT: TrackedData = DataTracker.registerData(HybridAquaticFishEntity::class.java, TrackedDataHandlerRegistry.STRING) var VARIANT_DATA: TrackedData = DataTracker.registerData(HybridAquaticFishEntity::class.java, TrackedDataHandlerRegistry.NBT_COMPOUND) - const val MAX_HUNGER = 1200 + const val MAX_HUNGER = 2400 const val HUNGER_KEY = "Hunger" const val MOISTNESS_KEY = "Moistness" const val VARIANT_KEY = "Variant" diff --git a/src/main/kotlin/dev/hybridlabs/aquatic/entity/shark/HybridAquaticSharkEntity.kt b/src/main/kotlin/dev/hybridlabs/aquatic/entity/shark/HybridAquaticSharkEntity.kt index ddfe7639..f73a033f 100644 --- a/src/main/kotlin/dev/hybridlabs/aquatic/entity/shark/HybridAquaticSharkEntity.kt +++ b/src/main/kotlin/dev/hybridlabs/aquatic/entity/shark/HybridAquaticSharkEntity.kt @@ -53,22 +53,14 @@ open class HybridAquaticSharkEntity( private val factory = GeckoLibUtil.createInstanceCache(this) private var angerTime = 0 private var angryAt: UUID? = null - private var moistness: Int - get() = dataTracker.get(MOISTNESS) - set(moistness) { - dataTracker.set(MOISTNESS, moistness) - } + private var hunger: Int get() = dataTracker.get(HUNGER) set(hunger) { dataTracker.set(HUNGER, hunger) } - var size: Int - get() = dataTracker.get(SHARK_SIZE) - set(size) { - dataTracker.set(SHARK_SIZE, size) - } + //#region Initialization init { setPathfindingPenalty(PathNodeType.WATER, 0.0f) setPathfindingPenalty(PathNodeType.WALKABLE, 10.0f) @@ -77,64 +69,15 @@ open class HybridAquaticSharkEntity( navigation = SwimNavigation(this, world) } - companion object { - const val MOISTNESS_KEY = "Moistness" - const val SHARK_SIZE_KEY = "SharkSize" - val SHARK_SIZE: TrackedData = DataTracker.registerData(HybridAquaticSharkEntity::class.java, TrackedDataHandlerRegistry.INTEGER) - val MOISTNESS: TrackedData = - DataTracker.registerData(HybridAquaticSharkEntity::class.java, TrackedDataHandlerRegistry.INTEGER) - - const val MAX_HUNGER = 1200 - const val HUNGER_KEY = "Hunger" - - val HUNGER: TrackedData = - DataTracker.registerData(HybridAquaticSharkEntity::class.java, TrackedDataHandlerRegistry.INTEGER) - - val ANGER_TIME_RANGE: UniformIntProvider = TimeHelper.betweenSeconds(10, 30) - - - fun canSpawn( - type: EntityType, - world: WorldAccess, - reason: SpawnReason?, - pos: BlockPos, - random: Random? - ): Boolean { - val topY = world.seaLevel - 8 - val bottomY = world.seaLevel - 24 - - return pos.y in bottomY..topY && - world.getFluidState(pos.down()).isIn(FluidTags.WATER) && - world.getBlockState(pos.up()).isOf(Blocks.WATER) - } - fun getScaleAdjustment(shark : HybridAquaticSharkEntity, adjustment : Float): Float { - return 1.0f + (shark.size * adjustment) - } - } - - override fun getMaxHeadRotation(): Int { - return 1 - } - - override fun getMaxLookPitchChange(): Int { - return 1 - } - override fun initGoals() { super.initGoals() goalSelector.add(4, SwimAroundGoal(this, 1.0, 2)) goalSelector.add(4, LookAroundGoal(this)) goalSelector.add(5, LookAtEntityGoal(this, PlayerEntity::class.java, 6.0f)) - goalSelector.add(1, MeleeAttackGoal(this, 1.1, false)) + goalSelector.add(1, SharkAttackGoal(this)) targetSelector.add(2, ActiveTargetGoal(this, PlayerEntity::class.java, 10, true, true) { entity: LivingEntity -> shouldAngerAt(entity) || shouldProximityAttack(entity as PlayerEntity) && !isPassive}) targetSelector.add(1, ActiveTargetGoal(this, LivingEntity::class.java, 10, true, true) { it.hasStatusEffect(HybridAquaticStatusEffects.BLEEDING) && it !is HybridAquaticSharkEntity && !isPassive}) - } - - override fun initDataTracker() { - super.initDataTracker() - dataTracker.startTracking(MOISTNESS, getMaxMoistness()) - dataTracker.startTracking(HUNGER, MAX_HUNGER) - dataTracker.startTracking(SHARK_SIZE, 0) + targetSelector.add(3, ActiveTargetGoal(this, LivingEntity::class.java, 10, true, true) { entity: LivingEntity -> prey.any { preyType -> entity.type.isIn(preyType) } && hunger < MAX_HUNGER / 4 }) } override fun initialize( @@ -150,19 +93,20 @@ open class HybridAquaticSharkEntity( return super.initialize(world, difficulty, spawnReason, entityData, entityNbt) } + override fun tick() { super.tick() if (isAiDisabled) { return } - if (this.isWet) { + if (this.isSubmergedInWater) { moistness = getMaxMoistness() } else { moistness -= 1 if (moistness <= -20) { moistness = 0 - damage(this.damageSources.dryOut(), 1.0f) + damage(this.damageSources.dryOut(), 2.0f) } } @@ -180,21 +124,19 @@ open class HybridAquaticSharkEntity( if (hunger > 0) hunger -= 1 } - override fun tickMovement() { - this.tickHandSwing() - super.tickMovement() - } - - override fun tickWaterBreathingAir(air: Int) {} + //#endregion - private fun getMaxMoistness(): Int { - return 1200 + override fun canImmediatelyDespawn(distanceSquared: Double): Boolean { + return false } - override fun travel(movementInput: Vec3d?) { - super.travel(movementInput) + override fun getLimitPerChunk(): Int { + return 3 } + //#region NBT & Data + private var fromFishingNet = false + override fun writeCustomDataToNbt(nbt: NbtCompound) { super.writeCustomDataToNbt(nbt) this.writeAngerToNbt(nbt) @@ -204,8 +146,6 @@ open class HybridAquaticSharkEntity( nbt.putBoolean("FromFishingNet", fromFishingNet) } - private var fromFishingNet = false - override fun readCustomDataFromNbt(nbt: NbtCompound) { super.readCustomDataFromNbt(nbt) this.readAngerFromNbt(this.world, nbt) @@ -215,44 +155,71 @@ open class HybridAquaticSharkEntity( fromFishingNet = nbt.getBoolean("FromFishingNet") } - protected open fun getMinSize() : Int { - return 0 + override fun initDataTracker() { + super.initDataTracker() + dataTracker.startTracking(MOISTNESS, getMaxMoistness()) + dataTracker.startTracking(SHARK_SIZE, 0) + dataTracker.startTracking(HUNGER, MAX_HUNGER) + dataTracker.startTracking(ATTEMPT_ATTACK, false) } - protected open fun getMaxSize() : Int { - return 0 - } + //#endregion - override fun getActiveEyeHeight(pose: EntityPose, dimensions: EntityDimensions): Float { - return dimensions.height * 0.65f + + //#region Movement + override fun tickMovement() { + this.tickHandSwing() + super.tickMovement() } - override fun canImmediatelyDespawn(distanceSquared: Double): Boolean { - return false + override fun travel(movementInput: Vec3d?) { + super.travel(movementInput) } - override fun getLimitPerChunk(): Int { - return 3 + override fun getMaxHeadRotation(): Int { + return 1 } - //#region SFX - override fun getHurtSound(source: DamageSource): SoundEvent { - return SoundEvents.ENTITY_HOSTILE_HURT + override fun getMaxLookPitchChange(): Int { + return 1 } - override fun getDeathSound(): SoundEvent { - return SoundEvents.ENTITY_HOSTILE_DEATH + //#endregion + + //#region Size & Dimensions + var size: Int + get() = dataTracker.get(SHARK_SIZE) + set(size) { + dataTracker.set(SHARK_SIZE, size) + } + + protected open fun getMinSize() : Int { + return 0 } - override fun getAmbientSound(): SoundEvent { - return SoundEvents.ENTITY_COD_AMBIENT + protected open fun getMaxSize() : Int { + return 0 } - override fun getSwimSound(): SoundEvent { - return SoundEvents.ENTITY_DOLPHIN_SWIM + override fun getActiveEyeHeight(pose: EntityPose, dimensions: EntityDimensions): Float { + return dimensions.height * 0.65f } //#endregion + + //#region Water Breathing + private var moistness: Int + get() = dataTracker.get(MOISTNESS) + set(moistness) { + dataTracker.set(MOISTNESS, moistness) + } + + override fun tickWaterBreathingAir(air: Int) {} + + private fun getMaxMoistness(): Int { + return 1200 + } + override fun getMaxAir(): Int { return 4800 } @@ -261,6 +228,9 @@ open class HybridAquaticSharkEntity( return this.maxAir } + //#endregion + + //#region Animations override fun registerControllers(controllerRegistrar: AnimatableManager.ControllerRegistrar) { controllerRegistrar.add( AnimationController( @@ -286,6 +256,23 @@ open class HybridAquaticSharkEntity( return factory } + //#endregion + + //#region SFX + override fun getHurtSound(source: DamageSource): SoundEvent { + return SoundEvents.ENTITY_COD_HURT + } + + override fun getDeathSound(): SoundEvent { + return SoundEvents.ENTITY_COD_DEATH + } + + override fun getAmbientSound(): SoundEvent { + return SoundEvents.ENTITY_COD_AMBIENT + } + + //#endregion + private fun shouldProximityAttack(player: PlayerEntity): Boolean { if (customName?.string == "friend") return false @@ -321,4 +308,89 @@ open class HybridAquaticSharkEntity( super.dropLoot(source, causedByPlayer) } } + + private var attemptAttack: Boolean + get() = dataTracker.get(ATTEMPT_ATTACK) + set(attemptAttack) { + dataTracker.set(ATTEMPT_ATTACK, attemptAttack) + } + + internal class SharkAttackGoal(private val shark: HybridAquaticSharkEntity) : MeleeAttackGoal(shark, 1.0,true) { + override fun canStart(): Boolean { + return !shark.fromFishingNet && super.canStart() + } + + override fun attack(target: LivingEntity, squaredDistance: Double) { + val d = getSquaredMaxAttackDistance(target) + if (squaredDistance <= d && this.isCooledDown) { + resetCooldown() + mob.tryAttack(target) + shark.isSprinting = true + shark.attemptAttack = true + + if (target.health <= 0) + shark.hunger = MAX_HUNGER + shark.health = shark.maxHealth + } + } + + override fun getSquaredMaxAttackDistance(entity: LivingEntity): Double { + return (1.25f + entity.width).toDouble() + } + + override fun start() { + super.start() + shark.attemptAttack = false + } + + override fun stop() { + super.stop() + shark.attemptAttack = false + } + } + + companion object { + const val MOISTNESS_KEY = "Moistness" + const val SHARK_SIZE_KEY = "SharkSize" + const val MAX_HUNGER = 2400 + const val HUNGER_KEY = "Hunger" + + val SHARK_SIZE: TrackedData = DataTracker.registerData(HybridAquaticSharkEntity::class.java, TrackedDataHandlerRegistry.INTEGER) + val MOISTNESS: TrackedData = DataTracker.registerData(HybridAquaticSharkEntity::class.java, TrackedDataHandlerRegistry.INTEGER) + val HUNGER: TrackedData = DataTracker.registerData(HybridAquaticSharkEntity::class.java, TrackedDataHandlerRegistry.INTEGER) + val ATTEMPT_ATTACK: TrackedData = DataTracker.registerData(HybridAquaticSharkEntity::class.java, TrackedDataHandlerRegistry.BOOLEAN) + val ANGER_TIME_RANGE: UniformIntProvider = TimeHelper.betweenSeconds(10, 30) + + fun canSpawn( + type: EntityType, + world: WorldAccess, + reason: SpawnReason?, + pos: BlockPos, + random: Random? + ): Boolean { + val topY = world.seaLevel - 8 + val bottomY = world.seaLevel - 24 + + return pos.y in bottomY..topY && + world.getFluidState(pos.down()).isIn(FluidTags.WATER) && + world.getBlockState(pos.up()).isOf(Blocks.WATER) + } + + @Suppress("UNUSED_PARAMETER", "DEPRECATION") + fun canUndergroundSpawn( + type: EntityType?, + world: WorldAccess, + reason: SpawnReason?, + pos: BlockPos, + random: Random? + ): Boolean { + return pos.y <= world.seaLevel - 32 && + world.getBaseLightLevel(pos, 0) == 0 && + world.getBlockState(pos).isOf(Blocks.WATER) + } + + fun getScaleAdjustment(shark : HybridAquaticSharkEntity, adjustment : Float): Float { + return 1.0f + (shark.size * adjustment) + } + } } \ No newline at end of file