All Downloads are FREE. Search and download functionalities are using the official Maven repository.

cn.nukkit.entity.passive.EntityWolf Maven / Gradle / Ivy

There is a newer version: 1.20.40-r1
Show newest version
package cn.nukkit.entity.passive;

import cn.nukkit.Player;
import cn.nukkit.api.PowerNukkitOnly;
import cn.nukkit.api.PowerNukkitXOnly;
import cn.nukkit.api.Since;
import cn.nukkit.entity.Entity;
import cn.nukkit.entity.EntityCanAttack;
import cn.nukkit.entity.ai.behavior.Behavior;
import cn.nukkit.entity.ai.behaviorgroup.BehaviorGroup;
import cn.nukkit.entity.ai.behaviorgroup.IBehaviorGroup;
import cn.nukkit.entity.ai.controller.LookController;
import cn.nukkit.entity.ai.controller.WalkController;
import cn.nukkit.entity.ai.evaluator.*;
import cn.nukkit.entity.ai.executor.*;
import cn.nukkit.entity.ai.memory.CoreMemoryTypes;
import cn.nukkit.entity.ai.route.finder.impl.SimpleFlatAStarRouteFinder;
import cn.nukkit.entity.ai.route.posevaluator.WalkingPosEvaluator;
import cn.nukkit.entity.ai.sensor.EntityAttackedByOwnerSensor;
import cn.nukkit.entity.ai.sensor.NearestPlayerSensor;
import cn.nukkit.entity.ai.sensor.NearestTargetEntitySensor;
import cn.nukkit.entity.ai.sensor.WolfNearestFeedingPlayerSensor;
import cn.nukkit.entity.data.ByteEntityData;
import cn.nukkit.entity.data.LongEntityData;
import cn.nukkit.entity.mob.EntitySkeleton;
import cn.nukkit.entity.mob.EntityStray;
import cn.nukkit.entity.mob.EntityWitherSkeleton;
import cn.nukkit.event.entity.EntityDamageByEntityEvent;
import cn.nukkit.event.entity.EntityDamageEvent;
import cn.nukkit.item.Item;
import cn.nukkit.item.ItemDye;
import cn.nukkit.item.ItemID;
import cn.nukkit.level.Sound;
import cn.nukkit.level.format.FullChunk;
import cn.nukkit.level.particle.ItemBreakParticle;
import cn.nukkit.math.Vector3;
import cn.nukkit.nbt.tag.CompoundTag;
import cn.nukkit.nbt.tag.FloatTag;
import cn.nukkit.nbt.tag.ListTag;
import cn.nukkit.network.protocol.EntityEventPacket;
import cn.nukkit.utils.DyeColor;
import cn.nukkit.utils.Utils;
import org.jetbrains.annotations.Nullable;

import java.util.List;
import java.util.Set;

import static cn.nukkit.entity.mob.EntityMob.DIFFICULTY_HAND_DAMAGE;

/**
 * @author BeYkeRYkt (Nukkit Project)
 * @author Cool_Loong (PowerNukkitX Project)
 * todo 野生狼不会被刷新
 */
public class EntityWolf extends EntityWalkingAnimal implements EntityTamable, EntityCanAttack {
    public static final int NETWORK_ID = 14;
    private Player owner = null;
    private String ownerName = "";
    private boolean sitting = false;
    private boolean angry = false;
    private DyeColor collarColor = DyeColor.RED;//项圈颜色
    private IBehaviorGroup behaviorGroup;
    private float[] diffHandDamage;

    public EntityWolf(FullChunk chunk, CompoundTag nbt) {
        super(chunk, nbt);
    }

    @Override
    public int getNetworkId() {
        return NETWORK_ID;
    }

