Paste #115398: Unnamed Java Paste

Date: 2023/09/19 07:48:17 UTC-07:00
Type: Java

View Raw Paste Download This Paste
Copy Link


package org.mineacademy.boss.hook;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import javax.annotation.Nullable;

import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.mineacademy.boss.model.Boss;
import org.mineacademy.boss.model.BossCitizensSettings;
import org.mineacademy.boss.model.BossRegion;
import org.mineacademy.boss.model.SpawnedBoss;
import org.mineacademy.fo.Common;
import org.mineacademy.fo.MinecraftVersion;
import org.mineacademy.fo.MinecraftVersion.V;
import org.mineacademy.fo.PlayerUtil;
import org.mineacademy.fo.collection.expiringmap.ExpiringMap;
import org.mineacademy.fo.jsonsimple.JSONObject;
import org.mineacademy.fo.jsonsimple.JSONParser;
import org.mineacademy.fo.remain.CompEquipmentSlot;
import org.mineacademy.fo.remain.Remain;

import net.citizensnpcs.api.CitizensAPI;
import net.citizensnpcs.api.ai.EntityTarget;
import net.citizensnpcs.api.ai.Goal;
import net.citizensnpcs.api.ai.Navigator;
import net.citizensnpcs.api.ai.goals.WanderGoal;
import net.citizensnpcs.api.npc.MetadataStore;
import net.citizensnpcs.api.npc.NPC;
import net.citizensnpcs.api.trait.trait.Equipment;
import net.citizensnpcs.api.trait.trait.Equipment.EquipmentSlot;
import net.citizensnpcs.trait.SkinTrait;

/**
 * Connector to Citizens for integration.
 */
public final class CitizensHook {

    /*
     * Skin cache
     */
    private static final Map<String, JSONObject> cache = ExpiringMap.builder().expiration(30, TimeUnit.MINUTES).build();

    /**
     * Register our custom traits
     */
    public static void registerTraits() {
        if (MinecraftVersion.atLeast(V.v1_9))
            try {
                net.citizensnpcs.api.CitizensAPI.getTraitFactory().registerTrait(net.citizensnpcs.api.trait.TraitInfo.create(MaxHealthTrait.class));
            } catch (final Throwable t) {
                // Ignore, no implementation for the current MC version
            }
    }

    /**
     * Spawns the given Boss at the location with help of Citizens
     *
     * @param boss
     * @param location
     * @return
     */
    public static Entity spawn(Boss boss, Location location) {
        final String alias = boss.getAlias();
        final NPC npc = CitizensAPI.getNPCRegistry().createNPC(boss.getType(), alias.length() > 16 ? alias.substring(0, 16) : alias);

        // Apply attributes
        update(boss, npc);

        // Spawn
        if (!npc.spawn(location)) {
            npc.destroy();

            return null;
        }

        // Set initial health to max and save
        ((LivingEntity) npc.getEntity()).setHealth(boss.getMaxHealth());
        final MaxHealthTrait trait = npc.getOrAddTrait(MaxHealthTrait.class);

        if (trait != null)
            trait.health = ((LivingEntity) npc.getEntity()).getHealth();

        return npc.getEntity();
    }

    /**
     * Updates Boss behavior for the spawned Boss entity
     *
     * @param boss
     * @param entity
     */
    public static void update(Boss boss, Entity entity) {
        final NPC npc = CitizensAPI.getNPCRegistry().getNPC(entity);

        if (npc == null)
            return;

        if (!npc.isSpawned())
            npc.destroy();

        update(boss, npc);
    }

    /**
     * Updates Boss behavior for the spawned Boss entity
     *
     * @param boss
     * @param npc
     */
    public static void update(Boss boss, NPC npc) {

        final BossCitizensSettings citizens = boss.getCitizensSettings();
        final MetadataStore data = npc.data();

        npc.setProtected(false);

        npc.getOrAddTrait(MaxHealthTrait.class);

        final Equipment equipment = npc.getOrAddTrait(Equipment.class);

        for (final CompEquipmentSlot slot : CompEquipmentSlot.values()) {
            final ItemStack item = boss.getEquipmentItem(slot);

            equipment.set(EquipmentSlot.valueOf(slot.getBukkitName()), item);
        }

        data.setPersistent("BossName", boss.getName());

        if (citizens.getDeathSound() != null)
            data.setPersistent(NPC.Metadata.DEATH_SOUND, citizens.getDeathSound());

        if (citizens.getHurtSound() != null)
            data.setPersistent(NPC.Metadata.HURT_SOUND, citizens.getHurtSound());

        if (citizens.getAmbientSound() != null)
            data.setPersistent(NPC.Metadata.AMBIENT_SOUND, citizens.getAmbientSound());

        data.setPersistent(NPC.Metadata.DROPS_ITEMS, true);
        data.setPersistent(NPC.Metadata.COLLIDABLE, true);
        data.setPersistent(NPC.Metadata.DAMAGE_OTHERS, true);

        if (citizens.isTargetGoalEnabled()) {
            final Goal goal = BossTargetNearbyEntityGoal.builder(npc)
                    .aggressive(citizens.isTargetGoalAggressive())
                    .radius(citizens.getTargetGoalRadius())
                    .targets(citizens.getTargetGoalEntities())
                    .build();

            npc.getDefaultGoalController().addGoal(goal, 1);
        }

        if (citizens.isWanderGoalEnabled())
            npc.getDefaultGoalController().addGoal(WanderGoal.builder(npc).xrange(citizens.getWanderGoalRadius()).yrange(citizens.getWanderGoalRadius()).build(), citizens.getWanderGoalRadius());
        else
            npc.getDefaultGoalController().cancelCurrentExecution();

        final Navigator gps = npc.getNavigator();

        // Fix weird slow motion issue
        if (boss.getType() == EntityType.PLAYER) {
            gps.getLocalParameters().speedModifier(1F);
            gps.getLocalParameters().speed(1F);
            gps.getLocalParameters().baseSpeed(4F);
        }

        // https://github.com/kangarko/Boss/issues/1195#issuecomment-1692729888
        gps.getLocalParameters().distanceMargin(0.5).pathDistanceMargin(0.5);

        // Fallback to MC AI
        if (!citizens.isTargetGoalEnabled() && !citizens.isWanderGoalEnabled())
            npc.setUseMinecraftAI(true);

        // Remove teleporting to players when far away
        npc.getNavigator().getDefaultParameters().stuckAction(null);

        // Set skin and finalize
        setSkinUrl(boss, npc);
    }

    /*
     * Helper to set Skin URL
     */
    private static void setSkinUrl(Boss boss, NPC npc) {
        final String skin = boss.getCitizensSettings().getSkinOrAlias();
        final SkinTrait trait = npc.getOrAddTrait(SkinTrait.class);

        if (skin.startsWith("http://") || skin.startsWith("https://")) {

            Common.runAsync(() -> {
                final JSONObject output = cache.get(skin);

                if (output != null) {
                    setSkin0(npc, trait, cache.get(skin));

                    return;
                }

                try {
                    final URL target = new URL("https://api.mineskin.org/generate/url");
                    final HttpURLConnection connection = (HttpURLConnection) target.openConnection();

                    connection.setRequestMethod("POST");
                    connection.setDoOutput(true);
                    connection.setConnectTimeout(2_000);
                    connection.setReadTimeout(5_000);

                    try (DataOutputStream out = new DataOutputStream(connection.getOutputStream())) {
                        out.writeBytes("url=" + URLEncoder.encode(skin, "UTF-8"));
                        out.close();

                        try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
                            final JSONObject newOutput = (JSONObject) JSONParser.deserialize(reader);
                            connection.disconnect();

                            Common.runLater(() -> {
                                setSkin0(npc, trait, newOutput);

                                cache.put(skin, newOutput);
                            });
                        }
                    }

                } catch (final Throwable t) {
                    Common.error(t, "Failed fetching NPC's " + npc.getName() + " skin from URL, check if it is valid.", "URL:" + skin);
                }
            });

        } else
            trait.setSkinName(skin, true);
    }

    /*
     * Helper method to set the persistent skin trait
     */
    private static void setSkin0(NPC npc, SkinTrait trait, JSONObject output) {
        final JSONObject data = (JSONObject) output.get("data");
        final String uuid = (String) data.get("uuid");

        final JSONObject texture = (JSONObject) data.get("texture");
        final String textureEncoded = (String) texture.get("value");
        final String signature = (String) texture.get("signature");

        trait.setSkinPersistent(uuid, signature, textureEncoded);
    }

    /**
     * Attempts to destroy NPC associated with the given NPC
     *
     * @param entity
     */
    public static void destroy(Entity entity) {
        final NPC npc = CitizensAPI.getNPCRegistry().getNPC(entity);

        if (npc != null)
            npc.destroy();
    }

    /**
     * Retargets the closest entity for the given boss
     *
     * @param spawnedBoss the boss
     */
    public static void retarget(SpawnedBoss spawnedBoss) {
        final Entity entity = spawnedBoss.getEntity();
        final Boss boss = spawnedBoss.getBoss();

        final BossRegion spawnRegion = boss.isKeptInSpawnRegion() ? spawnedBoss.getSpawnRegion() : null;

        final NPC npc = CitizensAPI.getNPCRegistry().getNPC(entity);
        final BossCitizensSettings citizens = boss.getCitizensSettings();

        if (npc != null && citizens.isTargetGoalEnabled()) {

            final List<Entity> potentialTargets = new ArrayList<>();
            final EntityTarget oldTarget = npc.getNavigator().getEntityTarget();

            for (final Entity nearby : Remain.getNearbyEntities(entity.getLocation(), citizens.getTargetGoalRadius())) {

                // Ignore same type entities
                if (entity.getType() == nearby.getType())
                    continue;

                // Do not target self
                if (entity.getUniqueId().equals(nearby.getUniqueId()))
                    continue;

                // Only retarget to someone else
                if (oldTarget != null && oldTarget.getTarget().getUniqueId().equals(nearby.getUniqueId()))
                    continue;

                // Ignore entities out of reach
                if (spawnRegion != null && !spawnRegion.isWithin(nearby.getLocation()))
                    continue;

                if (nearby instanceof Player) {
                    final Player player = (Player) nearby;

                    if (player.getGameMode() != GameMode.SURVIVAL || PlayerUtil.isVanished(player))
                        continue;
                }

                final SpawnedBoss nearbyBoss = Boss.findBoss(nearby);

                if (nearbyBoss != null && nearbyBoss.getBoss().equals(boss))
                    continue;

                if (citizens.getTargetGoalEntities().contains(nearby.getType()))
                    potentialTargets.add(nearby);
            }

            if (!potentialTargets.isEmpty()) {
                Collections.shuffle(potentialTargets);

                npc.getNavigator().setTarget(potentialTargets.get(0), citizens.isTargetGoalAggressive());
            }
        }

        // Remove teleporting to players when far away
        if (npc != null)
            npc.getNavigator().getDefaultParameters().stuckAction(null);
    }

    /**
     * Sends the given Boss to the given location
     *
     * @param boss
     * @param target
     */
    public static void sendToLocation(SpawnedBoss boss, Location target) {
        final NPC npc = CitizensAPI.getNPCRegistry().getNPC(boss.getEntity());

        if (npc != null)
            npc.getNavigator().setTarget(target);
    }

    /**
     * Attempts to find a boss from the given NPC.
     *
     * @param entity
     * @return
     */
    @Nullable
    public static SpawnedBoss findNPC(Entity entity) {
        final NPC npc = CitizensAPI.getNPCRegistry().getNPC(entity);

        if (npc != null) {
            final String bossName = npc.data().get("BossName");

            if (bossName != null) {
                final Boss boss = Boss.findBoss(bossName);

                if (boss != null)
                    return new SpawnedBoss(boss, (LivingEntity) entity);
            }
        }

        return null;
    }
}


Highlighting for 'Other' types handled by Highlight.JS, which was released under the BSD 3-Clause License.