Compare commits

...
11 changed files with 296 additions and 1 deletions
@@ -9,13 +9,21 @@
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.CobblemonItems;
import net.minecraft.world.entity.monster.piglin.PiglinAi;
import com.cobblemon.mod.common.CobblemonMemories;
import com.cobblemon.mod.common.entity.ai.PiglinDanceTarget;
import com.google.common.collect.ImmutableList;
import net.minecraft.world.entity.ai.Brain;
import net.minecraft.world.entity.monster.piglin.*;
import net.minecraft.world.entity.schedule.Activity;
import net.minecraft.world.item.ItemStack;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.*;
@Mixin(PiglinAi.class)
public class PiglinBarterMixin {
@@ -29,5 +37,31 @@ public class PiglinBarterMixin {
}
}
@Inject(method = "makeBrain", at = @At("TAIL"), cancellable = true)
private static void cobblemon$makeBrain(Piglin piglin, Brain<Piglin> brain, CallbackInfoReturnable<Brain<?>> cir) {
// Get access to private task map
brain.addActivityAndRemoveMemoryWhenStopped(Activity.INVESTIGATE, 10,
ImmutableList.of(
new PiglinDanceTarget()
), CobblemonMemories.INSTANCE.getNEAREST_LOVED_ENTITY());
}
@Redirect(
method = "updateActivity(Lnet/minecraft/world/entity/monster/piglin/Piglin;)V",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/entity/ai/Brain;setActiveActivityToFirstValid(Ljava/util/List;)V"
)
)
private static void redirectSetActiveActivityToFirstValid(
Brain<Piglin> brain,
List<Activity> originalList
) {
// Add new activity to the front
List<Activity> newList = new ArrayList<>(List.of(Activity.INVESTIGATE));
newList.addAll(originalList);
// Call original method with modified list
brain.setActiveActivityToFirstValid(newList);
}
}
@@ -9,10 +9,19 @@
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.CobblemonItems;
import com.cobblemon.mod.common.CobblemonMemories;
import com.cobblemon.mod.common.CobblemonSensors;
import com.google.common.collect.ImmutableList;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.ai.memory.MemoryModuleType;
import net.minecraft.world.entity.ai.sensing.Sensor;
import net.minecraft.world.entity.ai.sensing.SensorType;
import net.minecraft.world.entity.monster.piglin.Piglin;
import net.minecraft.world.item.ItemStack;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@@ -28,4 +37,27 @@ public abstract class PiglinEntityMixin {
ci.cancel();
}
}
@Shadow
@Final
@Mutable
private static ImmutableList<SensorType<? extends Sensor<? super Piglin>>> SENSOR_TYPES;
@Shadow
@Final
@Mutable
private static ImmutableList<MemoryModuleType<?>> MEMORY_TYPES;
@Inject(method = "<clinit>", at = @At("TAIL"))
private static void cobblemon$onInit(CallbackInfo ci) {
SENSOR_TYPES = ImmutableList.<SensorType<? extends Sensor<? super Piglin>>>builder()
.addAll(SENSOR_TYPES)
.add(CobblemonSensors.INSTANCE.getNEARBY_PIGLIN_LOVED_POKEMON())
.build();
MEMORY_TYPES = ImmutableList.<MemoryModuleType<?>>builder()
.addAll(MEMORY_TYPES)
.add(CobblemonMemories.INSTANCE.getNEAREST_LOVED_ENTITY())
.build();
}
}
@@ -0,0 +1,42 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.Brain;
import net.minecraft.world.entity.ai.memory.MemoryModuleType;
import net.minecraft.world.entity.ai.memory.NearestVisibleLivingEntities;
import net.minecraft.world.entity.ai.sensing.PiglinSpecificSensor;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.Optional;
@Mixin(PiglinSpecificSensor.class)
public abstract class PiglinSpecificSensorMixin {
@Inject(method = "doTick", at = @At("TAIL"))
protected void cobblemon$doTick(ServerLevel level, LivingEntity entity, CallbackInfo ci) {
Brain<?> brain = entity.getBrain();
if (!brain.hasMemoryValue(MemoryModuleType.NEAREST_VISIBLE_ZOMBIFIED)) {
NearestVisibleLivingEntities nearestVisibleLivingEntities = brain.getMemory(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES).orElse(NearestVisibleLivingEntities.empty());
Optional<LivingEntity> fearedEntity = Optional.empty();
for (LivingEntity livingEntity : nearestVisibleLivingEntities.findAll((x) -> true)) {
if (fearedEntity.isEmpty() && livingEntity instanceof PokemonEntity && ((PokemonEntity) livingEntity).getPokemon().getSpecies().getBehaviour().getEntityInteract().getAvoidedByPiglin()) {
fearedEntity = Optional.of(livingEntity);
brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_ZOMBIFIED, livingEntity);
}
}
}
}
}
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common
import com.cobblemon.mod.common.api.ai.CobblemonAttackTargetData
import com.cobblemon.mod.common.api.dialogue.ActiveDialogue
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.ListCodec
import com.mojang.serialization.codecs.PrimitiveCodec
@@ -48,6 +49,7 @@ object CobblemonMemories {
/** how many are following me rn? */
val HERD_SIZE = register<Int>("herd_size") // Don't bother saving it, we'll try to keep count roughly.
val ATTACK_TARGET_DATA = register<CobblemonAttackTargetData>("attack_target_data") // This is used to store additional information about the attack target, such as when to give up pursuit.
val NEAREST_LOVED_ENTITY = register<PokemonEntity>("nearst_loved_entity")
fun <U> register(id: String, codec: Codec<U>): MemoryModuleType<U> {
val memoryModule = MemoryModuleType(Optional.of(codec))
@@ -12,6 +12,7 @@ import com.cobblemon.mod.common.entity.pokemon.ai.sensors.DefendOwnerSensor
import com.cobblemon.mod.common.entity.pokemon.ai.sensors.DrowsySensor
import com.cobblemon.mod.common.entity.pokemon.ai.sensors.PokemonAdultSensor
import com.cobblemon.mod.common.entity.pokemon.ai.sensors.PokemonGrowableCropSensor
import com.cobblemon.mod.common.entity.pokemon.ai.sensors.PokemonPiglinLovedSensor
import com.cobblemon.mod.common.pokemon.ai.PokemonDisturbancesSensor
import com.cobblemon.mod.common.entity.sensor.BattlingPokemonSensor
import com.cobblemon.mod.common.entity.sensor.NPCBattlingSensor
@@ -36,6 +37,8 @@ object CobblemonSensors {
val NEARBY_GROWABLE_CROPS = register("nearby_growable_crops", ::PokemonGrowableCropSensor)
val NEARBY_PIGLIN_LOVED_POKEMON = register("nearby_golden_pokemon", ::PokemonPiglinLovedSensor)
fun <E : Entity, U : Sensor<E>> register(id: String, supplier: Supplier<U>): SensorType<U> {
val sensor = SensorType(supplier)
sensors[id] = sensor
@@ -0,0 +1,128 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.entity.ai
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.google.common.collect.ImmutableMap
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.ai.behavior.Behavior
import net.minecraft.world.entity.ai.behavior.EntityTracker
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.MemoryStatus
import net.minecraft.world.entity.ai.memory.WalkTarget
import net.minecraft.world.entity.ai.util.LandRandomPos
import net.minecraft.world.entity.monster.piglin.Piglin
import java.util.*
class PiglinDanceTarget : Behavior<Piglin>(
ImmutableMap.of(
CobblemonMemories.NEAREST_LOVED_ENTITY, MemoryStatus.VALUE_PRESENT,
MemoryModuleType.WALK_TARGET, MemoryStatus.REGISTERED,
),
Int.MAX_VALUE,
Int.MAX_VALUE
) {
var lovedPokemon: PokemonEntity? = null
val wayTooFar = 20F
var tooFar = 5F
var closeEnough = 3F
var tooClose = 1.5F
override fun checkExtraStartConditions(level: ServerLevel, owner: Piglin): Boolean {
lovedPokemon = (owner.brain.getMemory(CobblemonMemories.NEAREST_LOVED_ENTITY).orElse(null) ?: return false)
return lovedPokemon != null
}
override fun canStillUse(level: ServerLevel, entity: Piglin, gameTime: Long) : Boolean {
return lovedPokemon != null && lovedPokemon?.isAlive == true && (lovedPokemon?.distanceTo(entity) ?: 999F) < wayTooFar
}
override fun tick(level: ServerLevel, entity: Piglin, gameTime: Long) {
val leader : PokemonEntity = lovedPokemon ?: return
if (entity.brain.hasMemoryValue(MemoryModuleType.WALK_TARGET)) {
return // We're busy goin' n' shit
}
if (leader.distanceTo(entity) > tooFar) {
entity.brain.setMemory(
MemoryModuleType.WALK_TARGET,
CobblemonWalkTarget(
pos = leader.blockPosition(),
speedModifier = 0.6F,
completionRange = closeEnough.toInt()
)
)
} else if (leader.distanceTo(entity) < tooClose) {
// Give the dude some space
for (j in 0..9) {
val vec3 = LandRandomPos.getPosAway(entity, 5, 7, leader.position())
if (vec3 != null) {
entity.brain.setMemory(
MemoryModuleType.WALK_TARGET,
WalkTarget(
vec3,
0.4F,
closeEnough.toInt()
)
)
}
}
} else if (leader.brain.hasMemoryValue(MemoryModuleType.WALK_TARGET)) {
// Go to the leader's walk target, roughly.
val walkTarget = leader.brain.getMemory(MemoryModuleType.WALK_TARGET).orElse(null) ?: return
entity.brain.setMemory(
MemoryModuleType.WALK_TARGET,
CobblemonWalkTarget(
pos = walkTarget.target.currentBlockPosition(),
speedModifier = walkTarget.speedModifier,
nodeTypeFilter = (walkTarget as? CobblemonWalkTarget)?.nodeTypeFilter ?: { true },
completionRange = closeEnough.toInt()
)
)
} else {
// Maybe wander around and mingle
if (entity.random.nextFloat() < 0.01f) {
val vec3 = LandRandomPos.getPosTowards(entity,5, 7, leader.position())
if (vec3 != null) {
entity.brain.setMemory(
MemoryModuleType.WALK_TARGET,
WalkTarget(
vec3,
0.5F,
1
)
)
}
}
}
}
override fun start(level: ServerLevel, entity: Piglin, gameTime: Long) {
val pokemonEntity = lovedPokemon ?: return
entity.brain.setMemory(MemoryModuleType.DANCING, true)
entity.brain.setMemory(MemoryModuleType.CELEBRATE_LOCATION, Optional.of(pokemonEntity.blockPosition()))
entity.brain.setMemory(MemoryModuleType.LOOK_TARGET, EntityTracker(pokemonEntity, true))
}
override fun stop(level: ServerLevel, entity: Piglin, gameTime: Long) {
entity.brain.setMemory(MemoryModuleType.DANCING, false)
entity.brain.eraseMemory(MemoryModuleType.CELEBRATE_LOCATION)
}
}
@@ -0,0 +1,41 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.entity.pokemon.ai.sensors
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.NearestVisibleLivingEntities
import net.minecraft.world.entity.ai.sensing.Sensor
/**
* Senses when the Pokémon is near an adult of the same species.
*
* @author JazzMcNade
* @since May 15th, 2024
*/
class PokemonPiglinLovedSensor : Sensor<LivingEntity>(100) {
override fun requires() = setOf(CobblemonMemories.NEAREST_LOVED_ENTITY, MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES)
override fun doTick(world: ServerLevel, entity: LivingEntity) {
entity.brain.getMemory(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES).ifPresent { visibleMobs ->
setNearestPokemonAdult(entity, visibleMobs)
}
}
private fun setNearestPokemonAdult(entity: LivingEntity, visibleMobs: NearestVisibleLivingEntities) {
val nearestGoldenIdol = visibleMobs.findClosest { mob ->
mob is PokemonEntity && mob.exposedSpecies.behaviour.entityInteract.lovedByPiglin
}.map { mob -> mob as PokemonEntity }
entity.brain.setMemory(CobblemonMemories.NEAREST_LOVED_ENTITY, nearestGoldenIdol)
}
}
@@ -22,12 +22,16 @@ class EntityBehaviour {
val avoidedByCreeper = false
val avoidedByPhantom = false
val avoidedBySkeleton = false
val lovedByPiglin = false
val avoidedByPiglin = false
@Transient
val struct = ObjectValue(this).also {
it.addFunction("avoided_by_creeper") { DoubleValue(avoidedByCreeper) }
it.addFunction("avoided_by_phantom") { DoubleValue(avoidedByPhantom) }
it.addFunction("avoided_by_skeleton") { DoubleValue(avoidedBySkeleton) }
it.addFunction("loved_by_piglin") { DoubleValue(lovedByPiglin) }
it.addFunction("avoid_by_piglin") { DoubleValue(avoidedByPiglin) }
}
companion object {
@@ -58,6 +58,9 @@
"fly": {
"canFly": true
}
},
"entityInteract": {
"avoidedByPiglin": true
}
},
"drops": {
@@ -52,6 +52,11 @@
"resting": {
"canSleep": true,
"light": "0-4"
},
"entityInteract": {
"avoidedByCreeper": true,
"avoidedByPhantom": true,
"lovedByPiglin": true
},
"moving": {
"swim": {
@@ -61,6 +61,7 @@
"PhantomSweepAttackGoalMixin",
"PiglinBarterMixin",
"PiglinEntityMixin",
"PiglinSpecificSensorMixin",
"PlayerAdvancementsMixin",
"PlayerMixin",
"PointedDripstoneBlockMixin",