    @Override
    public IBehaviorGroup getBehaviorGroup() {
        if (behaviorGroup == null) {
            behaviorGroup = new BehaviorGroup(
                    this.tickSpread,
                    Set.of(
                            //用于刷新InLove状态的核心行为
                            new Behavior(
                                    new InLoveExecutor(400),
                                    new AllMatchEvaluator(
                                            new PassByTimeEvaluator(CoreMemoryTypes.LAST_BE_FED_TIME, 0, 400),
                                            new PassByTimeEvaluator(CoreMemoryTypes.LAST_IN_LOVE_TIME, 6000, Integer.MAX_VALUE)
                                    ),
                                    1, 1
                            ),
                            //刷新攻击目标
                            new Behavior(
                                    entity -> {
                                        var storage = getMemoryStorage();
                                        var hasOwner = hasOwner();
                                        Entity attackTarget = null;
                                        var attackEvent = storage.get(CoreMemoryTypes.BE_ATTACKED_EVENT);
                                        EntityDamageByEntityEvent attackByEntityEvent = null;
                                        if (attackEvent instanceof EntityDamageByEntityEvent attackByEntityEv) attackByEntityEvent = attackByEntityEv;
                                        boolean validAttacker = attackByEntityEvent != null && attackByEntityEvent.getDamager().isAlive() && (!(attackByEntityEvent.getDamager() instanceof Player player) || player.isSurvival());
                                        if (hasOwner) {
                                            //已驯服
                                            if (storage.notEmpty(CoreMemoryTypes.ENTITY_ATTACKING_OWNER) && storage.get(CoreMemoryTypes.ENTITY_ATTACKING_OWNER).isAlive()) {
                                                //攻击攻击主人的生物
                                                attackTarget = storage.get(CoreMemoryTypes.ENTITY_ATTACKING_OWNER);
                                            } else if (storage.notEmpty(CoreMemoryTypes.ENTITY_ATTACKED_BY_OWNER) && storage.get(CoreMemoryTypes.ENTITY_ATTACKED_BY_OWNER).isAlive()) {
                                                //攻击主人攻击的生物
                                                attackTarget = storage.get(CoreMemoryTypes.ENTITY_ATTACKED_BY_OWNER);
                                            } else if (attackByEntityEvent != null && validAttacker && !attackByEntityEvent.getDamager().equals(getOwner())) {
                                                //攻击攻击自己的生物(主人例外)
                                                attackTarget = attackByEntityEvent.getDamager();
                                            } else if (storage.notEmpty(CoreMemoryTypes.NEAREST_SKELETON) && storage.get(CoreMemoryTypes.NEAREST_SKELETON).isAlive()) {
                                                //攻击最近的骷髅
                                                attackTarget = storage.get(CoreMemoryTypes.NEAREST_SKELETON);
                                            }
                                        } else {
                                            //未驯服
                                            if (attackByEntityEvent != null && validAttacker) {
                                                //攻击攻击自己的生物
                                                attackTarget = attackByEntityEvent.getDamager();
                                            } else if (storage.notEmpty(CoreMemoryTypes.WOLF_NEAREST_SUITABLE_ATTACK_TARGET) && storage.get(CoreMemoryTypes.WOLF_NEAREST_SUITABLE_ATTACK_TARGET).isAlive()) {
                                                //攻击最近的合适生物
                                                attackTarget = storage.get(CoreMemoryTypes.WOLF_NEAREST_SUITABLE_ATTACK_TARGET);
                                            }
                                        }
                                        storage.put(CoreMemoryTypes.ATTACK_TARGET, attackTarget);
                                        return true;
                                    },
                                    entity -> true, 1
                            )
                    ),
                    Set.of(
                            //攻击仇恨目标 todo 召集同伴
                            new Behavior(new WolfAttackExecutor(CoreMemoryTypes.ATTACK_TARGET, 0.35f, 33, true, 15),
                                    new MemoryCheckNotEmptyEvaluator(CoreMemoryTypes.ATTACK_TARGET)
                                    , 6, 1),
                            new Behavior(new EntityBreedingExecutor<>(EntityWolf.class, 16, 100, 0.35f), entity -> entity.getMemoryStorage().get(CoreMemoryTypes.IS_IN_LOVE), 5, 1),
                            new Behavior(new WolfMoveToOwnerExecutor(0.35f, true, 15), entity -> {
                                if (entity instanceof EntityWolf entityWolf && entityWolf.hasOwner()) {
                                    var player = entityWolf.getServer().getPlayer(entityWolf.getOwnerName());
                                    if (player == null) return false;
                                    if (!player.isOnGround()) return false;
                                    if (this.isSitting()) return false;
                                    var distanceSquared = entity.distanceSquared(player);
                                    return distanceSquared >= 100;
                                } else return false;
                            }, 4, 1),
                            new Behavior(new WolfLookFeedingPlayerExecutor(), new MemoryCheckNotEmptyEvaluator(CoreMemoryTypes.NEAREST_FEEDING_PLAYER), 3, 1),
                            new Behavior(new LookAtTargetExecutor(CoreMemoryTypes.NEAREST_PLAYER, 150), new ConditionalProbabilityEvaluator(3, 7, entity -> entityHasOwner(entity, false, false), 10),
                                    1, 1, 25),
                            new Behavior(new RandomRoamExecutor(0.1f, 12, 150, false, -1, true, 10),
                                    new ProbabilityEvaluator(5, 10), 1, 1, 50)
                    ),
                    Set.of(new WolfNearestFeedingPlayerSensor(7, 0),
                            new NearestPlayerSensor(8, 0, 20),
                            new NearestTargetEntitySensor<>(0, 20, 20,
                                    List.of(CoreMemoryTypes.WOLF_NEAREST_SUITABLE_ATTACK_TARGET, CoreMemoryTypes.NEAREST_SKELETON), this::attackTarget,
                                    entity -> switch (entity.getNetworkId()) {
                                        case EntitySkeleton.NETWORK_ID, EntityWitherSkeleton.NETWORK_ID, EntityStray.NETWORK_ID ->
                                                true;
                                        default -> false;
                                    }),
                            new EntityAttackedByOwnerSensor(5, false)
                    ),
                    Set.of(new WalkController(), new LookController(true, true)),
                    new SimpleFlatAStarRouteFinder(new WalkingPosEvaluator(), this)
            );
        }
        return behaviorGroup;
    }

    @Override
    public float getWidth() {
        if (isBaby()) {
            return 0.3f;
        }
        return 0.6f;
    }

    @Override
    public float getHeight() {
        if (isBaby()) {
            return 0.425f;
        }
        return 0.8f;
    }

    @PowerNukkitOnly
    @Since("1.5.1.0-PN")
    @Override
    public String getOriginalName() {
        return "Wolf";
    }

    @Override
    public void initEntity() {
        this.setMaxHealth(8);
        super.initEntity();

        if (this.namedTag.contains("Sitting")) {
            if (this.namedTag.getBoolean("Sitting")) {
                this.sitting = true;
                this.setDataFlag(DATA_FLAGS, DATA_FLAG_SITTING, true);
            }
        }

        if (this.namedTag.contains("Angry")) {
            if (this.namedTag.getBoolean("Angry")) {
                this.angry = true;
                this.setDataFlag(DATA_FLAGS, DATA_FLAG_ANGRY, true);
            }
        }

        if (this.namedTag.contains("CollarColor")) {
            var collarColor = DyeColor.getByDyeData(this.namedTag.getByte("CollarColor"));
            if (collarColor == null) {
                this.collarColor = DyeColor.RED;
                this.setDataProperty(new ByteEntityData(DATA_COLOUR, DyeColor.RED.getWoolData()));
            } else {
                this.collarColor = collarColor;
                this.setDataProperty(new ByteEntityData(DATA_COLOUR, collarColor.getWoolData()));
            }
        }

        if (this.namedTag.contains(DIFFICULTY_HAND_DAMAGE)) {
            var damageList = this.namedTag.getList(DIFFICULTY_HAND_DAMAGE, FloatTag.class);
            this.diffHandDamage[0] = damageList.get(0).getData();
            this.diffHandDamage[1] = damageList.get(1).getData();
            this.diffHandDamage[2] = damageList.get(2).getData();
        }

        if (this.diffHandDamage == null) {
            this.setDiffHandDamage(new float[]{3, 4, 6});
        }
    }

    @Override
    public void saveNBT() {
        super.saveNBT();
        this.namedTag.putBoolean("Angry", this.angry);
        this.namedTag.putByte("CollarColor", this.collarColor.getDyeData());
        this.namedTag.putBoolean("Sitting", this.sitting);
        this.namedTag.putString("OwnerName", this.ownerName);
        if (diffHandDamage != null)
            this.namedTag.putList(new ListTag(DIFFICULTY_HAND_DAMAGE).add(new FloatTag("", this.diffHandDamage[0])).add(new FloatTag("", this.diffHandDamage[1])).add(new FloatTag("", this.diffHandDamage[2])));
    }

    @Override
    public boolean onUpdate(int currentTick) {
        var result = super.onUpdate(currentTick);

        //initEntity的时候玩家还没连接,所以只能在onUpdate里面更新
        if (this.namedTag.contains("OwnerName") && this.owner == null) {
            String ownerName = namedTag.getString("OwnerName");
            if (ownerName != null && ownerName.length() > 0) {
                setOwnerName(ownerName);
            }
        }
        return result;
    }

    @Override
    public boolean onInteract(Player player, Item item, Vector3 clickedPos) {
        if (item.getId() == Item.NAME_TAG && !player.isAdventure()) {
            return applyNameTag(player, item);
        }

        int healable = this.getHealableItem(item);
        if (item.getId() == ItemID.BONE) {
            if (!this.hasOwner() && !this.isAngry()) {
                player.getInventory().decreaseCount(player.getInventory().getHeldItemIndex());
                if (Utils.rand(1, 3) == 3) {
                    EntityEventPacket packet = new EntityEventPacket();
                    packet.eid = this.getId();
                    packet.event = EntityEventPacket.TAME_SUCCESS;
                    player.dataPacket(packet);

                    this.setMaxHealth(20);
                    this.setHealth(20);
                    this.setOwnerName(player.getName());
                    this.setCollarColor(DyeColor.RED);
                    this.saveNBT();

                    this.getLevel().dropExpOrb(this, Utils.rand(1, 7));

                    return true;
                } else {
                    EntityEventPacket packet = new EntityEventPacket();
                    packet.eid = this.getId();
                    packet.event = EntityEventPacket.TAME_FAIL;
                    player.dataPacket(packet);
                }
            }
        } else if (item.getId() == Item.DYE) {
            if (this.hasOwner() && player.equals(this.getOwner())) {
                player.getInventory().decreaseCount(player.getInventory().getHeldItemIndex());
                this.setCollarColor(((ItemDye) item).getDyeColor());
                return true;
            }
        } else if (this.isBreedingItem(item)) {
            player.getInventory().decreaseCount(player.getInventory().getHeldItemIndex());
            this.getLevel().addSound(this, Sound.RANDOM_EAT);
            this.getLevel().addParticle(new ItemBreakParticle(this.add(0, getHeight() * 0.75F, 0), Item.get(item.getId(), 0, 1)));

            if (healable != 0) {
                this.setHealth(Math.max(this.getMaxHealth(), this.getHealth() + healable));
            }

            getMemoryStorage().put(CoreMemoryTypes.LAST_FEED_PLAYER, player);
            return true;
        } else if (this.hasOwner() && player.getName().equals(getOwnerName())) {
            this.setSitting(!this.isSitting());
            return true;
        }

        return false;
    }

    @Override
    public boolean attack(EntityDamageEvent source) {
        var result = super.attack(source);
        if (source instanceof EntityDamageByEntityEvent entityDamageByEntityEvent) {
            if (entityDamageByEntityEvent.getDamager() instanceof Player player && player.isCreative()) return result;
//            //更新仇恨目标
//            getMemoryStorage().put(CoreMemoryTypes.ATTACK_TARGET, entityDamageByEntityEvent.getDamager());
        }
        return result;
    }

    @PowerNukkitXOnly
    @Since("1.19.30-r1")
    @Override
    public String getOwnerName() {
        return this.ownerName;
    }

    @PowerNukkitXOnly
    @Since("1.19.30-r1")
    @Override
    public void setOwnerName(String playerName) {
        var player = getServer().getPlayerExact(playerName);
        if (player == null) return;
        this.owner = player;
        this.ownerName = playerName;
        this.setDataProperty(new LongEntityData(DATA_OWNER_EID, player.getId()));
        this.setTamed(true);
        this.namedTag.putString("OwnerName", this.ownerName);
    }

    @PowerNukkitXOnly
    @Since("1.19.30-r1")
    @Nullable
    @Override
    public Player getOwner() {
        return this.owner;
    }

    @PowerNukkitXOnly
    @Since("1.19.30-r1")
    @Override
    public boolean hasOwner() {
        return hasOwner(true);
    }

    @PowerNukkitXOnly
    @Since("1.19.30-r1")
    public boolean hasOwner(boolean checkOnline) {
        if (checkOnline) {
            if (this.ownerName == null || this.ownerName.isEmpty()) return false;
            var owner = getServer().getPlayerExact(this.ownerName);
            return owner != null;
        } else {
            return this.ownerName != null && !this.ownerName.isEmpty();
        }
    }

    @PowerNukkitXOnly
    @Since("1.19.30-r1")
    public boolean isSitting() {
        return this.sitting;
    }

    @PowerNukkitXOnly
    @Since("1.19.30-r1")
    public void setSitting(boolean sit) {
        this.sitting = sit;
        this.setDataFlag(DATA_FLAGS, DATA_FLAG_SITTING, sit);
        this.namedTag.putBoolean("Sitting", sit);
    }

    @PowerNukkitXOnly
    @Since("1.19.30-r1")
    private void setTamed(boolean tamed) {
        this.setDataFlag(DATA_FLAGS, DATA_FLAG_TAMED, tamed);
    }

    @PowerNukkitXOnly
    @Since("1.19.30-r1")
    public boolean isAngry() {
        return this.angry;
    }

    @PowerNukkitXOnly
    @Since("1.19.30-r1")
    public void setAngry(boolean angry) {
        this.angry = angry;
        this.setDataFlag(DATA_FLAGS, DATA_FLAG_ANGRY, angry);
        this.namedTag.putBoolean("Angry", angry);
    }

    @PowerNukkitXOnly
    @Since("1.19.30-r1")
    public void setCollarColor(DyeColor color) {
        this.collarColor = color;
        this.setDataProperty(new ByteEntityData(DATA_COLOUR, color.getWoolData()));
        this.namedTag.putByte("CollarColor", color.getDyeData());
    }

    @PowerNukkitXOnly
    @Since("1.19.30-r1")
    @Override
    public boolean isBreedingItem(Item item) {
        return item.getId() == ItemID.RAW_CHICKEN ||
                item.getId() == ItemID.COOKED_CHICKEN ||
                item.getId() == ItemID.RAW_BEEF ||
                item.getId() == ItemID.COOKED_BEEF ||
                item.getId() == ItemID.RAW_MUTTON ||
                item.getId() == ItemID.COOKED_MUTTON ||
                item.getId() == ItemID.RAW_PORKCHOP ||
                item.getId() == ItemID.COOKED_PORKCHOP ||
                item.getId() == ItemID.RAW_RABBIT ||
                item.getId() == ItemID.COOKED_RABBIT ||
                item.getId() == ItemID.ROTTEN_FLESH;
    }

    /**
     * 获得可以治疗狼的物品的治疗量
     */
    @PowerNukkitXOnly
    @Since("1.19.30-r1")
    public int getHealableItem(Item item) {
        return switch (item.getId()) {
            case ItemID.RAW_PORKCHOP, ItemID.RAW_BEEF, ItemID.RAW_RABBIT -> 3;
            case ItemID.COOKED_PORKCHOP, ItemID.COOKED_BEEF -> 8;
            case ItemID.RAW_FISH, ItemID.RAW_SALMON, ItemID.RAW_CHICKEN, ItemID.RAW_MUTTON -> 2;
            case ItemID.CLOWNFISH, ItemID.PUFFERFISH -> 1;
            case ItemID.COOKED_FISH, ItemID.COOKED_RABBIT -> 5;
            case ItemID.COOKED_SALMON, ItemID.COOKED_CHICKEN, ItemID.COOKED_MUTTON -> 6;
            case ItemID.ROTTEN_FLESH -> 4;
            case ItemID.RABBIT_STEW -> 10;
            default -> 0;
        };
    }

    //兔子、狐狸、骷髅及其变种、羊驼、绵羊和小海龟。然而它们被羊驼啐唾沫时会逃跑。
    @PowerNukkitXOnly
    @Since("1.19.30-r1")
    public boolean attackTarget(Entity entity) {
        return switch (entity.getNetworkId()) {
            case EntityRabbit.NETWORK_ID, EntityFox.NETWORK_ID, EntitySkeleton.NETWORK_ID, EntityWitherSkeleton.NETWORK_ID, EntityStray.NETWORK_ID, EntityLlama.NETWORK_ID,
                    EntitySheep.NETWORK_ID, EntityTurtle.NETWORK_ID -> true;
            default -> false;
        };
    }

    @PowerNukkitXOnly
    @Since("1.19.30-r1")
    private boolean entityHasOwner(Entity entity, boolean checkOnline, boolean defaultValue) {
        if (entity instanceof EntityWolf entityWolf) {
            return entityWolf.hasOwner(checkOnline);
        } else return defaultValue;
    }

    @Override
    public float[] getDiffHandDamage() {
        return this.diffHandDamage;
    }

    @Override
    public void setDiffHandDamage(float[] damages) {
        this.diffHandDamage = damages;
        this.namedTag.putList(new ListTag(DIFFICULTY_HAND_DAMAGE).add(new FloatTag("", this.diffHandDamage[0])).add(new FloatTag("", this.diffHandDamage[1])).add(new FloatTag("", this.diffHandDamage[2])));
    }

    @Override
    public float getDiffHandDamage(int difficulty) {
        return diffHandDamage[difficulty - 1];
    }

    @Override
    public void setDiffHandDamage(int difficulty, float damage) {
        this.diffHandDamage[difficulty - 1] = damage;
        this.namedTag.putList(new ListTag(DIFFICULTY_HAND_DAMAGE).add(new FloatTag("", this.diffHandDamage[0])).add(new FloatTag("", this.diffHandDamage[1])).add(new FloatTag("", this.diffHandDamage[2])));
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy