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

eu.cloudnetservice.modules.npc.platform.bukkit.BukkitPlatformNPCManagement Maven / Gradle / Ivy

/*
 * Copyright 2019-2024 CloudNetService team & contributors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package eu.cloudnetservice.modules.npc.platform.bukkit;

import com.github.juliarn.npclib.api.NpcActionController;
import com.github.juliarn.npclib.api.Platform;
import com.github.juliarn.npclib.api.protocol.PlatformPacketAdapter;
import com.github.juliarn.npclib.bukkit.BukkitPlatform;
import com.github.juliarn.npclib.bukkit.BukkitWorldAccessor;
import com.github.juliarn.npclib.bukkit.protocol.BukkitProtocolAdapter;
import com.github.juliarn.npclib.ext.labymod.LabyModExtension;
import com.github.retrooper.packetevents.manager.server.ServerVersion;
import com.github.retrooper.packetevents.util.PEVersion;
import com.google.common.base.Preconditions;
import eu.cloudnetservice.driver.ComponentInfo;
import eu.cloudnetservice.driver.event.EventManager;
import eu.cloudnetservice.driver.provider.CloudServiceProvider;
import eu.cloudnetservice.driver.registry.injection.Service;
import eu.cloudnetservice.driver.service.ServiceEnvironmentType;
import eu.cloudnetservice.driver.service.ServiceInfoSnapshot;
import eu.cloudnetservice.driver.service.ServiceLifeCycle;
import eu.cloudnetservice.modules.bridge.WorldPosition;
import eu.cloudnetservice.modules.bridge.player.PlayerManager;
import eu.cloudnetservice.modules.npc.NPC;
import eu.cloudnetservice.modules.npc.configuration.NPCConfiguration;
import eu.cloudnetservice.modules.npc.platform.PlatformNPCManagement;
import eu.cloudnetservice.modules.npc.platform.PlatformSelectorEntity;
import eu.cloudnetservice.modules.npc.platform.bukkit.entity.EntityBukkitPlatformSelectorEntity;
import eu.cloudnetservice.modules.npc.platform.bukkit.entity.NPCBukkitPlatformSelector;
import eu.cloudnetservice.wrapper.configuration.WrapperConfiguration;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import java.util.concurrent.ThreadLocalRandom;
import lombok.NonNull;
import org.bukkit.Location;
import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.Plugin;
import org.bukkit.scheduler.BukkitScheduler;
import org.bukkit.scheduler.BukkitTask;
import org.bukkit.scoreboard.Scoreboard;
import org.bukkit.util.NumberConversions;

@Singleton
public class BukkitPlatformNPCManagement extends
  PlatformNPCManagement {

  protected final Plugin plugin;
  protected final Server server;
  protected final BukkitScheduler scheduler;
  protected final PlayerManager playerManager;

  protected final Platform npcPlatform;
  protected final BukkitTask knockBackTask;

  protected volatile BukkitTask npcEmoteTask;

  @Inject
  public BukkitPlatformNPCManagement(
    @NonNull Plugin plugin,
    @NonNull Server server,
    @NonNull BukkitScheduler scheduler,
    @NonNull EventManager eventManager,
    @NonNull ComponentInfo componentInfo,
    @NonNull @Service PlayerManager playerManager,
    @NonNull CloudServiceProvider cloudServiceProvider,
    @NonNull WrapperConfiguration wrapperConfiguration
  ) {
    super(eventManager, componentInfo, cloudServiceProvider, wrapperConfiguration);

    this.plugin = plugin;
    this.server = server;
    this.scheduler = scheduler;
    this.playerManager = playerManager;

    // npc pool init
    var entry = this.applicableNPCConfigurationEntry();
    if (entry != null) {
      this.npcPlatform = BukkitPlatform.bukkitNpcPlatformBuilder()
        .extension(plugin)
        .debug(true)
        .actionController(builder -> builder
          .flag(NpcActionController.SPAWN_DISTANCE, entry.npcPoolOptions().spawnDistance())
          .flag(NpcActionController.IMITATE_DISTANCE, entry.npcPoolOptions().actionDistance())
          .flag(NpcActionController.TAB_REMOVAL_TICKS, entry.npcPoolOptions().tabListRemoveTicks()))
        .worldAccessor(BukkitWorldAccessor.nameBasedAccessor())
        .packetFactory(this.resolvePacketAdapter())
        .build();
    } else {
      this.npcPlatform = BukkitPlatform.bukkitNpcPlatformBuilder()
        .extension(plugin)
        .worldAccessor(BukkitWorldAccessor.nameBasedAccessor())
        .packetFactory(this.resolvePacketAdapter())
        .build();
    }

    // start the emote player
    this.startEmoteTask(false);
    // start the knock back task
    this.knockBackTask = this.scheduler.runTaskTimer(plugin, () -> {
      var configEntry = this.applicableNPCConfigurationEntry();
      if (configEntry != null) {
        // check if knock back is enabled
        var distance = configEntry.knockbackDistance();
        var strength = configEntry.knockbackStrength();
        if (distance > 0 && strength > 0) {
          // select the knockback emote id now (sometimes we need to play them sync for all npcs)
          var labyModEmotes = configEntry.emoteConfiguration().onKnockbackEmoteIds();
          var emoteId = this.randomEmoteId(configEntry.emoteConfiguration(), labyModEmotes);
          //
          for (var value : this.trackedEntities.values()) {
            if (value.spawned()) {
              // select all nearby entities of each spawned mob
              var nearbyEntities = value.location().getWorld().getNearbyEntities(
                value.location(),
                distance,
                distance,
                distance);
              // loop over all entities and knock them back
              if (!nearbyEntities.isEmpty()) {
                for (var entity : nearbyEntities) {
                  // check if the entity is a player
                  if (entity instanceof Player player && !entity.hasPermission("cloudnet.npcs.knockback.bypass")) {
                    // apply the strength to the curren vector
                    var vector = player.getLocation().toVector().subtract(value.location().toVector())
                      .normalize()
                      .multiply(strength)
                      .setY(0.2);
                    if (NumberConversions.isFinite(vector.getX()) && NumberConversions.isFinite(vector.getZ())) {
                      // apply the velocity
                      player.setVelocity(vector);
                      // check if we should send a labymod emote
                      if (value instanceof NPCBukkitPlatformSelector npcSelector) {
                        if (emoteId == -1) {
                          var emote = labyModEmotes[ThreadLocalRandom.current().nextInt(0, labyModEmotes.length)];
                          LabyModExtension
                            .createEmotePacket(this.npcPlatform.packetFactory(), emote)
                            .schedule(player, npcSelector.handleNPC());
                        } else {
                          LabyModExtension
                            .createEmotePacket(this.npcPlatform.packetFactory(), emoteId)
                            .schedule(player, npcSelector.handleNPC());
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }, 20, 5);
  }

  @Override
  public void initialize() {
    super.initialize();

    // spawn all npcs that are in chunks that were loaded before the plugin was enabled
    for (var entity : this.trackedEntities.values()) {
      if (entity.canSpawn()) {
        entity.spawn();
      }
    }
  }

  @Override
  protected @NonNull PlatformSelectorEntity createSelectorEntity(
    @NonNull NPC base
  ) {
    return base.npcType() == NPC.NPCType.ENTITY
      ? new EntityBukkitPlatformSelectorEntity(base, this.plugin, this.server, this.scheduler, this.playerManager, this)
      : new NPCBukkitPlatformSelector(
        base,
        this.plugin,
        this.server,
        this.scheduler,
        this.playerManager,
        this,
        this.npcPlatform);
  }

  @Override
  public @NonNull WorldPosition toWorldPosition(@NonNull Location location, @NonNull String group) {
    Preconditions.checkNotNull(location.getWorld(), "world unloaded");
    return new WorldPosition(
      location.getX(),
      location.getY(),
      location.getZ(),
      location.getYaw(),
      location.getPitch(),
      location.getWorld().getName(),
      group);
  }

  @Override
  public @NonNull Location toPlatformLocation(@NonNull WorldPosition position) {
    var world = this.server.getWorld(position.world());
    return new Location(
      world,
      position.x(),
      position.y(),
      position.z(),
      (float) position.yaw(),
      (float) position.pitch());
  }

  @Override
  protected boolean shouldTrack(@NonNull ServiceInfoSnapshot service) {
    return service.lifeCycle() == ServiceLifeCycle.RUNNING
      && service.serviceId().environment().readProperty(ServiceEnvironmentType.JAVA_SERVER);
  }

  @Override
  public void handleInternalNPCConfigUpdate(@NonNull NPCConfiguration configuration) {
    super.handleInternalNPCConfigUpdate(configuration);
    // re-schedule the emote task if it's not yet running
    this.startEmoteTask(false);
  }

  public @NonNull Platform npcPlatform() {
    return this.npcPlatform;
  }

  protected void startEmoteTask(boolean force) {
    // only start the task if not yet running
    if (this.npcEmoteTask == null || force) {
      var ent = this.applicableNPCConfigurationEntry();
      if (ent != null && ent.emoteConfiguration().minEmoteDelayTicks() > 0) {
        // get the delay for the next npc emote play
        long delay;
        if (ent.emoteConfiguration().maxEmoteDelayTicks() > ent.emoteConfiguration().minEmoteDelayTicks()) {
          delay = ThreadLocalRandom.current().nextLong(
            ent.emoteConfiguration().minEmoteDelayTicks(),
            ent.emoteConfiguration().maxEmoteDelayTicks());
        } else {
          delay = ent.emoteConfiguration().minEmoteDelayTicks();
        }
        // run the task
        this.npcEmoteTask = this.scheduler.runTaskLaterAsynchronously(this.plugin, () -> {
          // select an emote to play
          var emotes = ent.emoteConfiguration().emoteIds();
          var emoteId = this.randomEmoteId(ent.emoteConfiguration(), emotes);
          // check if we can select an emote
          if (emoteId >= -1) {
            // play the emote on each npc
            for (var npc : this.npcPlatform.npcTracker().trackedNpcs()) {
              if (emoteId == -1) {
                var emote = emotes[ThreadLocalRandom.current().nextInt(0, emotes.length)];
                LabyModExtension.createEmotePacket(this.npcPlatform.packetFactory(), emote).scheduleForTracked(npc);
              } else {
                LabyModExtension.createEmotePacket(this.npcPlatform.packetFactory(), emoteId).scheduleForTracked(npc);
              }
            }
          }
          // re-schedule
          this.startEmoteTask(true);
        }, delay);
      } else {
        this.npcEmoteTask = null;
      }
    }
  }

  protected @NonNull PlatformPacketAdapter resolvePacketAdapter() {
    var bukkitVersion = this.server.getBukkitVersion();
    var parsedVersion = PEVersion.fromString(bukkitVersion.substring(0, bukkitVersion.indexOf("-")));
    var latestPEVersion = PEVersion.fromString(ServerVersion.getLatest().getReleaseName());
    if (parsedVersion.isNewerThan(latestPEVersion)) {
      this.plugin.getLogger().info("NPCs using ProtocolLib for version " + bukkitVersion);
      return BukkitProtocolAdapter.protocolLib();
    }

    this.plugin.getLogger().info("NPCs using PacketEvents for version " + bukkitVersion);
    return BukkitProtocolAdapter.packetEvents();
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy