-
-
Save hugo4715/808542f9cca7f2942d29 to your computer and use it in GitHub Desktop.
NPC library written for MC 1.8 - spawns and maintains human NPCs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
GNU GENERAL PUBLIC LICENSE | |
Version 3, 29 June 2007 | |
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> | |
Everyone is permitted to copy and distribute verbatim copies | |
of this license document, but changing it is not allowed. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Copyright (C) 2015 Mark Hendriks | |
* | |
* This file is part of DarkSeraphim's NPC library. | |
* | |
* DarkSeraphim's NPC library is free software: you can redistribute it | |
* and/or modify it under the terms of the GNU General Public License | |
* as published by the Free Software Foundation, either version 3 of | |
* the License, or (at your option) any later version. | |
* | |
* DarkSeraphim's NPC library is distributed in the hope that it will be useful, | |
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
* GNU General Public License for more details. | |
* | |
* You should have received a copy of the GNU General Public License | |
* along with DarkSeraphim's NPC library. If not, see <http://www.gnu.org/licenses/>. | |
*/ | |
package net.darkseraphim.npc; | |
import org.bukkit.Bukkit; | |
import org.bukkit.Location; | |
import org.bukkit.entity.Player; | |
import org.bukkit.event.EventHandler; | |
import org.bukkit.event.Listener; | |
import org.bukkit.event.player.PlayerJoinEvent; | |
import org.bukkit.event.player.PlayerQuitEvent; | |
import org.bukkit.plugin.java.JavaPlugin; | |
/** | |
* | |
* @author DarkSeraphim. | |
*/ | |
public class Example extends JavaPlugin implements Listener | |
{ | |
private NPC npc; | |
@Override | |
public void onEnable() | |
{ | |
this.npc = NPC.createNPC(this, "NPC", new Location(Bukkit.getWorlds().get(0), 0, 0, 0)); | |
Bukkit.getPluginManager().registerEvents(this, this); | |
} | |
@EventHandler | |
public void onJoin(PlayerJoinEvent event) | |
{ | |
Player player = event.getPlayer(); | |
Location loc = player.getLocation(); | |
this.npc.setPositionRotation(loc.getX(), loc.getY(), loc.getZ(), loc.getYaw(), loc.getPitch()); | |
Bukkit.getScheduler().runTaskLater(this, () -> this.npc.spawn(player), 20L); | |
} | |
@EventHandler | |
public void onQuit(PlayerQuitEvent event) | |
{ | |
this.npc.despawn(event.getPlayer(), true); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Copyright (C) 2015 Mark Hendriks | |
* | |
* This file is part of DarkSeraphim's NPC library. | |
* | |
* DarkSeraphim's NPC library is free software: you can redistribute it | |
* and/or modify it under the terms of the GNU General Public License | |
* as published by the Free Software Foundation, either version 3 of | |
* the License, or (at your option) any later version. | |
* | |
* DarkSeraphim's NPC library is distributed in the hope that it will be useful, | |
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
* GNU General Public License for more details. | |
* | |
* You should have received a copy of the GNU General Public License | |
* along with DarkSeraphim's NPC library. If not, see <http://www.gnu.org/licenses/>. | |
*/ | |
package net.darkseraphim.npc; | |
import com.google.common.base.Charsets; | |
import com.mojang.authlib.GameProfile; | |
import net.minecraft.server.v1_8_R1.EntityPlayer; | |
import net.minecraft.server.v1_8_R1.EnumPlayerInfoAction; | |
import net.minecraft.server.v1_8_R1.IChatBaseComponent; | |
import net.minecraft.server.v1_8_R1.MinecraftServer; | |
import net.minecraft.server.v1_8_R1.Packet; | |
import net.minecraft.server.v1_8_R1.PacketPlayOutEntityDestroy; | |
import net.minecraft.server.v1_8_R1.PacketPlayOutNamedEntitySpawn; | |
import net.minecraft.server.v1_8_R1.PacketPlayOutPlayerInfo; | |
import net.minecraft.server.v1_8_R1.PlayerConnection; | |
import net.minecraft.server.v1_8_R1.PlayerInteractManager; | |
import net.minecraft.server.v1_8_R1.WorldServer; | |
import org.bukkit.Bukkit; | |
import org.bukkit.Location; | |
import org.bukkit.World; | |
import org.bukkit.craftbukkit.v1_8_R1.CraftWorld; | |
import org.bukkit.craftbukkit.v1_8_R1.entity.CraftPlayer; | |
import org.bukkit.entity.Player; | |
import org.bukkit.plugin.Plugin; | |
import org.bukkit.scheduler.BukkitTask; | |
import java.util.Arrays; | |
import java.util.HashSet; | |
import java.util.List; | |
import java.util.Set; | |
import java.util.UUID; | |
import java.util.function.Consumer; | |
import java.util.function.Function; | |
import java.util.stream.Stream; | |
/** | |
* @author DarkSeraphim. | |
*/ | |
public class NPC extends EntityPlayer | |
{ | |
// Returns the Packets required to spawn a NPC in form of a List | |
private static final Function<NPC, List<Packet>> getSpawnPackets = (npc) -> | |
{ | |
if(npc.spawnPackets == null) | |
{ | |
Packet[] packets = new Packet[] | |
{ | |
new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.ADD_PLAYER, npc), | |
new PacketPlayOutNamedEntitySpawn(npc), | |
new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.REMOVE_PLAYER, npc) | |
}; | |
npc.spawnPackets = Arrays.asList(packets); | |
} | |
return npc.spawnPackets; | |
}; | |
// Returns the Packet required to despawn a NPC | |
private static final Function<NPC, Consumer<PlayerConnection>> getDespawnPacket = (npc) -> | |
{ | |
if(npc.despawnPacket == null) | |
{ | |
npc.despawnPacket = new PacketPlayOutEntityDestroy(npc.getId()); | |
} | |
return (connection) -> connection.sendPacket(npc.despawnPacket); | |
}; | |
private final Plugin plugin; | |
private List<Packet> spawnPackets; | |
private Packet despawnPacket; | |
private Set<UUID> tracked = new HashSet<>(); | |
private BukkitTask task; | |
private NPC(Plugin plugin, WorldServer world, GameProfile profile, PlayerInteractManager manager) | |
{ | |
super(MinecraftServer.getServer(), world, profile, manager); | |
this.plugin = plugin; | |
this.ping = 100; | |
} | |
/** | |
* @return the name shown in the tab list | |
*/ | |
@Override | |
public IChatBaseComponent getPlayerListName() | |
{ | |
// Override to set a name | |
// Not like we would need it, we don't even want a tab list entry | |
return null; | |
} | |
/** | |
* Creates an NPC, owned by {@code plugin}, with name {@code name} at {@code location}. | |
* | |
* @param plugin Plugin which owns the NPC. | |
* @param name Name of the NPC. | |
* @param location Location where the NPC should spawn. | |
* @return NPC which was created. | |
*/ | |
public static NPC createNPC(Plugin plugin, String name, Location location) | |
{ | |
GameProfile profile = new GameProfile(UUID.nameUUIDFromBytes(("NPC:" + name).getBytes(Charsets.UTF_8)), name); | |
WorldServer world = getWorld(location.getWorld()); | |
NPC npc = new NPC(plugin, world, profile, new PlayerInteractManager(world)); | |
npc.setLocation(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); | |
return npc; | |
} | |
// Returns the NMS World from the Bukkit World | |
private static WorldServer getWorld(World world) | |
{ | |
return ((CraftWorld) world).getHandle(); | |
} | |
// Returns the PlayerConnection of the Player | |
private PlayerConnection getConnection(Player player) | |
{ | |
return ((CraftPlayer) player).getHandle().playerConnection; | |
} | |
/** | |
* Spawns (shows) this NPC for Player. | |
* | |
* @param player player to spawn NPC for. | |
*/ | |
public void spawn(Player player) | |
{ | |
Bukkit.getScheduler().runTaskLater(this.plugin, () -> | |
{ | |
PlayerConnection connection = getConnection(player); | |
NPC.getSpawnPackets.apply(this).stream().forEach(connection::sendPacket); | |
this.tracked.add(player.getUniqueId()); | |
if (this.task == null) | |
{ | |
this.task = new NPCTrackerTask(this).runTaskTimer(this.plugin, 0L, 5L); | |
} | |
}, 1L); | |
} | |
/** | |
* Returns a Stream of all Players which track this NPC. | |
* | |
* @return a {@code Stream<Player>} of tracked Players | |
*/ | |
protected Stream<Player> getTrackedPlayers() | |
{ | |
return this.tracked.stream().map(Bukkit::getPlayer).filter(player -> player != null); | |
} | |
/** | |
* Despawns (hides) NPC for the given Player. | |
* | |
* @param player player to despawn NPC for. | |
*/ | |
public void despawn(Player player) | |
{ | |
despawn(player, false); | |
} | |
/** | |
* Despawns (hides) NPC for the given Player. | |
* | |
* <i>Note: always invoke this method onQuit. It's good memory management!</i> | |
* | |
* @param player player to despawn NPC for. | |
* @param logout whether it was triggered by a logout. | |
*/ | |
public void despawn(Player player, boolean logout) | |
{ | |
if(this.tracked.remove(player.getUniqueId()) && this.tracked.isEmpty()) | |
{ | |
this.task.cancel(); | |
this.task = null; | |
} | |
if(!logout) | |
{ | |
cleanup(player); | |
} | |
} | |
/** | |
* Sends a PacketPlayOutDestroyEntity to the Player. | |
* | |
* @param player Player which will receive the packet | |
*/ | |
protected void cleanup(Player player) | |
{ | |
PlayerConnection connection = getConnection(player); | |
NPC.getDespawnPacket.apply(this).accept(connection); | |
} | |
/** | |
* Auxiliary method which returns the distance^2 between | |
* the Player and this NPC | |
* @param player Player with which the distance should be computed for. | |
* @return the distance^2 between the Player and this NPC | |
*/ | |
protected double getDistanceSquared(Player player) | |
{ | |
Location loc = player.getLocation(); | |
double dx = loc.getX() - this.locX; | |
double dy = loc.getY() - this.locY; | |
double dz = loc.getZ() - this.locZ; | |
return dx * dx + dy * dy + dz * dz; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Copyright (C) 2015 Mark Hendriks | |
* | |
* This file is part of DarkSeraphim's NPC library. | |
* | |
* DarkSeraphim's NPC library is free software: you can redistribute it | |
* and/or modify it under the terms of the GNU General Public License | |
* as published by the Free Software Foundation, either version 3 of | |
* the License, or (at your option) any later version. | |
* | |
* DarkSeraphim's NPC library is distributed in the hope that it will be useful, | |
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
* GNU General Public License for more details. | |
* | |
* You should have received a copy of the GNU General Public License | |
* along with DarkSeraphim's NPC library. If not, see <http://www.gnu.org/licenses/>. | |
*/ | |
package net.darkseraphim.npc; | |
import org.bukkit.entity.Player; | |
import org.bukkit.scheduler.BukkitRunnable; | |
import java.util.AbstractMap; | |
import java.util.Arrays; | |
import java.util.HashSet; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.Set; | |
import java.util.UUID; | |
import java.util.function.Consumer; | |
import java.util.function.Function; | |
import java.util.function.Predicate; | |
/** | |
* @author DarkSeraphim. | |
*/ | |
public class NPCTrackerTask extends BukkitRunnable | |
{ | |
/** | |
* Composition of multiple Consumers, allowing you to chain Consumers | |
* in one lambda call. | |
* | |
* @param <T> Generic type of composed consumers | |
*/ | |
private static class CompositeConsumer<T> implements Consumer<T> | |
{ | |
private final List<Consumer<T>> consumers; | |
private CompositeConsumer(List<Consumer<T>> consumers) | |
{ | |
this.consumers = consumers; | |
} | |
/** | |
* Accepts t for all contained Consumers. | |
* | |
* @param t parameter to accept | |
*/ | |
@Override | |
public void accept(T t) | |
{ | |
this.consumers.stream().forEach(consumer -> consumer.accept(t)); | |
} | |
/** | |
* Creates a CompositeConsumer of zero or more consumers. | |
* | |
* @param consumers Consumers to compose. | |
* @param <T> Generic type of the Consumers. | |
* @return A composition of the given consumers. | |
*/ | |
@SafeVarargs | |
private static <T> Consumer<T> of(Consumer<T>... consumers) | |
{ | |
return new CompositeConsumer<>(Arrays.asList(consumers)); | |
} | |
} | |
/** | |
* Predicate which only returns true once, and will return only return true once | |
* there has been at least one validation which resulted to false. | |
* @param <T> Predicate type | |
*/ | |
private static class CachedPredicate<T extends Map.Entry<Player, Boolean>> implements Predicate<T> | |
{ | |
private final Predicate<T> original; | |
private final Set<UUID> cache = new HashSet<>(); | |
private CachedPredicate(Predicate<T> original) | |
{ | |
this.original = original; | |
} | |
/** | |
* Tests if the predicate holds with the given variable, if and only if it previously returned false | |
* | |
* @param t variable to test | |
* @return {@code result && !this.cache.contains(t.getKey().getUniqueId())} | |
* @modifies adds {@code t.getKey().getUniqueId()} to the cache if \result is true, | |
* or removes it if \result is false | |
*/ | |
@Override | |
public boolean test(T t) | |
{ | |
boolean result = this.original.test(t); | |
UUID uuid = t.getKey().getUniqueId(); | |
if (result) | |
{ | |
return this.cache.add(uuid); | |
} | |
else | |
{ | |
this.cache.remove(uuid); | |
} | |
return false; | |
} | |
} | |
// Distance to respawn, customise to your liking | |
private static final double DISTANCE = 50; | |
private static final double DISTANCE_SQUARED = DISTANCE * DISTANCE; | |
private final Function<Player, Map.Entry<Player, Boolean>> inRange; | |
private final NPC npc; | |
private final Consumer<Player> update; | |
private final Predicate<Map.Entry<Player, Boolean>> filter; | |
protected NPCTrackerTask(NPC npc) | |
{ | |
this.npc = npc; | |
this.inRange = player -> new AbstractMap.SimpleEntry<>(player, this.npc.getDistanceSquared(player) < DISTANCE_SQUARED); | |
Consumer<Player> cleanup = this.npc::cleanup; | |
Consumer<Player> spawn = this.npc::spawn; | |
this.update = CompositeConsumer.<Player>of(cleanup, spawn); | |
this.filter = new CachedPredicate<>(Map.Entry::getValue); | |
} | |
/** | |
* Respawns the NPC for any players which moved within {@code DISTANCE} of the NPC. | |
*/ | |
@Override | |
public void run() | |
{ | |
this.npc.getTrackedPlayers().map(this.inRange) | |
.filter(this.filter) | |
.map(Map.Entry::getKey) | |
.forEach(this.update); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment