- Баллы
- 98
- Имя в Minecraft
- Dymeth
На самом деле, это не полноценный туториал, однако существенная подсазка о том, как организовать кастомный ИИ мобам. Возможно кто-то однажды решится написать полноценную статью на основе этих материалов.
Вариантов реализации кастомного интеллекта у мобов довольно много, но у каждого есть свои преимущества и недостатки. Выбирайте, что подойдёт конкретно вам.
Другие способы в сообщении ниже
Вариантов реализации кастомного интеллекта у мобов довольно много, но у каждого есть свои преимущества и недостатки. Выбирайте, что подойдёт конкретно вам.
Если нужно заменить мобу интеллект на интеллект другого ванильного моба - это элементарно можно сделать при помощи плагина LibsDisguises. Например, можно заставить свинью ходить за игроками и атаковать их (задать свинье интеллект зомби). При необходимости на моба можно даже повесить скин игрока, тем самым создав NPC.
Само собой, у плагина есть API.
Плагин: https://spigotmc.org/resources/81
Документация: https://spigotmc.org/wiki/lib-s-disguises
* Бесплатная версия LibsDisguises не включает в себя часть функций (смотрите на странице плагина, какие именно). Если нужны они - используйте платную версию: https://spigotmc.org/resources/32453
* Пробовал вместо LibsDisguises использовать iDusguise, но работает он существенно хуже, имеет меньше функционала и не поддерживает новые версии
Само собой, у плагина есть API.
Плагин: https://spigotmc.org/resources/81
Документация: https://spigotmc.org/wiki/lib-s-disguises
* Бесплатная версия LibsDisguises не включает в себя часть функций (смотрите на странице плагина, какие именно). Если нужны они - используйте платную версию: https://spigotmc.org/resources/32453
* Пробовал вместо LibsDisguises использовать iDusguise, но работает он существенно хуже, имеет меньше функционала и не поддерживает новые версии
Если же нужна полностью собственная логика работы интеллекта, то в Paper есть API для этого. Нужно понимать, что чем более новую версию вы используете - тем больше будет возможностей и тем меньше рутинного кода вы будете писать.
Всё перечислять не буду, упомяну лишь наиболее интересные, на мой взгляд, возможности.
На версии 1.12 или даже старше появились EntityPathfindEvent и SentientNPC.setTarget(LivingEntity)
В 1.13 появилась возможность управлять передвижением моба при помощи Mob.getPathfinder(), а также с комфортом проводить трассировку при помощи LivingEntity.rayTraceBlocks(), LivingEntity.getTargetBlockInfo() и LivingEntity.getTargetEntityInfo()
В 1.15 появились Server.getMobGoals(), который позволят полноценно добавлять, удалять и получать блоки интеллекта.
В 1.16 добавили удобный метод Mob.lookAt()
В 1.15 добавили методы LivingEntity.swingMainHand() и LivingEntity.swingOffHand()
В 1.19 появился и Mob.rayTraceEntitites() на замену старому LivingEntity.getTargetEntityInfo(), также для удобства добавили LivingEntity.swingHand()
Хотя полностью все задачи Paper до сих пор не покрывает. Лишь позволяет менять блоки интеллекта, но вот переопределить значения НМС-полей и логику НМС-методов по-прежнему нельзя.
Всё перечислять не буду, упомяну лишь наиболее интересные, на мой взгляд, возможности.
На версии 1.12 или даже старше появились EntityPathfindEvent и SentientNPC.setTarget(LivingEntity)
В 1.13 появилась возможность управлять передвижением моба при помощи Mob.getPathfinder(), а также с комфортом проводить трассировку при помощи LivingEntity.rayTraceBlocks(), LivingEntity.getTargetBlockInfo() и LivingEntity.getTargetEntityInfo()
В 1.15 появились Server.getMobGoals(), который позволят полноценно добавлять, удалять и получать блоки интеллекта.
Примеры возможно даже не тестировал, но для понимания принципа работы этого должно быть достаточно.
Копипаст блока из ядра ванильного сервера, который заставит детёныша моба ходить за своим родителем до взросления.
PHP:
import com.destroystokyo.paper.entity.ai.Goal;
import com.destroystokyo.paper.entity.ai.GoalKey;
import com.destroystokyo.paper.entity.ai.GoalType;
import lombok.NonNull;
import org.bukkit.entity.Animals;
import java.util.EnumSet;
public final class GoalFollowParentAnimal implements Goal<Animals> {
private static final GoalKey<Animals> KEY = AiHelper.key(GoalFollowParentAnimal.class, Animals.class);
private final Animals mob;
private Animals parent;
private final double movingSpeed;
private int cooldownTicks;
public GoalFollowParentAnimal(Animals mob, double movingSpeed) {
this.mob = mob;
this.movingSpeed = movingSpeed;
}
@Override
public boolean shouldActivate() {
if (this.mob.isAdult()) return false;
Animals parent = null;
double minDistance = Double.MAX_VALUE;
for (Animals animal : AiHelper.getNearbyEntitiesByType(
this.mob.getWorld(),
Animals.class,
this.mob.getBoundingBox().expand(8.0D, 4.0D, 8.0D),
null)
) {
if (!animal.isAdult()) continue;
double distance = this.mob.getLocation().distanceSquared(animal.getLocation());
if (distance > minDistance) continue;
minDistance = distance;
parent = animal;
}
if (parent == null) return false;
if (minDistance < 9.0D) return false;
this.parent = parent;
return true;
}
@Override
public boolean shouldStayActive() {
if (this.mob.isAdult()) return false;
if (this.parent.isDead()) return false;
double distanceSquired = this.mob.getLocation().distanceSquared(this.parent.getLocation());
return !(distanceSquired < 9.0D) && !(distanceSquired > 256.0D);
}
@Override
public void start() {
this.cooldownTicks = 0;
}
@Override
public void stop() {
this.parent = null;
}
@Override
public void tick() {
if (--this.cooldownTicks > 0) return;
this.cooldownTicks = 10;
this.mob.getPathfinder().moveTo(this.parent, this.movingSpeed);
}
@Override
@NonNull
public GoalKey<Animals> getKey() {
return KEY;
}
@Override
@NonNull
public EnumSet<GoalType> getTypes() {
return EnumSet.of(GoalType.MOVE, GoalType.LOOK);
}
}
Блок, который заставит мобов ходить за предметов в руке у других мобов.
PHP:
import com.destroystokyo.paper.entity.ai.Goal;
import com.destroystokyo.paper.entity.ai.GoalType;
import lombok.NonNull;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Mob;
import org.*********ventory.EntityEquipment;
import org.*********ventory.ItemStack;
import java.util.EnumSet;
public abstract class GoalFollowItemsInHand<M extends Mob, T extends LivingEntity> implements Goal<M> { // Mob, Target
private final M mob;
private final Class<T> targetClass;
private final double entitySearchRadius;
private final double walkingSpeed;
private final double stopWalkingDistanceRadiusSquired;
private final long cooldownTicksAfterStopping;
private T closestEntity;
private long cooldownTicks;
protected GoalFollowItemsInHand(M mob, Class<T> targetClass,
double entitySearchRadius,
double walkingSpeed,
double stopWalkingDistanceRadius,
long cooldownTicksAfterStopping
) {
this.mob = mob;
this.targetClass = targetClass;
this.entitySearchRadius = entitySearchRadius;
this.walkingSpeed = walkingSpeed;
this.stopWalkingDistanceRadiusSquired = Math.pow(stopWalkingDistanceRadius, 2);
this.cooldownTicksAfterStopping = cooldownTicksAfterStopping;
}
@Override
public boolean shouldActivate() {
if (this.cooldownTicks-- > 0) return false;
this.closestEntity = AiHelper.getClosestEntity(this.mob.getLocation(), this.entitySearchRadius, this.targetClass, this::isValidTarget);
if (this.closestEntity == null) return false;
EntityEquipment inv = this.closestEntity.getEquipment();
if (inv == null) {
throw new IllegalStateException("Сущности " + EntityTypeUtils.getTypeName(this.closestEntity) + " не имеют экипировки");
}
return this.isRequiredItem(inv.getItemInMainHand()) || this.isRequiredItem(inv.getItemInOffHand());
}
@Override
public void stop() {
this.mob.getPathfinder().stopPathfinding();
this.mob.setTarget(null);
this.cooldownTicks = this.cooldownTicksAfterStopping;
}
@Override
public void tick() {
this.mob.setTarget(this.closestEntity);
if (this.mob.getLocation().distanceSquared(this.closestEntity.getLocation()) < this.stopWalkingDistanceRadiusSquired) {
this.mob.getPathfinder().stopPathfinding();
} else {
this.mob.getPathfinder().moveTo(this.closestEntity, this.walkingSpeed);
}
}
@Override
@NonNull
public EnumSet<GoalType> getTypes() {
return EnumSet.of(GoalType.MOVE, GoalType.LOOK);
}
protected abstract boolean isValidTarget(@NonNull T target);
protected abstract boolean isRequiredItem(@NonNull ItemStack stack);
}
Заставляет деревенщин ходить за изумрудами в руках игрока. Работает благодаря классу-родителю GoalFollowItemsInHand из примера вышею
PHP:
import com.destroystokyo.paper.entity.ai.GoalKey;
import lombok.NonNull;
import org.bukkit.GameMode;
import org.bukkit.Material;
import org.bukkit.Tag;
import org.bukkit.entity.Player;
import org.bukkit.entity.Villager;
import org.*********ventory.ItemStack;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;
public final class GoalVillagerFollowPlayerEmeralds extends GoalFollowItemsInHand<Villager, Player> {
private static final Set<Material> REQUIRED_MATERIALS = new HashSet<>(Arrays.asList(Material.EMERALD, Material.EMERALD_BLOCK));
static {
REQUIRED_MATERIALS.addAll(Tag.EMERALD_ORES.getValues());
}
private static final GoalKey<Villager> KEY = AiHelper.key(GoalVillagerFollowPlayerEmeralds.class, Villager.class);
public GoalVillagerFollowPlayerEmeralds(@NonNull Villager villager) {
super(villager, Player.class,
10,
1.0,
2,
SchedulerHelper.toTicks(5, TimeUnit.SECONDS));
}
@Override
@NonNull
public GoalKey<Villager> getKey() {
return KEY;
}
@Override
public boolean isValidTarget(@NonNull Player target) {
return !target.isDead() && target.getGameMode() != GameMode.SPECTATOR && target.isValid();
}
@Override
protected boolean isRequiredItem(@NonNull ItemStack stack) {
return REQUIRED_MATERIALS.contains(stack.getType());
}
}
В 1.16 добавили удобный метод Mob.lookAt()
В 1.15 добавили методы LivingEntity.swingMainHand() и LivingEntity.swingOffHand()
В 1.19 появился и Mob.rayTraceEntitites() на замену старому LivingEntity.getTargetEntityInfo(), также для удобства добавили LivingEntity.swingHand()
Хотя полностью все задачи Paper до сих пор не покрывает. Лишь позволяет менять блоки интеллекта, но вот переопределить значения НМС-полей и логику НМС-методов по-прежнему нельзя.
Другие способы в сообщении ниже
Последнее редактирование: