Move Entity creation logic into EntityController, add /npc type, reduce Location object churn

This commit is contained in:
fullwall 2012-12-08 20:08:58 +08:00
parent 4473b2e57e
commit e998d2a78c
58 changed files with 1153 additions and 1035 deletions

View File

@ -83,12 +83,14 @@ public class EventListen implements Listener {
@EventHandler(ignoreCancelled = true) @EventHandler(ignoreCancelled = true)
public void onChunkUnload(ChunkUnloadEvent event) { public void onChunkUnload(ChunkUnloadEvent event) {
ChunkCoord coord = toCoord(event.getChunk()); ChunkCoord coord = toCoord(event.getChunk());
Location cachedLocation = new Location(null, 0, 0, 0);
for (NPC npc : npcRegistry) { for (NPC npc : npcRegistry) {
if (!npc.isSpawned()) if (!npc.isSpawned())
continue; continue;
Location loc = npc.getBukkitEntity().getLocation(); cachedLocation = npc.getBukkitEntity().getLocation(cachedLocation);
boolean sameChunkCoordinates = coord.z == loc.getBlockZ() >> 4 && coord.x == loc.getBlockX() >> 4; boolean sameChunkCoordinates = coord.z == cachedLocation.getBlockZ() >> 4
if (sameChunkCoordinates && event.getWorld().equals(loc.getWorld())) { && coord.x == cachedLocation.getBlockX() >> 4;
if (sameChunkCoordinates && event.getWorld().equals(cachedLocation.getWorld())) {
npc.despawn(DespawnReason.CHUNK_UNLOAD); npc.despawn(DespawnReason.CHUNK_UNLOAD);
toRespawn.put(coord, npc.getId()); toRespawn.put(coord, npc.getId());
Messaging.debug("Despawned", npc.getId(), "due to chunk unload at [" + coord.x + "," Messaging.debug("Despawned", npc.getId(), "due to chunk unload at [" + coord.x + ","

View File

@ -69,48 +69,6 @@ import org.bukkit.scheduler.BukkitTask;
*/ */
public class Metrics { public class Metrics {
/**
* The current revision number
*/
private final static int REVISION = 6;
/**
* The base url of the metrics domain
*/
private static final String BASE_URL = "http://mcstats.org";
/**
* The url used to report a server's status
*/
private static final String REPORT_URL = "/report/%s";
/**
* The separator to use for custom data. This MUST NOT change unless you are
* hosting your own version of metrics and want to change it.
*/
private static final String CUSTOM_DATA_SEPARATOR = "~~";
/**
* Interval of time to ping (in minutes)
*/
private static final int PING_INTERVAL = 10;
/**
* The plugin this metrics submits for
*/
private final Plugin plugin;
/**
* All of the custom graphs to submit to metrics
*/
private final Set<Graph> graphs = Collections.synchronizedSet(new HashSet<Graph>());
/**
* The default graph, used for addCustomData when you don't want a specific
* graph
*/
private final Graph defaultGraph = new Graph("Default");
/** /**
* The plugin configuration file * The plugin configuration file
*/ */
@ -121,21 +79,37 @@ public class Metrics {
*/ */
private final File configurationFile; private final File configurationFile;
/**
* Unique server id
*/
private final String guid;
/** /**
* Debug mode * Debug mode
*/ */
private final boolean debug; private final boolean debug;
/**
* The default graph, used for addCustomData when you don't want a specific
* graph
*/
private final Graph defaultGraph = new Graph("Default");
/**
* All of the custom graphs to submit to metrics
*/
private final Set<Graph> graphs = Collections.synchronizedSet(new HashSet<Graph>());
/**
* Unique server id
*/
private final String guid;
/** /**
* Lock for synchronization * Lock for synchronization
*/ */
private final Object optOutLock = new Object(); private final Object optOutLock = new Object();
/**
* The plugin this metrics submits for
*/
private final Plugin plugin;
/** /**
* The scheduled task * The scheduled task
*/ */
@ -168,6 +142,39 @@ public class Metrics {
debug = configuration.getBoolean("debug", false); debug = configuration.getBoolean("debug", false);
} }
/**
* Adds a custom data plotter to the default graph
*
* @param plotter
* The plotter to use to plot custom data
*/
public void addCustomData(final Plotter plotter) {
if (plotter == null) {
throw new IllegalArgumentException("Plotter cannot be null");
}
// Add the plotter to the graph o/
defaultGraph.addPlotter(plotter);
// Ensure the default graph is included in the submitted graphs
graphs.add(defaultGraph);
}
/**
* Add a Graph object to Metrics that represents data for the plugin that
* should be sent to the backend
*
* @param graph
* The name of the graph
*/
public void addGraph(final Graph graph) {
if (graph == null) {
throw new IllegalArgumentException("Graph cannot be null");
}
graphs.add(graph);
}
/** /**
* Construct and create a Graph that can be used to separate specific * Construct and create a Graph that can be used to separate specific
* plotters to their own graphs on the metrics website. Plotters can be * plotters to their own graphs on the metrics website. Plotters can be
@ -193,154 +200,6 @@ public class Metrics {
return graph; return graph;
} }
/**
* Add a Graph object to Metrics that represents data for the plugin that
* should be sent to the backend
*
* @param graph
* The name of the graph
*/
public void addGraph(final Graph graph) {
if (graph == null) {
throw new IllegalArgumentException("Graph cannot be null");
}
graphs.add(graph);
}
/**
* Adds a custom data plotter to the default graph
*
* @param plotter
* The plotter to use to plot custom data
*/
public void addCustomData(final Plotter plotter) {
if (plotter == null) {
throw new IllegalArgumentException("Plotter cannot be null");
}
// Add the plotter to the graph o/
defaultGraph.addPlotter(plotter);
// Ensure the default graph is included in the submitted graphs
graphs.add(defaultGraph);
}
/**
* Start measuring statistics. This will immediately create an async
* repeating task as the plugin and send the initial data to the metrics
* backend, and then after that it will post in increments of PING_INTERVAL
* * 1200 ticks.
*
* @return True if statistics measuring is running, otherwise false.
*/
public boolean start() {
synchronized (optOutLock) {
// Did we opt out?
if (isOptOut()) {
return false;
}
// Is metrics already running?
if (task != null) {
return true;
}
// Begin hitting the server with glorious data
task = plugin.getServer().getScheduler().runTaskTimerAsynchronously(plugin, new Runnable() {
private boolean firstPost = true;
@Override
public void run() {
try {
// This has to be synchronized or it can collide with
// the disable method.
synchronized (optOutLock) {
// Disable Task, if it is running and the server
// owner decided to opt-out
if (isOptOut() && task != null) {
task.cancel();
task = null;
// Tell all plotters to stop gathering
// information.
for (Graph graph : graphs) {
graph.onOptOut();
}
}
}
// We use the inverse of firstPost because if it is the
// first time we are posting,
// it is not a interval ping, so it evaluates to FALSE
// Each time thereafter it will evaluate to TRUE, i.e
// PING!
postPlugin(!firstPost);
// After the first post we set firstPost to false
// Each post thereafter will be a ping
firstPost = false;
} catch (IOException e) {
if (debug) {
Bukkit.getLogger().log(Level.INFO, "[Metrics] " + e.getMessage());
}
}
}
}, 0, PING_INTERVAL * 1200);
return true;
}
}
/**
* Has the server owner denied plugin metrics?
*
* @return true if metrics should be opted out of it
*/
public boolean isOptOut() {
synchronized (optOutLock) {
try {
// Reload the metrics file
configuration.load(getConfigFile());
} catch (IOException ex) {
if (debug) {
Bukkit.getLogger().log(Level.INFO, "[Metrics] " + ex.getMessage());
}
return true;
} catch (InvalidConfigurationException ex) {
if (debug) {
Bukkit.getLogger().log(Level.INFO, "[Metrics] " + ex.getMessage());
}
return true;
}
return configuration.getBoolean("opt-out", false);
}
}
/**
* Enables metrics for the server by setting "opt-out" to false in the
* config file and starting the metrics task.
*
* @throws IOException
*/
public void enable() throws IOException {
// This has to be synchronized or it can collide with the check in the
// task.
synchronized (optOutLock) {
// Check if the server owner has already set opt-out, if not, set
// it.
if (isOptOut()) {
configuration.set("opt-out", false);
configuration.save(configurationFile);
}
// Enable Task, if it is not running
if (task == null) {
start();
}
}
}
/** /**
* Disables metrics for the server by setting "opt-out" to true in the * Disables metrics for the server by setting "opt-out" to true in the
* config file and canceling the metrics task. * config file and canceling the metrics task.
@ -366,6 +225,30 @@ public class Metrics {
} }
} }
/**
* Enables metrics for the server by setting "opt-out" to false in the
* config file and starting the metrics task.
*
* @throws IOException
*/
public void enable() throws IOException {
// This has to be synchronized or it can collide with the check in the
// task.
synchronized (optOutLock) {
// Check if the server owner has already set opt-out, if not, set
// it.
if (isOptOut()) {
configuration.set("opt-out", false);
configuration.save(configurationFile);
}
// Enable Task, if it is not running
if (task == null) {
start();
}
}
}
/** /**
* Gets the File object of the config file that should be used to store data * Gets the File object of the config file that should be used to store data
* such as the GUID and opt-out status * such as the GUID and opt-out status
@ -385,6 +268,46 @@ public class Metrics {
return new File(new File(pluginsFolder, "PluginMetrics"), "config.yml"); return new File(new File(pluginsFolder, "PluginMetrics"), "config.yml");
} }
/**
* Check if mineshafter is present. If it is, we need to bypass it to send
* POST requests
*
* @return true if mineshafter is installed on the server
*/
private boolean isMineshafterPresent() {
try {
Class.forName("mineshafter.MineServer");
return true;
} catch (Exception e) {
return false;
}
}
/**
* Has the server owner denied plugin metrics?
*
* @return true if metrics should be opted out of it
*/
public boolean isOptOut() {
synchronized (optOutLock) {
try {
// Reload the metrics file
configuration.load(getConfigFile());
} catch (IOException ex) {
if (debug) {
Bukkit.getLogger().log(Level.INFO, "[Metrics] " + ex.getMessage());
}
return true;
} catch (InvalidConfigurationException ex) {
if (debug) {
Bukkit.getLogger().log(Level.INFO, "[Metrics] " + ex.getMessage());
}
return true;
}
return configuration.getBoolean("opt-out", false);
}
}
/** /**
* Generic method that posts a plugin to the metrics website * Generic method that posts a plugin to the metrics website
*/ */
@ -516,52 +439,69 @@ public class Metrics {
} }
/** /**
* Check if mineshafter is present. If it is, we need to bypass it to send * Start measuring statistics. This will immediately create an async
* POST requests * repeating task as the plugin and send the initial data to the metrics
* backend, and then after that it will post in increments of PING_INTERVAL
* * 1200 ticks.
* *
* @return true if mineshafter is installed on the server * @return True if statistics measuring is running, otherwise false.
*/ */
private boolean isMineshafterPresent() { public boolean start() {
try { synchronized (optOutLock) {
Class.forName("mineshafter.MineServer"); // Did we opt out?
return true; if (isOptOut()) {
} catch (Exception e) {
return false; return false;
} }
// Is metrics already running?
if (task != null) {
return true;
} }
/** // Begin hitting the server with glorious data
* <p> task = plugin.getServer().getScheduler().runTaskTimerAsynchronously(plugin, new Runnable() {
* Encode a key/value data pair to be used in a HTTP post request. This
* INCLUDES a & so the first key/value pair MUST be included manually, e.g: private boolean firstPost = true;
* </p>
* <code> @Override
* StringBuffer data = new StringBuffer(); public void run() {
* data.append(encode("guid")).append('=').append(encode(guid)); try {
* encodeDataPair(data, "version", description.getVersion()); // This has to be synchronized or it can collide with
* </code> // the disable method.
* synchronized (optOutLock) {
* @param buffer // Disable Task, if it is running and the server
* the stringbuilder to append the data pair onto // owner decided to opt-out
* @param key if (isOptOut() && task != null) {
* the key value task.cancel();
* @param value task = null;
* the value // Tell all plotters to stop gathering
*/ // information.
private static void encodeDataPair(final StringBuilder buffer, final String key, final String value) for (Graph graph : graphs) {
throws UnsupportedEncodingException { graph.onOptOut();
buffer.append('&').append(encode(key)).append('=').append(encode(value)); }
}
} }
/** // We use the inverse of firstPost because if it is the
* Encode text as UTF-8 // first time we are posting,
* // it is not a interval ping, so it evaluates to FALSE
* @param text // Each time thereafter it will evaluate to TRUE, i.e
* the text to encode // PING!
* @return the encoded text, as UTF-8 postPlugin(!firstPost);
*/
private static String encode(final String text) throws UnsupportedEncodingException { // After the first post we set firstPost to false
return URLEncoder.encode(text, "UTF-8"); // Each post thereafter will be a ping
firstPost = false;
} catch (IOException e) {
if (debug) {
Bukkit.getLogger().log(Level.INFO, "[Metrics] " + e.getMessage());
}
}
}
}, 0, PING_INTERVAL * 1200);
return true;
}
} }
/** /**
@ -584,15 +524,6 @@ public class Metrics {
this.name = name; this.name = name;
} }
/**
* Gets the graph's name
*
* @return the Graph's name
*/
public String getName() {
return name;
}
/** /**
* Add a plotter to the graph, which will be used to plot entries * Add a plotter to the graph, which will be used to plot entries
* *
@ -603,14 +534,23 @@ public class Metrics {
plotters.add(plotter); plotters.add(plotter);
} }
@Override
public boolean equals(final Object object) {
if (!(object instanceof Graph)) {
return false;
}
final Graph graph = (Graph) object;
return graph.name.equals(name);
}
/** /**
* Remove a plotter from the graph * Gets the graph's name
* *
* @param plotter * @return the Graph's name
* the plotter to remove from the graph
*/ */
public void removePlotter(final Plotter plotter) { public String getName() {
plotters.remove(plotter); return name;
} }
/** /**
@ -627,16 +567,6 @@ public class Metrics {
return name.hashCode(); return name.hashCode();
} }
@Override
public boolean equals(final Object object) {
if (!(object instanceof Graph)) {
return false;
}
final Graph graph = (Graph) object;
return graph.name.equals(name);
}
/** /**
* Called when the server owner decides to opt-out of Metrics while the * Called when the server owner decides to opt-out of Metrics while the
* server is running. * server is running.
@ -644,6 +574,16 @@ public class Metrics {
protected void onOptOut() { protected void onOptOut() {
} }
/**
* Remove a plotter from the graph
*
* @param plotter
* the plotter to remove from the graph
*/
public void removePlotter(final Plotter plotter) {
plotters.remove(plotter);
}
} }
/** /**
@ -674,16 +614,15 @@ public class Metrics {
this.name = name; this.name = name;
} }
/** @Override
* Get the current value for the plotted point. Since this function public boolean equals(final Object object) {
* defers to an external function it may or may not return immediately if (!(object instanceof Plotter)) {
* thus cannot be guaranteed to be thread friendly or safe. This return false;
* function can be called from any thread so care should be taken when }
* accessing resources that need to be synchronized.
* final Plotter plotter = (Plotter) object;
* @return the current value for the point to be plotted. return plotter.name.equals(name) && plotter.getValue() == getValue();
*/ }
public abstract int getValue();
/** /**
* Get the column name for the plotted point * Get the column name for the plotted point
@ -695,26 +634,87 @@ public class Metrics {
} }
/** /**
* Called after the website graphs have been updated * Get the current value for the plotted point. Since this function
* defers to an external function it may or may not return immediately
* thus cannot be guaranteed to be thread friendly or safe. This
* function can be called from any thread so care should be taken when
* accessing resources that need to be synchronized.
*
* @return the current value for the point to be plotted.
*/ */
public void reset() { public abstract int getValue();
}
@Override @Override
public int hashCode() { public int hashCode() {
return getColumnName().hashCode(); return getColumnName().hashCode();
} }
@Override /**
public boolean equals(final Object object) { * Called after the website graphs have been updated
if (!(object instanceof Plotter)) { */
return false; public void reset() {
} }
final Plotter plotter = (Plotter) object;
return plotter.name.equals(name) && plotter.getValue() == getValue();
} }
/**
* The base url of the metrics domain
*/
private static final String BASE_URL = "http://mcstats.org";
/**
* The separator to use for custom data. This MUST NOT change unless you are
* hosting your own version of metrics and want to change it.
*/
private static final String CUSTOM_DATA_SEPARATOR = "~~";
/**
* Interval of time to ping (in minutes)
*/
private static final int PING_INTERVAL = 10;
/**
* The url used to report a server's status
*/
private static final String REPORT_URL = "/report/%s";
/**
* The current revision number
*/
private final static int REVISION = 6;
/**
* Encode text as UTF-8
*
* @param text
* the text to encode
* @return the encoded text, as UTF-8
*/
private static String encode(final String text) throws UnsupportedEncodingException {
return URLEncoder.encode(text, "UTF-8");
}
/**
* <p>
* Encode a key/value data pair to be used in a HTTP post request. This
* INCLUDES a & so the first key/value pair MUST be included manually, e.g:
* </p>
* <code>
* StringBuffer data = new StringBuffer();
* data.append(encode("guid")).append('=').append(encode(guid));
* encodeDataPair(data, "version", description.getVersion());
* </code>
*
* @param buffer
* the stringbuilder to append the data pair onto
* @param key
* the key value
* @param value
* the value
*/
private static void encodeDataPair(final StringBuilder buffer, final String key, final String value)
throws UnsupportedEncodingException {
buffer.append('&').append(encode(key)).append('=').append(encode(value));
} }
} }

View File

@ -94,7 +94,6 @@ public class NPCDataStore {
saves = new YamlStorage(new File(folder, Setting.STORAGE_FILE.asString()), "Citizens NPC Storage"); saves = new YamlStorage(new File(folder, Setting.STORAGE_FILE.asString()), "Citizens NPC Storage");
if (!saves.load()) if (!saves.load())
return null; return null;
Messaging.logTr(Messages.SAVE_METHOD_SET_NOTIFICATION, saves.toString());
return new NPCDataStore(saves); return new NPCDataStore(saves);
} }
} }

View File

@ -24,6 +24,7 @@ import net.citizensnpcs.command.exception.CommandException;
import net.citizensnpcs.command.exception.NoPermissionsException; import net.citizensnpcs.command.exception.NoPermissionsException;
import net.citizensnpcs.command.exception.ServerCommandException; import net.citizensnpcs.command.exception.ServerCommandException;
import net.citizensnpcs.npc.CitizensNPC; import net.citizensnpcs.npc.CitizensNPC;
import net.citizensnpcs.npc.EntityControllers;
import net.citizensnpcs.npc.NPCSelector; import net.citizensnpcs.npc.NPCSelector;
import net.citizensnpcs.npc.Template; import net.citizensnpcs.npc.Template;
import net.citizensnpcs.trait.Age; import net.citizensnpcs.trait.Age;
@ -814,7 +815,12 @@ public class NPCCommands {
Messaging.sendErrorTr(sender, Messages.NPC_NAME_TOO_LONG); Messaging.sendErrorTr(sender, Messages.NPC_NAME_TOO_LONG);
newName = newName.substring(0, 15); newName = newName.substring(0, 15);
} }
Location prev = npc.isSpawned() ? npc.getBukkitEntity().getLocation() : null;
npc.despawn();
npc.setName(newName); npc.setName(newName);
if (prev != null)
npc.spawn(prev);
Messaging.sendTr(sender, Messages.NPC_RENAMED, oldName, newName); Messaging.sendTr(sender, Messages.NPC_RENAMED, oldName, newName);
} }
@ -989,6 +995,22 @@ public class NPCCommands {
Messaging.sendTr(sender, Messages.NPC_TELEPORTED, npc.getName()); Messaging.sendTr(sender, Messages.NPC_TELEPORTED, npc.getName());
} }
@Command(
aliases = { "npc" },
usage = "type",
desc = "Sets an NPC's entity type",
modifiers = { "type" },
min = 2,
max = 2,
permission = "npc.type")
public void type(CommandContext args, CommandSender sender, NPC npc) throws CommandException {
EntityType type = Util.matchEntityType(args.getString(1));
if (type == null)
throw new CommandException(Messages.INVALID_ENTITY_TYPE, args.getString(1));
((CitizensNPC) npc).setEntityController(EntityControllers.createForType(type));
Messaging.sendTr(sender, Messages.ENTITY_TYPE_SET, npc.getName(), args.getString(1));
}
@Command( @Command(
aliases = { "npc" }, aliases = { "npc" },
usage = "vulnerable (-t)", usage = "vulnerable (-t)",

View File

@ -0,0 +1,42 @@
package net.citizensnpcs.editor;
import net.citizensnpcs.api.npc.NPC;
import net.citizensnpcs.api.trait.trait.Equipment;
import net.citizensnpcs.util.Messages;
import net.citizensnpcs.util.Messaging;
import org.bukkit.Material;
import org.bukkit.entity.Enderman;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.material.MaterialData;
public class EndermanEquipper implements Equipper {
@Override
public void equip(Player equipper, NPC npc) {
ItemStack hand = equipper.getItemInHand();
if (!hand.getType().isBlock()) {
Messaging.sendErrorTr(equipper, Messages.EQUIPMENT_EDITOR_INVALID_BLOCK);
return;
}
MaterialData carried = ((Enderman) npc.getBukkitEntity()).getCarriedMaterial();
if (carried.getItemType() == Material.AIR) {
if (hand.getType() == Material.AIR) {
Messaging.sendErrorTr(equipper, Messages.EQUIPMENT_EDITOR_INVALID_BLOCK);
return;
}
} else {
equipper.getWorld()
.dropItemNaturally(npc.getBukkitEntity().getLocation(), carried.toItemStack(1));
((Enderman) npc.getBukkitEntity()).setCarriedMaterial(hand.getData());
}
ItemStack set = hand.clone();
if (set.getType() != Material.AIR) {
set.setAmount(1);
hand.setAmount(hand.getAmount() - 1);
}
npc.getTrait(Equipment.class).set(0, set);
}
}

View File

@ -1,8 +0,0 @@
package net.citizensnpcs.editor;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
public interface Equipable {
public void equip(Player equipper, ItemStack toEquip);
}

View File

@ -1,10 +1,13 @@
package net.citizensnpcs.editor; package net.citizensnpcs.editor;
import java.util.Map;
import net.citizensnpcs.api.CitizensAPI; import net.citizensnpcs.api.CitizensAPI;
import net.citizensnpcs.api.npc.NPC; import net.citizensnpcs.api.npc.NPC;
import net.citizensnpcs.util.Messages; import net.citizensnpcs.util.Messages;
import net.citizensnpcs.util.Messaging; import net.citizensnpcs.util.Messaging;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.Event.Result; import org.bukkit.event.Event.Result;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
@ -13,6 +16,8 @@ import org.bukkit.event.player.PlayerInteractEntityEvent;
import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import com.google.common.collect.Maps;
public class EquipmentEditor extends Editor { public class EquipmentEditor extends Editor {
private final NPC npc; private final NPC npc;
private final Player player; private final Player player;
@ -40,14 +45,23 @@ public class EquipmentEditor extends Editor {
@EventHandler @EventHandler
public void onPlayerInteractEntity(PlayerInteractEntityEvent event) { public void onPlayerInteractEntity(PlayerInteractEntityEvent event) {
if (!event.getPlayer().equals(player) if (!npc.isSpawned() || !event.getPlayer().equals(player)
|| !npc.equals(CitizensAPI.getNPCRegistry().getNPC(event.getRightClicked()))) || !npc.equals(CitizensAPI.getNPCRegistry().getNPC(event.getRightClicked())))
return; return;
if (npc instanceof Equipable) { Equipper equipper = EQUIPPERS.get(npc.getBukkitEntity().getType());
if (equipper == null)
return;
ItemStack hand = event.getPlayer().getItemInHand(); ItemStack hand = event.getPlayer().getItemInHand();
((Equipable) npc).equip(event.getPlayer(), hand); equipper.equip(event.getPlayer(), npc);
event.getPlayer().setItemInHand(hand.getAmount() > 0 ? hand : null); event.getPlayer().setItemInHand(hand.getAmount() > 0 ? hand : null);
} }
private static final Map<EntityType, Equipper> EQUIPPERS = Maps.newEnumMap(EntityType.class);
static {
EQUIPPERS.put(EntityType.PIG, new PigEquipper());
EQUIPPERS.put(EntityType.PLAYER, new HumanEquipper());
EQUIPPERS.put(EntityType.SHEEP, new SheepEquipper());
EQUIPPERS.put(EntityType.ENDERMAN, new EndermanEquipper());
} }
} }

View File

@ -0,0 +1,9 @@
package net.citizensnpcs.editor;
import net.citizensnpcs.api.npc.NPC;
import org.bukkit.entity.Player;
public interface Equipper {
public void equip(Player equipper, NPC toEquip);
}

View File

@ -0,0 +1,84 @@
package net.citizensnpcs.editor;
import net.citizensnpcs.api.npc.NPC;
import net.citizensnpcs.api.trait.trait.Equipment;
import net.citizensnpcs.util.Messages;
import net.citizensnpcs.util.Messaging;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
public class HumanEquipper implements Equipper {
@Override
public void equip(Player equipper, NPC toEquip) {
ItemStack hand = equipper.getItemInHand();
Equipment trait = toEquip.getTrait(Equipment.class);
int slot = 0;
Material type = hand == null ? Material.AIR : hand.getType();
// First, determine the slot to edit
switch (type) {
case SKULL_ITEM:
case PUMPKIN:
case JACK_O_LANTERN:
case LEATHER_HELMET:
case CHAINMAIL_HELMET:
case GOLD_HELMET:
case IRON_HELMET:
case DIAMOND_HELMET:
if (!equipper.isSneaking())
slot = 1;
break;
case LEATHER_CHESTPLATE:
case CHAINMAIL_CHESTPLATE:
case GOLD_CHESTPLATE:
case IRON_CHESTPLATE:
case DIAMOND_CHESTPLATE:
if (!equipper.isSneaking())
slot = 2;
break;
case LEATHER_LEGGINGS:
case CHAINMAIL_LEGGINGS:
case GOLD_LEGGINGS:
case IRON_LEGGINGS:
case DIAMOND_LEGGINGS:
if (!equipper.isSneaking())
slot = 3;
break;
case LEATHER_BOOTS:
case CHAINMAIL_BOOTS:
case GOLD_BOOTS:
case IRON_BOOTS:
case DIAMOND_BOOTS:
if (!equipper.isSneaking())
slot = 4;
break;
case AIR:
for (int i = 0; i < 5; i++) {
if (trait.get(i) != null && trait.get(i).getType() != Material.AIR) {
equipper.getWorld().dropItemNaturally(toEquip.getBukkitEntity().getLocation(),
trait.get(i));
trait.set(i, null);
}
}
Messaging.sendTr(equipper, Messages.EQUIPMENT_EDITOR_ALL_ITEMS_REMOVED, toEquip.getName());
break;
default:
break;
}
// Drop any previous equipment on the ground
ItemStack equippedItem = trait.get(slot);
if (equippedItem != null && equippedItem.getType() != Material.AIR)
equipper.getWorld().dropItemNaturally(toEquip.getBukkitEntity().getLocation(), equippedItem);
// Now edit the equipment based on the slot
if (type != Material.AIR) {
// Set the proper slot with one of the item
ItemStack clone = hand.clone();
clone.setAmount(1);
trait.set(slot, clone);
hand.setAmount(hand.getAmount() - 1);
}
}
}

View File

@ -0,0 +1,30 @@
package net.citizensnpcs.editor;
import net.citizensnpcs.api.npc.NPC;
import net.citizensnpcs.trait.Saddle;
import net.citizensnpcs.util.Messages;
import net.citizensnpcs.util.Messaging;
import org.bukkit.Material;
import org.bukkit.entity.Pig;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
public class PigEquipper implements Equipper {
@Override
public void equip(Player equipper, NPC toEquip) {
ItemStack hand = equipper.getItemInHand();
Pig pig = (Pig) toEquip.getBukkitEntity();
if (hand.getType() == Material.SADDLE) {
if (!pig.hasSaddle()) {
toEquip.getTrait(Saddle.class).toggle();
hand.setAmount(0);
Messaging.sendTr(equipper, Messages.SADDLED_SET, toEquip.getName());
}
} else if (pig.hasSaddle()) {
equipper.getWorld().dropItemNaturally(pig.getLocation(), new ItemStack(Material.SADDLE, 1));
toEquip.getTrait(Saddle.class).toggle();
Messaging.sendTr(equipper, Messages.SADDLED_STOPPED, toEquip.getName());
}
}
}

View File

@ -0,0 +1,38 @@
package net.citizensnpcs.editor;
import net.citizensnpcs.api.npc.NPC;
import net.citizensnpcs.trait.Sheared;
import net.citizensnpcs.trait.WoolColor;
import net.citizensnpcs.util.Messages;
import net.citizensnpcs.util.Messaging;
import org.bukkit.DyeColor;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.entity.Sheep;
import org.bukkit.inventory.ItemStack;
public class SheepEquipper implements Equipper {
@Override
public void equip(Player equipper, NPC toEquip) {
ItemStack hand = equipper.getItemInHand();
Sheep sheep = (Sheep) toEquip.getBukkitEntity();
if (hand.getType() == Material.SHEARS) {
Messaging.sendTr(equipper, toEquip.getTrait(Sheared.class).toggle() ? Messages.SHEARED_SET
: Messages.SHEARED_STOPPED, toEquip.getName());
} else if (hand.getType() == Material.INK_SACK) {
if (sheep.getColor() == DyeColor.getByData((byte) (15 - hand.getData().getData())))
return;
DyeColor color = DyeColor.getByData((byte) (15 - hand.getData().getData()));
toEquip.getTrait(WoolColor.class).setColor(color);
Messaging.sendTr(equipper, Messages.EQUIPMENT_EDITOR_SHEEP_COLOURED, toEquip.getName(), color
.name().toLowerCase().replace("_", " "));
hand.setAmount(hand.getAmount() - 1);
} else {
toEquip.getTrait(WoolColor.class).setColor(DyeColor.WHITE);
Messaging.sendTr(equipper, Messages.EQUIPMENT_EDITOR_SHEEP_COLOURED, toEquip.getName(), "white");
}
}
}

View File

@ -0,0 +1,30 @@
package net.citizensnpcs.npc;
import net.citizensnpcs.api.npc.NPC;
import org.bukkit.Location;
import org.bukkit.entity.LivingEntity;
public abstract class AbstractEntityController implements EntityController {
private LivingEntity bukkitEntity;
protected abstract LivingEntity createEntity(Location at, NPC npc);
@Override
public LivingEntity getBukkitEntity() {
return bukkitEntity;
}
@Override
public void remove() {
if (bukkitEntity == null)
return;
bukkitEntity.remove();
bukkitEntity = null;
}
@Override
public void spawn(Location at, NPC npc) {
bukkitEntity = createEntity(at, npc);
}
}

View File

@ -25,29 +25,30 @@ import net.citizensnpcs.util.NMS;
import net.citizensnpcs.util.Util; import net.citizensnpcs.util.Util;
import net.minecraft.server.v1_4_5.EntityLiving; import net.minecraft.server.v1_4_5.EntityLiving;
import org.apache.commons.lang.Validate;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.craftbukkit.v1_4_5.entity.CraftLivingEntity;
import org.bukkit.entity.LivingEntity; import org.bukkit.entity.LivingEntity;
import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason; import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason;
import org.bukkit.metadata.FixedMetadataValue; import org.bukkit.metadata.FixedMetadataValue;
import com.google.common.base.Function; import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter; import com.google.common.base.Splitter;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
public abstract class CitizensNPC extends AbstractNPC { public class CitizensNPC extends AbstractNPC {
protected EntityLiving mcEntity; private EntityController entityController;
private final CitizensNavigator navigator = new CitizensNavigator(this); private final CitizensNavigator navigator = new CitizensNavigator(this);
private final List<String> removedTraits = Lists.newArrayList(); private final List<String> removedTraits = Lists.newArrayList();
protected CitizensNPC(int id, String name) { public CitizensNPC(int id, String name, EntityController entityController) {
super(id, name); super(id, name);
Preconditions.checkNotNull(entityController);
this.entityController = entityController;
} }
protected abstract EntityLiving createHandle(Location loc);
@Override @Override
public boolean despawn(DespawnReason reason) { public boolean despawn(DespawnReason reason) {
if (!isSpawned()) if (!isSpawned())
@ -67,21 +68,19 @@ public abstract class CitizensNPC extends AbstractNPC {
data().remove("selectors"); data().remove("selectors");
for (Trait trait : traits.values()) for (Trait trait : traits.values())
trait.onDespawn(); trait.onDespawn();
getBukkitEntity().remove(); entityController.remove();
mcEntity = null;
return true; return true;
} }
@Override @Override
public LivingEntity getBukkitEntity() { public LivingEntity getBukkitEntity() {
if (mcEntity == null) return entityController.getBukkitEntity();
return null;
return (LivingEntity) mcEntity.getBukkitEntity();
} }
@Deprecated
public EntityLiving getHandle() { public EntityLiving getHandle() {
return mcEntity; return ((CraftLivingEntity) getBukkitEntity()).getHandle();
} }
@Override @Override
@ -91,7 +90,7 @@ public abstract class CitizensNPC extends AbstractNPC {
@Override @Override
public boolean isSpawned() { public boolean isSpawned() {
return mcEntity != null; return getBukkitEntity() != null;
} }
public void load(final DataKey root) { public void load(final DataKey root) {
@ -177,35 +176,49 @@ public abstract class CitizensNPC extends AbstractNPC {
removeTraitData(root); removeTraitData(root);
} }
public void setEntityController(EntityController newController) {
Preconditions.checkNotNull(newController);
boolean wasSpawned = isSpawned();
Location prev = null;
if (wasSpawned) {
prev = getBukkitEntity().getLocation();
despawn();
}
entityController = newController;
if (wasSpawned)
spawn(prev);
}
@Override @Override
public boolean spawn(Location loc) { public boolean spawn(Location at) {
Validate.notNull(loc, "location cannot be null"); Preconditions.checkNotNull(at, "location cannot be null");
if (isSpawned()) if (isSpawned())
return false; return false;
mcEntity = createHandle(loc); entityController.spawn(at, this);
boolean couldSpawn = !Util.isLoaded(loc) ? false : mcEntity.world.addEntity(mcEntity, EntityLiving mcEntity = getHandle();
boolean couldSpawn = !Util.isLoaded(at) ? false : mcEntity.world.addEntity(mcEntity,
SpawnReason.CUSTOM); SpawnReason.CUSTOM);
if (!couldSpawn) { if (!couldSpawn) {
// we need to wait for a chunk load before trying to spawn // we need to wait for a chunk load before trying to spawn
mcEntity = null; mcEntity = null;
EventListen.addForRespawn(loc, getId()); EventListen.addForRespawn(at, getId());
return true; return true;
} }
NPCSpawnEvent spawnEvent = new NPCSpawnEvent(this, loc); NPCSpawnEvent spawnEvent = new NPCSpawnEvent(this, at);
Bukkit.getPluginManager().callEvent(spawnEvent); Bukkit.getPluginManager().callEvent(spawnEvent);
if (spawnEvent.isCancelled()) { if (spawnEvent.isCancelled()) {
mcEntity = null; mcEntity = null;
return false; return false;
} }
NMS.setHeadYaw(mcEntity, loc.getYaw()); NMS.setHeadYaw(mcEntity, at.getYaw());
getBukkitEntity().setMetadata(NPC_METADATA_MARKER, getBukkitEntity().setMetadata(NPC_METADATA_MARKER,
new FixedMetadataValue(CitizensAPI.getPlugin(), true)); new FixedMetadataValue(CitizensAPI.getPlugin(), true));
// Set the spawned state // Set the spawned state
getTrait(CurrentLocation.class).setLocation(loc); getTrait(CurrentLocation.class).setLocation(at);
getTrait(Spawned.class).setSpawned(true); getTrait(Spawned.class).setSpawned(true);
navigator.onSpawn(); navigator.onSpawn();

View File

@ -1,43 +1,12 @@
package net.citizensnpcs.npc; package net.citizensnpcs.npc;
import java.util.EnumMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map;
import net.citizensnpcs.NPCDataStore; import net.citizensnpcs.NPCDataStore;
import net.citizensnpcs.api.npc.NPC; import net.citizensnpcs.api.npc.NPC;
import net.citizensnpcs.api.npc.NPCRegistry; import net.citizensnpcs.api.npc.NPCRegistry;
import net.citizensnpcs.api.trait.Trait; import net.citizensnpcs.api.trait.Trait;
import net.citizensnpcs.npc.ai.NPCHolder; import net.citizensnpcs.npc.ai.NPCHolder;
import net.citizensnpcs.npc.entity.CitizensBatNPC;
import net.citizensnpcs.npc.entity.CitizensBlazeNPC;
import net.citizensnpcs.npc.entity.CitizensCaveSpiderNPC;
import net.citizensnpcs.npc.entity.CitizensChickenNPC;
import net.citizensnpcs.npc.entity.CitizensCowNPC;
import net.citizensnpcs.npc.entity.CitizensCreeperNPC;
import net.citizensnpcs.npc.entity.CitizensEnderDragonNPC;
import net.citizensnpcs.npc.entity.CitizensEndermanNPC;
import net.citizensnpcs.npc.entity.CitizensGhastNPC;
import net.citizensnpcs.npc.entity.CitizensGiantNPC;
import net.citizensnpcs.npc.entity.CitizensHumanNPC;
import net.citizensnpcs.npc.entity.CitizensIronGolemNPC;
import net.citizensnpcs.npc.entity.CitizensMagmaCubeNPC;
import net.citizensnpcs.npc.entity.CitizensMushroomCowNPC;
import net.citizensnpcs.npc.entity.CitizensOcelotNPC;
import net.citizensnpcs.npc.entity.CitizensPigNPC;
import net.citizensnpcs.npc.entity.CitizensPigZombieNPC;
import net.citizensnpcs.npc.entity.CitizensSheepNPC;
import net.citizensnpcs.npc.entity.CitizensSilverfishNPC;
import net.citizensnpcs.npc.entity.CitizensSkeletonNPC;
import net.citizensnpcs.npc.entity.CitizensSlimeNPC;
import net.citizensnpcs.npc.entity.CitizensSnowmanNPC;
import net.citizensnpcs.npc.entity.CitizensSpiderNPC;
import net.citizensnpcs.npc.entity.CitizensSquidNPC;
import net.citizensnpcs.npc.entity.CitizensVillagerNPC;
import net.citizensnpcs.npc.entity.CitizensWitchNPC;
import net.citizensnpcs.npc.entity.CitizensWitherNPC;
import net.citizensnpcs.npc.entity.CitizensWolfNPC;
import net.citizensnpcs.npc.entity.CitizensZombieNPC;
import net.citizensnpcs.util.ByIdArray; import net.citizensnpcs.util.ByIdArray;
import org.bukkit.craftbukkit.v1_4_5.entity.CraftEntity; import org.bukkit.craftbukkit.v1_4_5.entity.CraftEntity;
@ -47,41 +16,9 @@ import org.bukkit.entity.EntityType;
public class CitizensNPCRegistry implements NPCRegistry { public class CitizensNPCRegistry implements NPCRegistry {
private final ByIdArray<NPC> npcs = new ByIdArray<NPC>(); private final ByIdArray<NPC> npcs = new ByIdArray<NPC>();
private final NPCDataStore saves; private final NPCDataStore saves;
private final Map<EntityType, Class<? extends CitizensNPC>> types = new EnumMap<EntityType, Class<? extends CitizensNPC>>(
EntityType.class);
public CitizensNPCRegistry(NPCDataStore store) { public CitizensNPCRegistry(NPCDataStore store) {
saves = store; saves = store;
types.put(EntityType.BAT, CitizensBatNPC.class);
types.put(EntityType.BLAZE, CitizensBlazeNPC.class);
types.put(EntityType.CAVE_SPIDER, CitizensCaveSpiderNPC.class);
types.put(EntityType.CHICKEN, CitizensChickenNPC.class);
types.put(EntityType.COW, CitizensCowNPC.class);
types.put(EntityType.CREEPER, CitizensCreeperNPC.class);
types.put(EntityType.ENDER_DRAGON, CitizensEnderDragonNPC.class);
types.put(EntityType.ENDERMAN, CitizensEndermanNPC.class);
types.put(EntityType.GHAST, CitizensGhastNPC.class);
types.put(EntityType.GIANT, CitizensGiantNPC.class);
types.put(EntityType.IRON_GOLEM, CitizensIronGolemNPC.class);
types.put(EntityType.MAGMA_CUBE, CitizensMagmaCubeNPC.class);
types.put(EntityType.MUSHROOM_COW, CitizensMushroomCowNPC.class);
types.put(EntityType.OCELOT, CitizensOcelotNPC.class);
types.put(EntityType.PIG, CitizensPigNPC.class);
types.put(EntityType.PIG_ZOMBIE, CitizensPigZombieNPC.class);
types.put(EntityType.PLAYER, CitizensHumanNPC.class);
types.put(EntityType.SHEEP, CitizensSheepNPC.class);
types.put(EntityType.SILVERFISH, CitizensSilverfishNPC.class);
types.put(EntityType.SKELETON, CitizensSkeletonNPC.class);
types.put(EntityType.SLIME, CitizensSlimeNPC.class);
types.put(EntityType.SNOWMAN, CitizensSnowmanNPC.class);
types.put(EntityType.SPIDER, CitizensSpiderNPC.class);
types.put(EntityType.SQUID, CitizensSquidNPC.class);
types.put(EntityType.VILLAGER, CitizensVillagerNPC.class);
types.put(EntityType.WOLF, CitizensWolfNPC.class);
types.put(EntityType.WITCH, CitizensWitchNPC.class);
types.put(EntityType.WITHER, CitizensWitherNPC.class);
types.put(EntityType.ZOMBIE, CitizensZombieNPC.class);
} }
public NPC createNPC(EntityType type, int id, String name) { public NPC createNPC(EntityType type, int id, String name) {
@ -134,17 +71,7 @@ public class CitizensNPCRegistry implements NPCRegistry {
} }
private CitizensNPC getByType(EntityType type, int id, String name) { private CitizensNPC getByType(EntityType type, int id, String name) {
Class<? extends CitizensNPC> npcClass = types.get(type); return new CitizensNPC(id, name, EntityControllers.createForType(type));
if (npcClass == null)
throw new IllegalArgumentException("Invalid EntityType: " + type);
try {
return npcClass.getConstructor(int.class, String.class).newInstance(id, name);
} catch (Throwable ex) {
if (ex.getCause() != null)
ex = ex.getCause();
ex.printStackTrace();
return null;
}
} }
@Override @Override

View File

@ -0,0 +1,14 @@
package net.citizensnpcs.npc;
import net.citizensnpcs.api.npc.NPC;
import org.bukkit.Location;
import org.bukkit.entity.LivingEntity;
public interface EntityController {
LivingEntity getBukkitEntity();
void remove();
void spawn(Location at, NPC npc);
}

View File

@ -0,0 +1,88 @@
package net.citizensnpcs.npc;
import java.util.Map;
import net.citizensnpcs.npc.entity.BatController;
import net.citizensnpcs.npc.entity.BlazeController;
import net.citizensnpcs.npc.entity.CaveSpiderController;
import net.citizensnpcs.npc.entity.ChickenController;
import net.citizensnpcs.npc.entity.CowController;
import net.citizensnpcs.npc.entity.CreeperController;
import net.citizensnpcs.npc.entity.EnderDragonController;
import net.citizensnpcs.npc.entity.EndermanController;
import net.citizensnpcs.npc.entity.GhastController;
import net.citizensnpcs.npc.entity.GiantController;
import net.citizensnpcs.npc.entity.HumanController;
import net.citizensnpcs.npc.entity.IronGolemController;
import net.citizensnpcs.npc.entity.MagmaCubeController;
import net.citizensnpcs.npc.entity.MushroomCowController;
import net.citizensnpcs.npc.entity.OcelotController;
import net.citizensnpcs.npc.entity.PigController;
import net.citizensnpcs.npc.entity.PigZombieController;
import net.citizensnpcs.npc.entity.SheepController;
import net.citizensnpcs.npc.entity.SilverfishController;
import net.citizensnpcs.npc.entity.SkeletonController;
import net.citizensnpcs.npc.entity.SlimeController;
import net.citizensnpcs.npc.entity.SnowmanController;
import net.citizensnpcs.npc.entity.SpiderController;
import net.citizensnpcs.npc.entity.SquidController;
import net.citizensnpcs.npc.entity.VillagerController;
import net.citizensnpcs.npc.entity.WitchController;
import net.citizensnpcs.npc.entity.WitherController;
import net.citizensnpcs.npc.entity.WolfController;
import net.citizensnpcs.npc.entity.ZombieController;
import org.bukkit.entity.EntityType;
import com.google.common.collect.Maps;
public class EntityControllers {
private static final Map<EntityType, Class<? extends EntityController>> TYPES = Maps
.newEnumMap(EntityType.class);
public static EntityController createForType(EntityType type) {
Class<? extends EntityController> controllerClass = TYPES.get(type);
if (controllerClass == null)
throw new IllegalArgumentException("Invalid EntityType: " + type);
try {
return controllerClass.newInstance();
} catch (Throwable ex) {
if (ex.getCause() != null)
ex = ex.getCause();
ex.printStackTrace();
return null;
}
}
static {
TYPES.put(EntityType.BAT, BatController.class);
TYPES.put(EntityType.BLAZE, BlazeController.class);
TYPES.put(EntityType.CAVE_SPIDER, CaveSpiderController.class);
TYPES.put(EntityType.CHICKEN, ChickenController.class);
TYPES.put(EntityType.COW, CowController.class);
TYPES.put(EntityType.CREEPER, CreeperController.class);
TYPES.put(EntityType.ENDER_DRAGON, EnderDragonController.class);
TYPES.put(EntityType.ENDERMAN, EndermanController.class);
TYPES.put(EntityType.GHAST, GhastController.class);
TYPES.put(EntityType.GIANT, GiantController.class);
TYPES.put(EntityType.IRON_GOLEM, IronGolemController.class);
TYPES.put(EntityType.MAGMA_CUBE, MagmaCubeController.class);
TYPES.put(EntityType.MUSHROOM_COW, MushroomCowController.class);
TYPES.put(EntityType.OCELOT, OcelotController.class);
TYPES.put(EntityType.PIG, PigController.class);
TYPES.put(EntityType.PIG_ZOMBIE, PigZombieController.class);
TYPES.put(EntityType.PLAYER, HumanController.class);
TYPES.put(EntityType.SHEEP, SheepController.class);
TYPES.put(EntityType.SILVERFISH, SilverfishController.class);
TYPES.put(EntityType.SKELETON, SkeletonController.class);
TYPES.put(EntityType.SLIME, SlimeController.class);
TYPES.put(EntityType.SNOWMAN, SnowmanController.class);
TYPES.put(EntityType.SPIDER, SpiderController.class);
TYPES.put(EntityType.SQUID, SquidController.class);
TYPES.put(EntityType.VILLAGER, VillagerController.class);
TYPES.put(EntityType.WOLF, WolfController.class);
TYPES.put(EntityType.WITCH, WitchController.class);
TYPES.put(EntityType.WITHER, WitherController.class);
TYPES.put(EntityType.ZOMBIE, ZombieController.class);
}
}

View File

@ -1,7 +1,6 @@
package net.citizensnpcs.npc; package net.citizensnpcs.npc;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.Map; import java.util.Map;
import net.citizensnpcs.api.npc.NPC; import net.citizensnpcs.api.npc.NPC;
@ -12,42 +11,40 @@ import net.minecraft.server.v1_4_5.World;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.block.BlockFace; import org.bukkit.block.BlockFace;
import org.bukkit.craftbukkit.v1_4_5.CraftWorld; import org.bukkit.craftbukkit.v1_4_5.CraftWorld;
import org.bukkit.entity.LivingEntity;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
public abstract class CitizensMobNPC extends CitizensNPC { public abstract class MobEntityController extends AbstractEntityController {
private final Constructor<?> constructor; private final Constructor<?> constructor;
protected CitizensMobNPC(int id, String name, Class<?> clazz) { protected MobEntityController(Class<?> clazz) {
super(id, name);
this.constructor = getConstructor(clazz); this.constructor = getConstructor(clazz);
NMS.registerEntityClass(clazz); NMS.registerEntityClass(clazz);
} }
@Override
protected LivingEntity createEntity(Location at, NPC npc) {
EntityLiving entity = createEntityFromClass(((CraftWorld) at.getWorld()).getHandle(), npc);
entity.setPositionRotation(at.getX(), at.getY(), at.getZ(), at.getYaw(), at.getPitch());
// entity.onGround isn't updated right away - we approximate here so
// that things like pathfinding still work *immediately* after spawn.
org.bukkit.Material beneath = at.getBlock().getRelative(BlockFace.DOWN).getType();
if (beneath.isBlock())
entity.onGround = true;
return (LivingEntity) entity.getBukkitEntity();
}
private EntityLiving createEntityFromClass(Object... args) { private EntityLiving createEntityFromClass(Object... args) {
try { try {
Object[] newArgs = Arrays.copyOf(args, args.length + 1); return (EntityLiving) constructor.newInstance(args);
newArgs[args.length] = this;
return (EntityLiving) constructor.newInstance(newArgs);
} catch (Exception ex) { } catch (Exception ex) {
ex.printStackTrace(); ex.printStackTrace();
return null; return null;
} }
} }
@Override
protected EntityLiving createHandle(Location loc) {
EntityLiving entity = createEntityFromClass(((CraftWorld) loc.getWorld()).getHandle());
entity.setPositionRotation(loc.getX(), loc.getY(), loc.getZ(), loc.getYaw(), loc.getPitch());
// entity.onGround isn't updated right away - we approximate here so
// that things like pathfinding still work *immediately* after spawn.
org.bukkit.Material beneath = loc.getBlock().getRelative(BlockFace.DOWN).getType();
if (beneath.isBlock())
entity.onGround = true;
return entity;
}
private static final Map<Class<?>, Constructor<?>> CONSTRUCTOR_CACHE = Maps.newHashMap(); private static final Map<Class<?>, Constructor<?>> CONSTRUCTOR_CACHE = Maps.newHashMap();
private static Constructor<?> getConstructor(Class<?> clazz) { private static Constructor<?> getConstructor(Class<?> clazz) {

View File

@ -32,6 +32,8 @@ public class CitizensNavigator implements Navigator, Runnable {
private int lastX, lastY, lastZ; private int lastX, lastY, lastZ;
private NavigatorParameters localParams = defaultParams; private NavigatorParameters localParams = defaultParams;
private final CitizensNPC npc; private final CitizensNPC npc;
private final Location stationaryLocation = new Location(null, 0, 0, 0);
private int stationaryTicks; private int stationaryTicks;
public CitizensNavigator(CitizensNPC npc) { public CitizensNavigator(CitizensNPC npc) {
@ -211,19 +213,19 @@ public class CitizensNavigator implements Navigator, Runnable {
private boolean updateStationaryStatus() { private boolean updateStationaryStatus() {
if (localParams.stationaryTicks() < 0) if (localParams.stationaryTicks() < 0)
return false; return false;
Location handle = npc.getBukkitEntity().getLocation(); npc.getBukkitEntity().getLocation(stationaryLocation);
if (lastX == handle.getBlockX() && lastY == handle.getBlockY() && lastZ == handle.getBlockZ()) { if (lastX == stationaryLocation.getBlockX() && lastY == stationaryLocation.getBlockY()
&& lastZ == stationaryLocation.getBlockZ()) {
if (++stationaryTicks >= localParams.stationaryTicks()) { if (++stationaryTicks >= localParams.stationaryTicks()) {
stopNavigating(CancelReason.STUCK); stopNavigating(CancelReason.STUCK);
return true; return true;
} }
} else } else
stationaryTicks = 0; stationaryTicks = 0;
lastX = handle.getBlockX(); lastX = stationaryLocation.getBlockX();
lastY = handle.getBlockY(); lastY = stationaryLocation.getBlockY();
lastZ = handle.getBlockZ(); lastZ = stationaryLocation.getBlockZ();
return false; return false;
} }
private static int UNINITIALISED_SPEED = Integer.MIN_VALUE; private static int UNINITIALISED_SPEED = Integer.MIN_VALUE;
} }

View File

@ -22,8 +22,12 @@ public class MCTargetStrategy implements PathStrategy, EntityTarget {
private CancelReason cancelReason; private CancelReason cancelReason;
private final EntityLiving handle, target; private final EntityLiving handle, target;
private final Navigation navigation; private final Navigation navigation;
private final Location npcLocation = new Location(null, 0, 0, 0);
private final NavigatorParameters parameters; private final NavigatorParameters parameters;
private final Location targetLocation = new Location(null, 0, 0, 0);
public MCTargetStrategy(NPC handle, LivingEntity target, boolean aggro, NavigatorParameters params) { public MCTargetStrategy(NPC handle, LivingEntity target, boolean aggro, NavigatorParameters params) {
this.handle = ((CraftLivingEntity) handle.getBukkitEntity()).getHandle(); this.handle = ((CraftLivingEntity) handle.getBukkitEntity()).getHandle();
this.target = ((CraftLivingEntity) target).getHandle(); this.target = ((CraftLivingEntity) target).getHandle();
@ -38,14 +42,14 @@ public class MCTargetStrategy implements PathStrategy, EntityTarget {
&& (handle.boundingBox.e > target.boundingBox.b && handle.boundingBox.b < target.boundingBox.e) && (handle.boundingBox.e > target.boundingBox.b && handle.boundingBox.b < target.boundingBox.e)
&& distanceSquared() <= ATTACK_DISTANCE && hasLineOfSight(); && distanceSquared() <= ATTACK_DISTANCE && hasLineOfSight();
} }
@Override @Override
public void clearCancelReason() { public void clearCancelReason() {
cancelReason = null; cancelReason = null;
} }
private double distanceSquared() { private double distanceSquared() {
return handle.getBukkitEntity().getLocation().distanceSquared(target.getBukkitEntity().getLocation()); return handle.getBukkitEntity().getLocation(npcLocation)
.distanceSquared(target.getBukkitEntity().getLocation(targetLocation));
} }
@Override @Override

View File

@ -2,7 +2,7 @@ package net.citizensnpcs.npc.entity;
import net.citizensnpcs.api.event.NPCPushEvent; import net.citizensnpcs.api.event.NPCPushEvent;
import net.citizensnpcs.api.npc.NPC; import net.citizensnpcs.api.npc.NPC;
import net.citizensnpcs.npc.CitizensMobNPC; import net.citizensnpcs.npc.MobEntityController;
import net.citizensnpcs.npc.CitizensNPC; import net.citizensnpcs.npc.CitizensNPC;
import net.citizensnpcs.npc.ai.NPCHolder; import net.citizensnpcs.npc.ai.NPCHolder;
import net.citizensnpcs.util.NMS; import net.citizensnpcs.util.NMS;
@ -17,9 +17,9 @@ import org.bukkit.entity.Bat;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
public class CitizensBatNPC extends CitizensMobNPC { public class BatController extends MobEntityController {
public CitizensBatNPC(int id, String name) { public BatController() {
super(id, name, EntityBatNPC.class); super(EntityBatNPC.class);
} }
@Override @Override

View File

@ -2,7 +2,7 @@ package net.citizensnpcs.npc.entity;
import net.citizensnpcs.api.event.NPCPushEvent; import net.citizensnpcs.api.event.NPCPushEvent;
import net.citizensnpcs.api.npc.NPC; import net.citizensnpcs.api.npc.NPC;
import net.citizensnpcs.npc.CitizensMobNPC; import net.citizensnpcs.npc.MobEntityController;
import net.citizensnpcs.npc.CitizensNPC; import net.citizensnpcs.npc.CitizensNPC;
import net.citizensnpcs.npc.ai.NPCHolder; import net.citizensnpcs.npc.ai.NPCHolder;
import net.citizensnpcs.util.NMS; import net.citizensnpcs.util.NMS;
@ -17,10 +17,9 @@ import org.bukkit.entity.Blaze;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
public class CitizensBlazeNPC extends CitizensMobNPC { public class BlazeController extends MobEntityController {
public BlazeController() {
public CitizensBlazeNPC(int id, String name) { super(EntityBlazeNPC.class);
super(id, name, EntityBlazeNPC.class);
} }
@Override @Override

View File

@ -2,7 +2,7 @@ package net.citizensnpcs.npc.entity;
import net.citizensnpcs.api.event.NPCPushEvent; import net.citizensnpcs.api.event.NPCPushEvent;
import net.citizensnpcs.api.npc.NPC; import net.citizensnpcs.api.npc.NPC;
import net.citizensnpcs.npc.CitizensMobNPC; import net.citizensnpcs.npc.MobEntityController;
import net.citizensnpcs.npc.CitizensNPC; import net.citizensnpcs.npc.CitizensNPC;
import net.citizensnpcs.npc.ai.NPCHolder; import net.citizensnpcs.npc.ai.NPCHolder;
import net.citizensnpcs.util.NMS; import net.citizensnpcs.util.NMS;
@ -17,9 +17,9 @@ import org.bukkit.entity.CaveSpider;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
public class CitizensCaveSpiderNPC extends CitizensMobNPC { public class CaveSpiderController extends MobEntityController {
public CitizensCaveSpiderNPC(int id, String name) { public CaveSpiderController() {
super(id, name, EntityCaveSpiderNPC.class); super(EntityCaveSpiderNPC.class);
} }
@Override @Override

View File

@ -2,7 +2,7 @@ package net.citizensnpcs.npc.entity;
import net.citizensnpcs.api.event.NPCPushEvent; import net.citizensnpcs.api.event.NPCPushEvent;
import net.citizensnpcs.api.npc.NPC; import net.citizensnpcs.api.npc.NPC;
import net.citizensnpcs.npc.CitizensMobNPC; import net.citizensnpcs.npc.MobEntityController;
import net.citizensnpcs.npc.CitizensNPC; import net.citizensnpcs.npc.CitizensNPC;
import net.citizensnpcs.npc.ai.NPCHolder; import net.citizensnpcs.npc.ai.NPCHolder;
import net.citizensnpcs.util.NMS; import net.citizensnpcs.util.NMS;
@ -17,9 +17,9 @@ import org.bukkit.entity.Chicken;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
public class CitizensChickenNPC extends CitizensMobNPC { public class ChickenController extends MobEntityController {
public CitizensChickenNPC(int id, String name) { public ChickenController() {
super(id, name, EntityChickenNPC.class); super(EntityChickenNPC.class);
} }
@Override @Override

View File

@ -1,164 +0,0 @@
package net.citizensnpcs.npc.entity;
import net.citizensnpcs.api.event.NPCPushEvent;
import net.citizensnpcs.api.npc.NPC;
import net.citizensnpcs.api.trait.trait.Equipment;
import net.citizensnpcs.editor.Equipable;
import net.citizensnpcs.npc.CitizensMobNPC;
import net.citizensnpcs.npc.CitizensNPC;
import net.citizensnpcs.npc.ai.NPCHolder;
import net.citizensnpcs.util.Messages;
import net.citizensnpcs.util.Messaging;
import net.citizensnpcs.util.NMS;
import net.citizensnpcs.util.Util;
import net.minecraft.server.v1_4_5.EntityEnderman;
import net.minecraft.server.v1_4_5.World;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.craftbukkit.v1_4_5.CraftServer;
import org.bukkit.craftbukkit.v1_4_5.entity.CraftEnderman;
import org.bukkit.entity.Enderman;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.material.MaterialData;
import org.bukkit.util.Vector;
public class CitizensEndermanNPC extends CitizensMobNPC implements Equipable {
public CitizensEndermanNPC(int id, String name) {
super(id, name, EntityEndermanNPC.class);
}
@Override
public void equip(Player equipper, ItemStack hand) {
if (!hand.getType().isBlock()) {
Messaging.sendErrorTr(equipper, Messages.EQUIPMENT_EDITOR_INVALID_BLOCK);
return;
}
MaterialData carried = getBukkitEntity().getCarriedMaterial();
if (carried.getItemType() == Material.AIR) {
if (hand.getType() == Material.AIR) {
Messaging.sendErrorTr(equipper, Messages.EQUIPMENT_EDITOR_INVALID_BLOCK);
return;
}
} else {
equipper.getWorld().dropItemNaturally(getBukkitEntity().getLocation(), carried.toItemStack(1));
getBukkitEntity().setCarriedMaterial(hand.getData());
}
ItemStack set = hand.clone();
if (set.getType() != Material.AIR) {
set.setAmount(1);
hand.setAmount(hand.getAmount() - 1);
}
getTrait(Equipment.class).set(0, set);
}
@Override
public Enderman getBukkitEntity() {
return (Enderman) super.getBukkitEntity();
}
public static class EndermanNPC extends CraftEnderman implements NPCHolder {
private final CitizensNPC npc;
public EndermanNPC(EntityEndermanNPC entity) {
super((CraftServer) Bukkit.getServer(), entity);
this.npc = entity.npc;
}
@Override
public NPC getNPC() {
return npc;
}
}
public static class EntityEndermanNPC extends EntityEnderman implements NPCHolder {
private final CitizensNPC npc;
public EntityEndermanNPC(World world) {
this(world, null);
}
public EntityEndermanNPC(World world, NPC npc) {
super(world);
this.npc = (CitizensNPC) npc;
if (npc != null) {
NMS.clearGoals(goalSelector, targetSelector);
}
}
@Override
public void bl() {
super.bl();
if (npc != null)
npc.update();
}
@Override
public void bn() {
if (npc == null)
super.bn();
else {
NMS.updateAI(this);
npc.update();
}
}
@Override
public void c() {
if (npc == null)
super.c();
else {
NMS.updateAI(this);
npc.update();
}
}
@Override
public void collide(net.minecraft.server.v1_4_5.Entity entity) {
// this method is called by both the entities involved - cancelling
// it will not stop the NPC from moving.
super.collide(entity);
if (npc != null)
Util.callCollisionEvent(npc, entity);
}
@Override
public void g(double x, double y, double z) {
if (npc == null) {
super.g(x, y, z);
return;
}
if (NPCPushEvent.getHandlerList().getRegisteredListeners().length == 0) {
if (!npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true))
super.g(x, y, z);
return;
}
Vector vector = new Vector(x, y, z);
NPCPushEvent event = Util.callPushEvent(npc, vector);
if (!event.isCancelled()) {
vector = event.getCollisionVector();
super.g(vector.getX(), vector.getY(), vector.getZ());
}
// when another entity collides, this method is called to push the
// NPC so we prevent it from doing anything if the event is
// cancelled.
}
@Override
public Entity getBukkitEntity() {
if (bukkitEntity == null && npc != null)
bukkitEntity = new EndermanNPC(this);
return super.getBukkitEntity();
}
@Override
public NPC getNPC() {
return npc;
}
}
}

View File

@ -1,147 +0,0 @@
package net.citizensnpcs.npc.entity;
import net.citizensnpcs.Settings.Setting;
import net.citizensnpcs.api.CitizensAPI;
import net.citizensnpcs.api.trait.trait.Equipment;
import net.citizensnpcs.editor.Equipable;
import net.citizensnpcs.npc.CitizensNPC;
import net.citizensnpcs.util.Messages;
import net.citizensnpcs.util.Messaging;
import net.citizensnpcs.util.NMS;
import net.citizensnpcs.util.StringHelper;
import net.citizensnpcs.util.Util;
import net.minecraft.server.v1_4_5.EntityLiving;
import net.minecraft.server.v1_4_5.ItemInWorldManager;
import net.minecraft.server.v1_4_5.WorldServer;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.craftbukkit.v1_4_5.CraftWorld;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
public class CitizensHumanNPC extends CitizensNPC implements Equipable {
public CitizensHumanNPC(int id, String name) {
super(id, name);
}
@Override
protected EntityLiving createHandle(final Location loc) {
WorldServer ws = ((CraftWorld) loc.getWorld()).getHandle();
final EntityHumanNPC handle = new EntityHumanNPC(ws.getServer().getServer(), ws,
StringHelper.parseColors(getFullName()), new ItemInWorldManager(ws), this);
handle.getBukkitEntity().teleport(loc);
Bukkit.getScheduler().scheduleSyncDelayedTask(CitizensAPI.getPlugin(), new Runnable() {
@Override
public void run() {
boolean removeFromPlayerList = Setting.REMOVE_PLAYERS_FROM_PLAYER_LIST.asBoolean();
NMS.addOrRemoveFromPlayerList(getBukkitEntity(),
data().get("removefromplayerlist", removeFromPlayerList));
}
}, 1);
handle.getBukkitEntity().setSleepingIgnored(true);
return handle;
}
@Override
public void equip(Player equipper, ItemStack hand) {
Equipment trait = getTrait(Equipment.class);
int slot = 0;
Material type = hand == null ? Material.AIR : hand.getType();
// First, determine the slot to edit
switch (type) {
case SKULL_ITEM:
case PUMPKIN:
case JACK_O_LANTERN:
case LEATHER_HELMET:
case CHAINMAIL_HELMET:
case GOLD_HELMET:
case IRON_HELMET:
case DIAMOND_HELMET:
if (!equipper.isSneaking())
slot = 1;
break;
case LEATHER_CHESTPLATE:
case CHAINMAIL_CHESTPLATE:
case GOLD_CHESTPLATE:
case IRON_CHESTPLATE:
case DIAMOND_CHESTPLATE:
if (!equipper.isSneaking())
slot = 2;
break;
case LEATHER_LEGGINGS:
case CHAINMAIL_LEGGINGS:
case GOLD_LEGGINGS:
case IRON_LEGGINGS:
case DIAMOND_LEGGINGS:
if (!equipper.isSneaking())
slot = 3;
break;
case LEATHER_BOOTS:
case CHAINMAIL_BOOTS:
case GOLD_BOOTS:
case IRON_BOOTS:
case DIAMOND_BOOTS:
if (!equipper.isSneaking())
slot = 4;
break;
case AIR:
for (int i = 0; i < 5; i++) {
if (trait.get(i) != null && trait.get(i).getType() != Material.AIR) {
equipper.getWorld().dropItemNaturally(getBukkitEntity().getLocation(), trait.get(i));
trait.set(i, null);
}
}
Messaging.sendTr(equipper, Messages.EQUIPMENT_EDITOR_ALL_ITEMS_REMOVED, getName());
break;
default:
break;
}
// Drop any previous equipment on the ground
ItemStack equippedItem = trait.get(slot);
if (equippedItem != null && equippedItem.getType() != Material.AIR)
equipper.getWorld().dropItemNaturally(getBukkitEntity().getLocation(), equippedItem);
// Now edit the equipment based on the slot
if (type != Material.AIR) {
// Set the proper slot with one of the item
ItemStack clone = hand.clone();
clone.setAmount(1);
trait.set(slot, clone);
hand.setAmount(hand.getAmount() - 1);
}
}
@Override
public Player getBukkitEntity() {
if (getHandle() == null)
return null;
return getHandle().getBukkitEntity();
}
@Override
public EntityHumanNPC getHandle() {
return (EntityHumanNPC) mcEntity;
}
@Override
public void setName(String name) {
super.setName(name);
Location prev = getBukkitEntity().getLocation();
despawn();
spawn(prev);
}
@Override
public void update() {
super.update();
if (isSpawned() && Util.isLoaded(getBukkitEntity().getLocation())) {
if (!getNavigator().isNavigating() && !NMS.inWater(mcEntity))
mcEntity.move(0, -0.2, 0);
// gravity. also works around an entity.onGround not updating issue
// (onGround is normally updated by the client)
}
}
}

View File

@ -1,140 +0,0 @@
package net.citizensnpcs.npc.entity;
import net.citizensnpcs.api.event.NPCPushEvent;
import net.citizensnpcs.api.npc.NPC;
import net.citizensnpcs.editor.Equipable;
import net.citizensnpcs.npc.CitizensMobNPC;
import net.citizensnpcs.npc.CitizensNPC;
import net.citizensnpcs.npc.ai.NPCHolder;
import net.citizensnpcs.trait.Sheared;
import net.citizensnpcs.trait.WoolColor;
import net.citizensnpcs.util.Messages;
import net.citizensnpcs.util.Messaging;
import net.citizensnpcs.util.NMS;
import net.citizensnpcs.util.Util;
import net.minecraft.server.v1_4_5.EntitySheep;
import net.minecraft.server.v1_4_5.World;
import org.bukkit.Bukkit;
import org.bukkit.DyeColor;
import org.bukkit.Material;
import org.bukkit.craftbukkit.v1_4_5.CraftServer;
import org.bukkit.craftbukkit.v1_4_5.entity.CraftSheep;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.entity.Sheep;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.Vector;
public class CitizensSheepNPC extends CitizensMobNPC implements Equipable {
public CitizensSheepNPC(int id, String name) {
super(id, name, EntitySheepNPC.class);
}
@Override
public void equip(Player equipper, ItemStack hand) {
if (hand.getType() == Material.SHEARS) {
Messaging.sendTr(equipper, getTrait(Sheared.class).toggle() ? Messages.SHEARED_SET
: Messages.SHEARED_STOPPED, getName());
} else if (hand.getType() == Material.INK_SACK) {
if (getBukkitEntity().getColor() == DyeColor.getByData((byte) (15 - hand.getData().getData())))
return;
DyeColor color = DyeColor.getByData((byte) (15 - hand.getData().getData()));
getTrait(WoolColor.class).setColor(color);
Messaging.sendTr(equipper, Messages.EQUIPMENT_EDITOR_SHEEP_COLOURED, getName(), color.name()
.toLowerCase().replace("_", " "));
hand.setAmount(hand.getAmount() - 1);
} else {
getTrait(WoolColor.class).setColor(DyeColor.WHITE);
Messaging.sendTr(equipper, Messages.EQUIPMENT_EDITOR_SHEEP_COLOURED, getName(), "white");
}
}
@Override
public Sheep getBukkitEntity() {
return (Sheep) super.getBukkitEntity();
}
public static class EntitySheepNPC extends EntitySheep implements NPCHolder {
private final CitizensNPC npc;
public EntitySheepNPC(World world) {
this(world, null);
}
public EntitySheepNPC(World world, NPC npc) {
super(world);
this.npc = (CitizensNPC) npc;
if (npc != null) {
NMS.clearGoals(goalSelector, targetSelector);
}
}
@Override
public void bl() {
super.bl();
if (npc != null)
npc.update();
}
@Override
public void collide(net.minecraft.server.v1_4_5.Entity entity) {
// this method is called by both the entities involved - cancelling
// it will not stop the NPC from moving.
super.collide(entity);
if (npc != null)
Util.callCollisionEvent(npc, entity);
}
@Override
public void g(double x, double y, double z) {
if (npc == null) {
super.g(x, y, z);
return;
}
if (NPCPushEvent.getHandlerList().getRegisteredListeners().length == 0) {
if (!npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true))
super.g(x, y, z);
return;
}
Vector vector = new Vector(x, y, z);
NPCPushEvent event = Util.callPushEvent(npc, vector);
if (!event.isCancelled()) {
vector = event.getCollisionVector();
super.g(vector.getX(), vector.getY(), vector.getZ());
}
// when another entity collides, this method is called to push the
// NPC so we prevent it from doing anything if the event is
// cancelled.
}
@Override
public Entity getBukkitEntity() {
if (bukkitEntity == null && npc != null)
bukkitEntity = new SheepNPC(this);
return super.getBukkitEntity();
}
@Override
public NPC getNPC() {
return npc;
}
}
public static class SheepNPC extends CraftSheep implements NPCHolder {
private final CitizensNPC npc;
public SheepNPC(EntitySheepNPC entity) {
super((CraftServer) Bukkit.getServer(), entity);
this.npc = entity.npc;
}
@Override
public NPC getNPC() {
return npc;
}
}
}

View File

@ -2,7 +2,7 @@ package net.citizensnpcs.npc.entity;
import net.citizensnpcs.api.event.NPCPushEvent; import net.citizensnpcs.api.event.NPCPushEvent;
import net.citizensnpcs.api.npc.NPC; import net.citizensnpcs.api.npc.NPC;
import net.citizensnpcs.npc.CitizensMobNPC; import net.citizensnpcs.npc.MobEntityController;
import net.citizensnpcs.npc.CitizensNPC; import net.citizensnpcs.npc.CitizensNPC;
import net.citizensnpcs.npc.ai.NPCHolder; import net.citizensnpcs.npc.ai.NPCHolder;
import net.citizensnpcs.util.NMS; import net.citizensnpcs.util.NMS;
@ -17,10 +17,9 @@ import org.bukkit.entity.Cow;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
public class CitizensCowNPC extends CitizensMobNPC { public class CowController extends MobEntityController {
public CowController() {
public CitizensCowNPC(int id, String name) { super(EntityCowNPC.class);
super(id, name, EntityCowNPC.class);
} }
@Override @Override

View File

@ -2,7 +2,7 @@ package net.citizensnpcs.npc.entity;
import net.citizensnpcs.api.event.NPCPushEvent; import net.citizensnpcs.api.event.NPCPushEvent;
import net.citizensnpcs.api.npc.NPC; import net.citizensnpcs.api.npc.NPC;
import net.citizensnpcs.npc.CitizensMobNPC; import net.citizensnpcs.npc.MobEntityController;
import net.citizensnpcs.npc.CitizensNPC; import net.citizensnpcs.npc.CitizensNPC;
import net.citizensnpcs.npc.ai.NPCHolder; import net.citizensnpcs.npc.ai.NPCHolder;
import net.citizensnpcs.util.NMS; import net.citizensnpcs.util.NMS;
@ -18,10 +18,9 @@ import org.bukkit.entity.Creeper;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
public class CitizensCreeperNPC extends CitizensMobNPC { public class CreeperController extends MobEntityController {
public CreeperController() {
public CitizensCreeperNPC(int id, String name) { super(EntityCreeperNPC.class);
super(id, name, EntityCreeperNPC.class);
} }
@Override @Override

View File

@ -2,7 +2,7 @@ package net.citizensnpcs.npc.entity;
import net.citizensnpcs.api.event.NPCPushEvent; import net.citizensnpcs.api.event.NPCPushEvent;
import net.citizensnpcs.api.npc.NPC; import net.citizensnpcs.api.npc.NPC;
import net.citizensnpcs.npc.CitizensMobNPC; import net.citizensnpcs.npc.MobEntityController;
import net.citizensnpcs.npc.CitizensNPC; import net.citizensnpcs.npc.CitizensNPC;
import net.citizensnpcs.npc.ai.NPCHolder; import net.citizensnpcs.npc.ai.NPCHolder;
import net.citizensnpcs.util.NMS; import net.citizensnpcs.util.NMS;
@ -17,10 +17,9 @@ import org.bukkit.entity.EnderDragon;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
public class CitizensEnderDragonNPC extends CitizensMobNPC { public class EnderDragonController extends MobEntityController {
public EnderDragonController() {
public CitizensEnderDragonNPC(int id, String name) { super(EntityEnderDragonNPC.class);
super(id, name, EntityEnderDragonNPC.class);
} }
@Override @Override

View File

@ -0,0 +1,130 @@
package net.citizensnpcs.npc.entity;
import net.citizensnpcs.api.event.NPCPushEvent;
import net.citizensnpcs.api.npc.NPC;
import net.citizensnpcs.npc.MobEntityController;
import net.citizensnpcs.npc.CitizensNPC;
import net.citizensnpcs.npc.ai.NPCHolder;
import net.citizensnpcs.util.NMS;
import net.citizensnpcs.util.Util;
import net.minecraft.server.v1_4_5.EntityEnderman;
import net.minecraft.server.v1_4_5.World;
import org.bukkit.Bukkit;
import org.bukkit.craftbukkit.v1_4_5.CraftServer;
import org.bukkit.craftbukkit.v1_4_5.entity.CraftEnderman;
import org.bukkit.entity.Enderman;
import org.bukkit.entity.Entity;
import org.bukkit.util.Vector;
public class EndermanController extends MobEntityController {
public EndermanController() {
super(EntityEndermanNPC.class);
}
@Override
public Enderman getBukkitEntity() {
return (Enderman) super.getBukkitEntity();
}
public static class EndermanNPC extends CraftEnderman implements NPCHolder {
private final CitizensNPC npc;
public EndermanNPC(EntityEndermanNPC entity) {
super((CraftServer) Bukkit.getServer(), entity);
this.npc = entity.npc;
}
@Override
public NPC getNPC() {
return npc;
}
}
public static class EntityEndermanNPC extends EntityEnderman implements NPCHolder {
private final CitizensNPC npc;
public EntityEndermanNPC(World world) {
this(world, null);
}
public EntityEndermanNPC(World world, NPC npc) {
super(world);
this.npc = (CitizensNPC) npc;
if (npc != null) {
NMS.clearGoals(goalSelector, targetSelector);
}
}
@Override
public void bl() {
super.bl();
if (npc != null)
npc.update();
}
@Override
public void bn() {
if (npc == null)
super.bn();
else {
NMS.updateAI(this);
npc.update();
}
}
@Override
public void c() {
if (npc == null)
super.c();
else {
NMS.updateAI(this);
npc.update();
}
}
@Override
public void collide(net.minecraft.server.v1_4_5.Entity entity) {
// this method is called by both the entities involved - cancelling
// it will not stop the NPC from moving.
super.collide(entity);
if (npc != null)
Util.callCollisionEvent(npc, entity);
}
@Override
public void g(double x, double y, double z) {
if (npc == null) {
super.g(x, y, z);
return;
}
if (NPCPushEvent.getHandlerList().getRegisteredListeners().length == 0) {
if (!npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true))
super.g(x, y, z);
return;
}
Vector vector = new Vector(x, y, z);
NPCPushEvent event = Util.callPushEvent(npc, vector);
if (!event.isCancelled()) {
vector = event.getCollisionVector();
super.g(vector.getX(), vector.getY(), vector.getZ());
}
// when another entity collides, this method is called to push the
// NPC so we prevent it from doing anything if the event is
// cancelled.
}
@Override
public Entity getBukkitEntity() {
if (bukkitEntity == null && npc != null)
bukkitEntity = new EndermanNPC(this);
return super.getBukkitEntity();
}
@Override
public NPC getNPC() {
return npc;
}
}
}

View File

@ -24,6 +24,7 @@ import net.minecraft.server.v1_4_5.Packet5EntityEquipment;
import net.minecraft.server.v1_4_5.World; import net.minecraft.server.v1_4_5.World;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.craftbukkit.v1_4_5.CraftServer; import org.bukkit.craftbukkit.v1_4_5.CraftServer;
import org.bukkit.craftbukkit.v1_4_5.entity.CraftPlayer; import org.bukkit.craftbukkit.v1_4_5.entity.CraftPlayer;
import org.bukkit.metadata.MetadataValue; import org.bukkit.metadata.MetadataValue;
@ -31,7 +32,9 @@ import org.bukkit.plugin.Plugin;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
public class EntityHumanNPC extends EntityPlayer implements NPCHolder { public class EntityHumanNPC extends EntityPlayer implements NPCHolder {
private final Location loadedLocation = new Location(null, 0, 0, 0);
private final CitizensNPC npc; private final CitizensNPC npc;
private final net.minecraft.server.v1_4_5.ItemStack[] previousEquipment = { null, null, null, null, null }; private final net.minecraft.server.v1_4_5.ItemStack[] previousEquipment = { null, null, null, null, null };
public EntityHumanNPC(MinecraftServer minecraftServer, World world, String string, public EntityHumanNPC(MinecraftServer minecraftServer, World world, String string,
@ -125,6 +128,13 @@ public class EntityHumanNPC extends EntityPlayer implements NPCHolder {
if (npc == null) if (npc == null)
return; return;
if (getBukkitEntity() != null && Util.isLoaded(getBukkitEntity().getLocation(loadedLocation))) {
if (!npc.getNavigator().isNavigating() && !NMS.inWater(this))
move(0, -0.2, 0);
// gravity. also works around an entity.onGround not updating issue
// (onGround is normally updated by the client)
}
updateEquipment(); updateEquipment();
if (Math.abs(motX) < EPSILON && Math.abs(motY) < EPSILON && Math.abs(motZ) < EPSILON) if (Math.abs(motX) < EPSILON && Math.abs(motY) < EPSILON && Math.abs(motZ) < EPSILON)
motX = motY = motZ = 0; motX = motY = motZ = 0;

View File

@ -2,7 +2,7 @@ package net.citizensnpcs.npc.entity;
import net.citizensnpcs.api.event.NPCPushEvent; import net.citizensnpcs.api.event.NPCPushEvent;
import net.citizensnpcs.api.npc.NPC; import net.citizensnpcs.api.npc.NPC;
import net.citizensnpcs.npc.CitizensMobNPC; import net.citizensnpcs.npc.MobEntityController;
import net.citizensnpcs.npc.CitizensNPC; import net.citizensnpcs.npc.CitizensNPC;
import net.citizensnpcs.npc.ai.NPCHolder; import net.citizensnpcs.npc.ai.NPCHolder;
import net.citizensnpcs.util.NMS; import net.citizensnpcs.util.NMS;
@ -17,10 +17,9 @@ import org.bukkit.entity.Entity;
import org.bukkit.entity.Ghast; import org.bukkit.entity.Ghast;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
public class CitizensGhastNPC extends CitizensMobNPC { public class GhastController extends MobEntityController {
public GhastController() {
public CitizensGhastNPC(int id, String name) { super(EntityGhastNPC.class);
super(id, name, EntityGhastNPC.class);
} }
@Override @Override

View File

@ -2,7 +2,7 @@ package net.citizensnpcs.npc.entity;
import net.citizensnpcs.api.event.NPCPushEvent; import net.citizensnpcs.api.event.NPCPushEvent;
import net.citizensnpcs.api.npc.NPC; import net.citizensnpcs.api.npc.NPC;
import net.citizensnpcs.npc.CitizensMobNPC; import net.citizensnpcs.npc.MobEntityController;
import net.citizensnpcs.npc.CitizensNPC; import net.citizensnpcs.npc.CitizensNPC;
import net.citizensnpcs.npc.ai.NPCHolder; import net.citizensnpcs.npc.ai.NPCHolder;
import net.citizensnpcs.util.NMS; import net.citizensnpcs.util.NMS;
@ -17,10 +17,9 @@ import org.bukkit.entity.Entity;
import org.bukkit.entity.Giant; import org.bukkit.entity.Giant;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
public class CitizensGiantNPC extends CitizensMobNPC { public class GiantController extends MobEntityController {
public GiantController() {
public CitizensGiantNPC(int id, String name) { super(EntityGiantNPC.class);
super(id, name, EntityGiantNPC.class);
} }
@Override @Override

View File

@ -0,0 +1,41 @@
package net.citizensnpcs.npc.entity;
import net.citizensnpcs.Settings.Setting;
import net.citizensnpcs.api.CitizensAPI;
import net.citizensnpcs.api.npc.NPC;
import net.citizensnpcs.npc.AbstractEntityController;
import net.citizensnpcs.util.NMS;
import net.citizensnpcs.util.StringHelper;
import net.minecraft.server.v1_4_5.ItemInWorldManager;
import net.minecraft.server.v1_4_5.WorldServer;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.craftbukkit.v1_4_5.CraftWorld;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
public class HumanController extends AbstractEntityController {
@Override
protected LivingEntity createEntity(final Location at, final NPC npc) {
WorldServer ws = ((CraftWorld) at.getWorld()).getHandle();
final EntityHumanNPC handle = new EntityHumanNPC(ws.getServer().getServer(), ws,
StringHelper.parseColors(npc.getFullName()), new ItemInWorldManager(ws), npc);
handle.getBukkitEntity().teleport(at);
Bukkit.getScheduler().scheduleSyncDelayedTask(CitizensAPI.getPlugin(), new Runnable() {
@Override
public void run() {
boolean removeFromPlayerList = Setting.REMOVE_PLAYERS_FROM_PLAYER_LIST.asBoolean();
NMS.addOrRemoveFromPlayerList(getBukkitEntity(),
npc.data().get("removefromplayerlist", removeFromPlayerList));
}
}, 1);
handle.getBukkitEntity().setSleepingIgnored(true);
return handle.getBukkitEntity();
}
@Override
public Player getBukkitEntity() {
return (Player) super.getBukkitEntity();
}
}

View File

@ -2,7 +2,7 @@ package net.citizensnpcs.npc.entity;
import net.citizensnpcs.api.event.NPCPushEvent; import net.citizensnpcs.api.event.NPCPushEvent;
import net.citizensnpcs.api.npc.NPC; import net.citizensnpcs.api.npc.NPC;
import net.citizensnpcs.npc.CitizensMobNPC; import net.citizensnpcs.npc.MobEntityController;
import net.citizensnpcs.npc.CitizensNPC; import net.citizensnpcs.npc.CitizensNPC;
import net.citizensnpcs.npc.ai.NPCHolder; import net.citizensnpcs.npc.ai.NPCHolder;
import net.citizensnpcs.util.NMS; import net.citizensnpcs.util.NMS;
@ -17,10 +17,9 @@ import org.bukkit.entity.Entity;
import org.bukkit.entity.IronGolem; import org.bukkit.entity.IronGolem;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
public class CitizensIronGolemNPC extends CitizensMobNPC { public class IronGolemController extends MobEntityController {
public IronGolemController() {
public CitizensIronGolemNPC(int id, String name) { super(EntityIronGolemNPC.class);
super(id, name, EntityIronGolemNPC.class);
} }
@Override @Override

View File

@ -2,7 +2,7 @@ package net.citizensnpcs.npc.entity;
import net.citizensnpcs.api.event.NPCPushEvent; import net.citizensnpcs.api.event.NPCPushEvent;
import net.citizensnpcs.api.npc.NPC; import net.citizensnpcs.api.npc.NPC;
import net.citizensnpcs.npc.CitizensMobNPC; import net.citizensnpcs.npc.MobEntityController;
import net.citizensnpcs.npc.CitizensNPC; import net.citizensnpcs.npc.CitizensNPC;
import net.citizensnpcs.npc.ai.NPCHolder; import net.citizensnpcs.npc.ai.NPCHolder;
import net.citizensnpcs.util.NMS; import net.citizensnpcs.util.NMS;
@ -17,10 +17,9 @@ import org.bukkit.entity.Entity;
import org.bukkit.entity.MagmaCube; import org.bukkit.entity.MagmaCube;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
public class CitizensMagmaCubeNPC extends CitizensMobNPC { public class MagmaCubeController extends MobEntityController {
public MagmaCubeController() {
public CitizensMagmaCubeNPC(int id, String name) { super(EntityMagmaCubeNPC.class);
super(id, name, EntityMagmaCubeNPC.class);
} }
@Override @Override

View File

@ -2,7 +2,7 @@ package net.citizensnpcs.npc.entity;
import net.citizensnpcs.api.event.NPCPushEvent; import net.citizensnpcs.api.event.NPCPushEvent;
import net.citizensnpcs.api.npc.NPC; import net.citizensnpcs.api.npc.NPC;
import net.citizensnpcs.npc.CitizensMobNPC; import net.citizensnpcs.npc.MobEntityController;
import net.citizensnpcs.npc.CitizensNPC; import net.citizensnpcs.npc.CitizensNPC;
import net.citizensnpcs.npc.ai.NPCHolder; import net.citizensnpcs.npc.ai.NPCHolder;
import net.citizensnpcs.util.NMS; import net.citizensnpcs.util.NMS;
@ -17,10 +17,10 @@ import org.bukkit.entity.Entity;
import org.bukkit.entity.MushroomCow; import org.bukkit.entity.MushroomCow;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
public class CitizensMushroomCowNPC extends CitizensMobNPC { public class MushroomCowController extends MobEntityController {
public CitizensMushroomCowNPC(int id, String name) { public MushroomCowController() {
super(id, name, EntityMushroomCowNPC.class); super(EntityMushroomCowNPC.class);
} }
@Override @Override

View File

@ -2,7 +2,7 @@ package net.citizensnpcs.npc.entity;
import net.citizensnpcs.api.event.NPCPushEvent; import net.citizensnpcs.api.event.NPCPushEvent;
import net.citizensnpcs.api.npc.NPC; import net.citizensnpcs.api.npc.NPC;
import net.citizensnpcs.npc.CitizensMobNPC; import net.citizensnpcs.npc.MobEntityController;
import net.citizensnpcs.npc.CitizensNPC; import net.citizensnpcs.npc.CitizensNPC;
import net.citizensnpcs.npc.ai.NPCHolder; import net.citizensnpcs.npc.ai.NPCHolder;
import net.citizensnpcs.util.NMS; import net.citizensnpcs.util.NMS;
@ -17,10 +17,9 @@ import org.bukkit.entity.Entity;
import org.bukkit.entity.Ocelot; import org.bukkit.entity.Ocelot;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
public class CitizensOcelotNPC extends CitizensMobNPC { public class OcelotController extends MobEntityController {
public OcelotController() {
public CitizensOcelotNPC(int id, String name) { super(EntityOcelotNPC.class);
super(id, name, EntityOcelotNPC.class);
} }
@Override @Override

View File

@ -2,13 +2,9 @@ package net.citizensnpcs.npc.entity;
import net.citizensnpcs.api.event.NPCPushEvent; import net.citizensnpcs.api.event.NPCPushEvent;
import net.citizensnpcs.api.npc.NPC; import net.citizensnpcs.api.npc.NPC;
import net.citizensnpcs.editor.Equipable; import net.citizensnpcs.npc.MobEntityController;
import net.citizensnpcs.npc.CitizensMobNPC;
import net.citizensnpcs.npc.CitizensNPC; import net.citizensnpcs.npc.CitizensNPC;
import net.citizensnpcs.npc.ai.NPCHolder; import net.citizensnpcs.npc.ai.NPCHolder;
import net.citizensnpcs.trait.Saddle;
import net.citizensnpcs.util.Messages;
import net.citizensnpcs.util.Messaging;
import net.citizensnpcs.util.NMS; import net.citizensnpcs.util.NMS;
import net.citizensnpcs.util.Util; import net.citizensnpcs.util.Util;
import net.minecraft.server.v1_4_5.EntityLightning; import net.minecraft.server.v1_4_5.EntityLightning;
@ -16,35 +12,15 @@ import net.minecraft.server.v1_4_5.EntityPig;
import net.minecraft.server.v1_4_5.World; import net.minecraft.server.v1_4_5.World;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.craftbukkit.v1_4_5.CraftServer; import org.bukkit.craftbukkit.v1_4_5.CraftServer;
import org.bukkit.craftbukkit.v1_4_5.entity.CraftPig; import org.bukkit.craftbukkit.v1_4_5.entity.CraftPig;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.entity.Pig; import org.bukkit.entity.Pig;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
public class CitizensPigNPC extends CitizensMobNPC implements Equipable { public class PigController extends MobEntityController {
public PigController() {
public CitizensPigNPC(int id, String name) { super(EntityPigNPC.class);
super(id, name, EntityPigNPC.class);
}
@Override
public void equip(Player equipper, ItemStack hand) {
if (hand.getType() == Material.SADDLE) {
if (!getBukkitEntity().hasSaddle()) {
getTrait(Saddle.class).toggle();
hand.setAmount(0);
Messaging.sendTr(equipper, Messages.SADDLED_SET, getName());
}
} else if (getBukkitEntity().hasSaddle()) {
equipper.getWorld().dropItemNaturally(getBukkitEntity().getLocation(),
new ItemStack(Material.SADDLE, 1));
getTrait(Saddle.class).toggle();
Messaging.sendTr(equipper, Messages.SADDLED_STOPPED, getName());
}
} }
@Override @Override

View File

@ -2,7 +2,7 @@ package net.citizensnpcs.npc.entity;
import net.citizensnpcs.api.event.NPCPushEvent; import net.citizensnpcs.api.event.NPCPushEvent;
import net.citizensnpcs.api.npc.NPC; import net.citizensnpcs.api.npc.NPC;
import net.citizensnpcs.npc.CitizensMobNPC; import net.citizensnpcs.npc.MobEntityController;
import net.citizensnpcs.npc.CitizensNPC; import net.citizensnpcs.npc.CitizensNPC;
import net.citizensnpcs.npc.ai.NPCHolder; import net.citizensnpcs.npc.ai.NPCHolder;
import net.citizensnpcs.util.NMS; import net.citizensnpcs.util.NMS;
@ -17,10 +17,10 @@ import org.bukkit.entity.Entity;
import org.bukkit.entity.PigZombie; import org.bukkit.entity.PigZombie;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
public class CitizensPigZombieNPC extends CitizensMobNPC { public class PigZombieController extends MobEntityController {
public CitizensPigZombieNPC(int id, String name) { public PigZombieController() {
super(id, name, EntityPigZombieNPC.class); super(EntityPigZombieNPC.class);
} }
@Override @Override

View File

@ -0,0 +1,110 @@
package net.citizensnpcs.npc.entity;
import net.citizensnpcs.api.event.NPCPushEvent;
import net.citizensnpcs.api.npc.NPC;
import net.citizensnpcs.npc.MobEntityController;
import net.citizensnpcs.npc.CitizensNPC;
import net.citizensnpcs.npc.ai.NPCHolder;
import net.citizensnpcs.util.NMS;
import net.citizensnpcs.util.Util;
import net.minecraft.server.v1_4_5.EntitySheep;
import net.minecraft.server.v1_4_5.World;
import org.bukkit.Bukkit;
import org.bukkit.craftbukkit.v1_4_5.CraftServer;
import org.bukkit.craftbukkit.v1_4_5.entity.CraftSheep;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Sheep;
import org.bukkit.util.Vector;
public class SheepController extends MobEntityController {
public SheepController() {
super(EntitySheepNPC.class);
}
@Override
public Sheep getBukkitEntity() {
return (Sheep) super.getBukkitEntity();
}
public static class EntitySheepNPC extends EntitySheep implements NPCHolder {
private final CitizensNPC npc;
public EntitySheepNPC(World world) {
this(world, null);
}
public EntitySheepNPC(World world, NPC npc) {
super(world);
this.npc = (CitizensNPC) npc;
if (npc != null) {
NMS.clearGoals(goalSelector, targetSelector);
}
}
@Override
public void bl() {
super.bl();
if (npc != null)
npc.update();
}
@Override
public void collide(net.minecraft.server.v1_4_5.Entity entity) {
// this method is called by both the entities involved - cancelling
// it will not stop the NPC from moving.
super.collide(entity);
if (npc != null)
Util.callCollisionEvent(npc, entity);
}
@Override
public void g(double x, double y, double z) {
if (npc == null) {
super.g(x, y, z);
return;
}
if (NPCPushEvent.getHandlerList().getRegisteredListeners().length == 0) {
if (!npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true))
super.g(x, y, z);
return;
}
Vector vector = new Vector(x, y, z);
NPCPushEvent event = Util.callPushEvent(npc, vector);
if (!event.isCancelled()) {
vector = event.getCollisionVector();
super.g(vector.getX(), vector.getY(), vector.getZ());
}
// when another entity collides, this method is called to push the
// NPC so we prevent it from doing anything if the event is
// cancelled.
}
@Override
public Entity getBukkitEntity() {
if (bukkitEntity == null && npc != null)
bukkitEntity = new SheepNPC(this);
return super.getBukkitEntity();
}
@Override
public NPC getNPC() {
return npc;
}
}
public static class SheepNPC extends CraftSheep implements NPCHolder {
private final CitizensNPC npc;
public SheepNPC(EntitySheepNPC entity) {
super((CraftServer) Bukkit.getServer(), entity);
this.npc = entity.npc;
}
@Override
public NPC getNPC() {
return npc;
}
}
}

View File

@ -2,7 +2,7 @@ package net.citizensnpcs.npc.entity;
import net.citizensnpcs.api.event.NPCPushEvent; import net.citizensnpcs.api.event.NPCPushEvent;
import net.citizensnpcs.api.npc.NPC; import net.citizensnpcs.api.npc.NPC;
import net.citizensnpcs.npc.CitizensMobNPC; import net.citizensnpcs.npc.MobEntityController;
import net.citizensnpcs.npc.CitizensNPC; import net.citizensnpcs.npc.CitizensNPC;
import net.citizensnpcs.npc.ai.NPCHolder; import net.citizensnpcs.npc.ai.NPCHolder;
import net.citizensnpcs.util.NMS; import net.citizensnpcs.util.NMS;
@ -17,10 +17,9 @@ import org.bukkit.entity.Entity;
import org.bukkit.entity.Silverfish; import org.bukkit.entity.Silverfish;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
public class CitizensSilverfishNPC extends CitizensMobNPC { public class SilverfishController extends MobEntityController {
public SilverfishController() {
public CitizensSilverfishNPC(int id, String name) { super(EntitySilverfishNPC.class);
super(id, name, EntitySilverfishNPC.class);
} }
@Override @Override

View File

@ -2,7 +2,7 @@ package net.citizensnpcs.npc.entity;
import net.citizensnpcs.api.event.NPCPushEvent; import net.citizensnpcs.api.event.NPCPushEvent;
import net.citizensnpcs.api.npc.NPC; import net.citizensnpcs.api.npc.NPC;
import net.citizensnpcs.npc.CitizensMobNPC; import net.citizensnpcs.npc.MobEntityController;
import net.citizensnpcs.npc.CitizensNPC; import net.citizensnpcs.npc.CitizensNPC;
import net.citizensnpcs.npc.ai.NPCHolder; import net.citizensnpcs.npc.ai.NPCHolder;
import net.citizensnpcs.util.NMS; import net.citizensnpcs.util.NMS;
@ -17,10 +17,9 @@ import org.bukkit.entity.Entity;
import org.bukkit.entity.Skeleton; import org.bukkit.entity.Skeleton;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
public class CitizensSkeletonNPC extends CitizensMobNPC { public class SkeletonController extends MobEntityController {
public SkeletonController() {
public CitizensSkeletonNPC(int id, String name) { super(EntitySkeletonNPC.class);
super(id, name, EntitySkeletonNPC.class);
} }
@Override @Override

View File

@ -2,7 +2,7 @@ package net.citizensnpcs.npc.entity;
import net.citizensnpcs.api.event.NPCPushEvent; import net.citizensnpcs.api.event.NPCPushEvent;
import net.citizensnpcs.api.npc.NPC; import net.citizensnpcs.api.npc.NPC;
import net.citizensnpcs.npc.CitizensMobNPC; import net.citizensnpcs.npc.MobEntityController;
import net.citizensnpcs.npc.CitizensNPC; import net.citizensnpcs.npc.CitizensNPC;
import net.citizensnpcs.npc.ai.NPCHolder; import net.citizensnpcs.npc.ai.NPCHolder;
import net.citizensnpcs.util.NMS; import net.citizensnpcs.util.NMS;
@ -17,10 +17,10 @@ import org.bukkit.entity.Entity;
import org.bukkit.entity.Slime; import org.bukkit.entity.Slime;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
public class CitizensSlimeNPC extends CitizensMobNPC { public class SlimeController extends MobEntityController {
public CitizensSlimeNPC(int id, String name) { public SlimeController() {
super(id, name, EntitySlimeNPC.class); super(EntitySlimeNPC.class);
} }
@Override @Override

View File

@ -2,7 +2,7 @@ package net.citizensnpcs.npc.entity;
import net.citizensnpcs.api.event.NPCPushEvent; import net.citizensnpcs.api.event.NPCPushEvent;
import net.citizensnpcs.api.npc.NPC; import net.citizensnpcs.api.npc.NPC;
import net.citizensnpcs.npc.CitizensMobNPC; import net.citizensnpcs.npc.MobEntityController;
import net.citizensnpcs.npc.CitizensNPC; import net.citizensnpcs.npc.CitizensNPC;
import net.citizensnpcs.npc.ai.NPCHolder; import net.citizensnpcs.npc.ai.NPCHolder;
import net.citizensnpcs.util.NMS; import net.citizensnpcs.util.NMS;
@ -17,10 +17,9 @@ import org.bukkit.entity.Entity;
import org.bukkit.entity.Snowman; import org.bukkit.entity.Snowman;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
public class CitizensSnowmanNPC extends CitizensMobNPC { public class SnowmanController extends MobEntityController {
public SnowmanController() {
public CitizensSnowmanNPC(int id, String name) { super(EntitySnowmanNPC.class);
super(id, name, EntitySnowmanNPC.class);
} }
@Override @Override

View File

@ -2,7 +2,7 @@ package net.citizensnpcs.npc.entity;
import net.citizensnpcs.api.event.NPCPushEvent; import net.citizensnpcs.api.event.NPCPushEvent;
import net.citizensnpcs.api.npc.NPC; import net.citizensnpcs.api.npc.NPC;
import net.citizensnpcs.npc.CitizensMobNPC; import net.citizensnpcs.npc.MobEntityController;
import net.citizensnpcs.npc.CitizensNPC; import net.citizensnpcs.npc.CitizensNPC;
import net.citizensnpcs.npc.ai.NPCHolder; import net.citizensnpcs.npc.ai.NPCHolder;
import net.citizensnpcs.util.NMS; import net.citizensnpcs.util.NMS;
@ -17,9 +17,9 @@ import org.bukkit.entity.Entity;
import org.bukkit.entity.Spider; import org.bukkit.entity.Spider;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
public class CitizensSpiderNPC extends CitizensMobNPC { public class SpiderController extends MobEntityController {
public CitizensSpiderNPC(int id, String name) { public SpiderController() {
super(id, name, EntitySpiderNPC.class); super(EntitySpiderNPC.class);
} }
@Override @Override

View File

@ -2,7 +2,7 @@ package net.citizensnpcs.npc.entity;
import net.citizensnpcs.api.event.NPCPushEvent; import net.citizensnpcs.api.event.NPCPushEvent;
import net.citizensnpcs.api.npc.NPC; import net.citizensnpcs.api.npc.NPC;
import net.citizensnpcs.npc.CitizensMobNPC; import net.citizensnpcs.npc.MobEntityController;
import net.citizensnpcs.npc.CitizensNPC; import net.citizensnpcs.npc.CitizensNPC;
import net.citizensnpcs.npc.ai.NPCHolder; import net.citizensnpcs.npc.ai.NPCHolder;
import net.citizensnpcs.util.NMS; import net.citizensnpcs.util.NMS;
@ -17,10 +17,9 @@ import org.bukkit.entity.Entity;
import org.bukkit.entity.Squid; import org.bukkit.entity.Squid;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
public class CitizensSquidNPC extends CitizensMobNPC { public class SquidController extends MobEntityController {
public SquidController() {
public CitizensSquidNPC(int id, String name) { super(EntitySquidNPC.class);
super(id, name, EntitySquidNPC.class);
} }
@Override @Override

View File

@ -2,7 +2,7 @@ package net.citizensnpcs.npc.entity;
import net.citizensnpcs.api.event.NPCPushEvent; import net.citizensnpcs.api.event.NPCPushEvent;
import net.citizensnpcs.api.npc.NPC; import net.citizensnpcs.api.npc.NPC;
import net.citizensnpcs.npc.CitizensMobNPC; import net.citizensnpcs.npc.MobEntityController;
import net.citizensnpcs.npc.CitizensNPC; import net.citizensnpcs.npc.CitizensNPC;
import net.citizensnpcs.npc.ai.NPCHolder; import net.citizensnpcs.npc.ai.NPCHolder;
import net.citizensnpcs.util.NMS; import net.citizensnpcs.util.NMS;
@ -18,10 +18,9 @@ import org.bukkit.entity.Entity;
import org.bukkit.entity.Villager; import org.bukkit.entity.Villager;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
public class CitizensVillagerNPC extends CitizensMobNPC { public class VillagerController extends MobEntityController {
public VillagerController() {
public CitizensVillagerNPC(int id, String name) { super(EntityVillagerNPC.class);
super(id, name, EntityVillagerNPC.class);
} }
@Override @Override

View File

@ -2,7 +2,7 @@ package net.citizensnpcs.npc.entity;
import net.citizensnpcs.api.event.NPCPushEvent; import net.citizensnpcs.api.event.NPCPushEvent;
import net.citizensnpcs.api.npc.NPC; import net.citizensnpcs.api.npc.NPC;
import net.citizensnpcs.npc.CitizensMobNPC; import net.citizensnpcs.npc.MobEntityController;
import net.citizensnpcs.npc.CitizensNPC; import net.citizensnpcs.npc.CitizensNPC;
import net.citizensnpcs.npc.ai.NPCHolder; import net.citizensnpcs.npc.ai.NPCHolder;
import net.citizensnpcs.util.NMS; import net.citizensnpcs.util.NMS;
@ -17,9 +17,9 @@ import org.bukkit.entity.Entity;
import org.bukkit.entity.Witch; import org.bukkit.entity.Witch;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
public class CitizensWitchNPC extends CitizensMobNPC { public class WitchController extends MobEntityController {
public CitizensWitchNPC(int id, String name) { public WitchController() {
super(id, name, EntityWitchNPC.class); super(EntityWitchNPC.class);
} }
@Override @Override

View File

@ -2,7 +2,7 @@ package net.citizensnpcs.npc.entity;
import net.citizensnpcs.api.event.NPCPushEvent; import net.citizensnpcs.api.event.NPCPushEvent;
import net.citizensnpcs.api.npc.NPC; import net.citizensnpcs.api.npc.NPC;
import net.citizensnpcs.npc.CitizensMobNPC; import net.citizensnpcs.npc.MobEntityController;
import net.citizensnpcs.npc.CitizensNPC; import net.citizensnpcs.npc.CitizensNPC;
import net.citizensnpcs.npc.ai.NPCHolder; import net.citizensnpcs.npc.ai.NPCHolder;
import net.citizensnpcs.util.NMS; import net.citizensnpcs.util.NMS;
@ -17,9 +17,9 @@ import org.bukkit.entity.Entity;
import org.bukkit.entity.Wither; import org.bukkit.entity.Wither;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
public class CitizensWitherNPC extends CitizensMobNPC { public class WitherController extends MobEntityController {
public CitizensWitherNPC(int id, String name) { public WitherController() {
super(id, name, EntityWitherNPC.class); super(EntityWitherNPC.class);
} }
@Override @Override

View File

@ -2,7 +2,7 @@ package net.citizensnpcs.npc.entity;
import net.citizensnpcs.api.event.NPCPushEvent; import net.citizensnpcs.api.event.NPCPushEvent;
import net.citizensnpcs.api.npc.NPC; import net.citizensnpcs.api.npc.NPC;
import net.citizensnpcs.npc.CitizensMobNPC; import net.citizensnpcs.npc.MobEntityController;
import net.citizensnpcs.npc.CitizensNPC; import net.citizensnpcs.npc.CitizensNPC;
import net.citizensnpcs.npc.ai.NPCHolder; import net.citizensnpcs.npc.ai.NPCHolder;
import net.citizensnpcs.util.NMS; import net.citizensnpcs.util.NMS;
@ -17,10 +17,9 @@ import org.bukkit.entity.Entity;
import org.bukkit.entity.Wolf; import org.bukkit.entity.Wolf;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
public class CitizensWolfNPC extends CitizensMobNPC { public class WolfController extends MobEntityController {
public WolfController() {
public CitizensWolfNPC(int id, String name) { super(EntityWolfNPC.class);
super(id, name, EntityWolfNPC.class);
} }
@Override @Override

View File

@ -2,7 +2,7 @@ package net.citizensnpcs.npc.entity;
import net.citizensnpcs.api.event.NPCPushEvent; import net.citizensnpcs.api.event.NPCPushEvent;
import net.citizensnpcs.api.npc.NPC; import net.citizensnpcs.api.npc.NPC;
import net.citizensnpcs.npc.CitizensMobNPC; import net.citizensnpcs.npc.MobEntityController;
import net.citizensnpcs.npc.CitizensNPC; import net.citizensnpcs.npc.CitizensNPC;
import net.citizensnpcs.npc.ai.NPCHolder; import net.citizensnpcs.npc.ai.NPCHolder;
import net.citizensnpcs.util.NMS; import net.citizensnpcs.util.NMS;
@ -17,10 +17,9 @@ import org.bukkit.entity.Entity;
import org.bukkit.entity.Zombie; import org.bukkit.entity.Zombie;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
public class CitizensZombieNPC extends CitizensMobNPC { public class ZombieController extends MobEntityController {
public ZombieController() {
public CitizensZombieNPC(int id, String name) { super(EntityZombieNPC.class);
super(id, name, EntityZombieNPC.class);
} }
@Override @Override

View File

@ -7,31 +7,29 @@ import org.bukkit.Location;
public class CurrentLocation extends Trait { public class CurrentLocation extends Trait {
@Persist(value = "", required = true) @Persist(value = "", required = true)
private Location loc; private Location location = new Location(null, 0, 0, 0);
public CurrentLocation() { public CurrentLocation() {
super("location"); super("location");
} }
public Location getLocation() { public Location getLocation() {
if (loc != null && loc.getWorld() == null) return location.getWorld() == null ? null : location;
return null;
return loc;
} }
@Override @Override
public void run() { public void run() {
if (!npc.isSpawned()) if (!npc.isSpawned())
return; return;
loc = npc.getBukkitEntity().getLocation(); location = npc.getBukkitEntity().getLocation(location);
} }
public void setLocation(Location loc) { public void setLocation(Location loc) {
this.loc = loc; this.location = loc;
} }
@Override @Override
public String toString() { public String toString() {
return "CurrentLocation{" + loc + "}"; return "CurrentLocation{" + location + "}";
} }
} }

View File

@ -21,7 +21,9 @@ import org.bukkit.entity.Player;
public class LookClose extends Trait implements Toggleable, CommandConfigurable { public class LookClose extends Trait implements Toggleable, CommandConfigurable {
private boolean enabled = Setting.DEFAULT_LOOK_CLOSE.asBoolean(); private boolean enabled = Setting.DEFAULT_LOOK_CLOSE.asBoolean();
private Player lookingAt; private Player lookingAt;
private final Location npcLocation = new Location(null, 0, 0, 0);
private double range = Setting.DEFAULT_LOOK_CLOSE_RANGE.asDouble(); private double range = Setting.DEFAULT_LOOK_CLOSE_RANGE.asDouble();
private boolean realisticLooking = Setting.DEFAULT_REALISTIC_LOOKING.asBoolean(); private boolean realisticLooking = Setting.DEFAULT_REALISTIC_LOOKING.asBoolean();
public LookClose() { public LookClose() {
@ -41,7 +43,7 @@ public class LookClose extends Trait implements Toggleable, CommandConfigurable
private void findNewTarget() { private void findNewTarget() {
List<Entity> nearby = npc.getBukkitEntity().getNearbyEntities(range, range, range); List<Entity> nearby = npc.getBukkitEntity().getNearbyEntities(range, range, range);
final Location npcLocation = npc.getBukkitEntity().getLocation(); npc.getBukkitEntity().getLocation(npcLocation);
Collections.sort(nearby, new Comparator<Entity>() { Collections.sort(nearby, new Comparator<Entity>() {
@Override @Override
public int compare(Entity o1, Entity o2) { public int compare(Entity o1, Entity o2) {

View File

@ -50,6 +50,7 @@ public class Messages {
public static final String CURRENT_WAYPOINT_PROVIDER = "citizens.waypoints.current-provider"; public static final String CURRENT_WAYPOINT_PROVIDER = "citizens.waypoints.current-provider";
public static final String DATABASE_CONNECTION_FAILED = "citizens.notifications.database-connection-failed"; public static final String DATABASE_CONNECTION_FAILED = "citizens.notifications.database-connection-failed";
public static final String DELAY_TRIGGER_PROMPT = "citizens.editors.waypoints.triggers.delay.prompt"; public static final String DELAY_TRIGGER_PROMPT = "citizens.editors.waypoints.triggers.delay.prompt";
public static final String ENTITY_TYPE_SET = "citizens.commands.npc.type.set";
public static final String EQUIPMENT_EDITOR_ALL_ITEMS_REMOVED = "citizens.editors.equipment.all-items-removed"; public static final String EQUIPMENT_EDITOR_ALL_ITEMS_REMOVED = "citizens.editors.equipment.all-items-removed";
public static final String EQUIPMENT_EDITOR_BEGIN = "citizens.editors.equipment.begin"; public static final String EQUIPMENT_EDITOR_BEGIN = "citizens.editors.equipment.begin";
public static final String EQUIPMENT_EDITOR_END = "citizens.editors.equipment.end"; public static final String EQUIPMENT_EDITOR_END = "citizens.editors.equipment.end";
@ -78,6 +79,7 @@ public class Messages {
public static final String INVALID_AGE = "citizens.commands.npc.age.invalid-age"; public static final String INVALID_AGE = "citizens.commands.npc.age.invalid-age";
public static final String INVALID_ANCHOR_NAME = "citizens.commands.npc.anchor.invalid-name"; public static final String INVALID_ANCHOR_NAME = "citizens.commands.npc.anchor.invalid-name";
public static final String INVALID_ANIMATION = "citizens.editors.waypoints.triggers.animation.invalid-animation"; public static final String INVALID_ANIMATION = "citizens.editors.waypoints.triggers.animation.invalid-animation";
public static final String INVALID_ENTITY_TYPE = "citizens.commands.npc.type.invalid";
public static final String INVALID_POSE_NAME = "citizens.commands.npc.pose.invalid-name"; public static final String INVALID_POSE_NAME = "citizens.commands.npc.pose.invalid-name";
public static final String INVALID_PROFESSION = "citizens.commands.npc.profession.invalid-profession"; public static final String INVALID_PROFESSION = "citizens.commands.npc.profession.invalid-profession";
public static final String INVALID_SKELETON_TYPE = "citizens.commands.npc.skeletontype.invalid-type"; public static final String INVALID_SKELETON_TYPE = "citizens.commands.npc.skeletontype.invalid-type";
@ -140,7 +142,6 @@ public class Messages {
public static final String REMOVED_FROM_PLAYERLIST = "citizens.commands.npc.playerlist.removed"; public static final String REMOVED_FROM_PLAYERLIST = "citizens.commands.npc.playerlist.removed";
public static final String SADDLED_SET = "citizens.editors.equipment.saddled-set"; public static final String SADDLED_SET = "citizens.editors.equipment.saddled-set";
public static final String SADDLED_STOPPED = "citizens.editors.equipment.saddled-stopped"; public static final String SADDLED_STOPPED = "citizens.editors.equipment.saddled-stopped";
public static final String SAVE_METHOD_SET_NOTIFICATION = "citizens.notifications.save-method-set";
public static final String SCRIPT_COMPILED = "citizens.commands.script.compiled"; public static final String SCRIPT_COMPILED = "citizens.commands.script.compiled";
public static final String SCRIPT_COMPILING = "citizens.commands.script.compiling"; public static final String SCRIPT_COMPILING = "citizens.commands.script.compiling";
public static final String SCRIPT_FILE_MISSING = "citizens.commands.script.file-missing"; public static final String SCRIPT_FILE_MISSING = "citizens.commands.script.file-missing";

View File

@ -14,8 +14,8 @@ public class StringHelper {
public static String capitalize(Object string) { public static String capitalize(Object string) {
String capitalize = string.toString(); String capitalize = string.toString();
return capitalize.replaceFirst(String.valueOf(capitalize.charAt(0)), return capitalize.length() == 0 ? "" : Character.toUpperCase(capitalize.charAt(0))
String.valueOf(Character.toUpperCase(capitalize.charAt(0)))); + capitalize.substring(1, capitalize.length());
} }
public static int getLevenshteinDistance(String s, String t) { public static int getLevenshteinDistance(String s, String t) {

View File

@ -27,6 +27,10 @@ public class Util {
private Util() { private Util() {
} }
private static final Location atLocation = new Location(null, 0, 0, 0);
private static final Location fromLocation = new Location(null, 0, 0, 0);
private static Class<?> RNG_CLASS = null; private static Class<?> RNG_CLASS = null;
public static void assumePose(org.bukkit.entity.Entity entity, float yaw, float pitch) { public static void assumePose(org.bukkit.entity.Entity entity, float yaw, float pitch) {
@ -49,11 +53,16 @@ public class Util {
public static void faceEntity(Entity from, Entity at) { public static void faceEntity(Entity from, Entity at) {
if (from.getWorld() != at.getWorld()) if (from.getWorld() != at.getWorld())
return; return;
Location loc = from.getLocation(); double xDiff, yDiff, zDiff;
synchronized (fromLocation) {
double xDiff = at.getLocation().getX() - loc.getX(); from.getLocation(fromLocation);
double yDiff = at.getLocation().getY() - loc.getY(); synchronized (atLocation) {
double zDiff = at.getLocation().getZ() - loc.getZ(); at.getLocation(atLocation);
xDiff = atLocation.getX() - fromLocation.getX();
yDiff = atLocation.getY() - fromLocation.getY();
zDiff = atLocation.getZ() - fromLocation.getZ();
}
}
double distanceXZ = Math.sqrt(xDiff * xDiff + zDiff * zDiff); double distanceXZ = Math.sqrt(xDiff * xDiff + zDiff * zDiff);
double distanceY = Math.sqrt(distanceXZ * distanceXZ + yDiff * yDiff); double distanceY = Math.sqrt(distanceXZ * distanceXZ + yDiff * yDiff);

View File

@ -78,6 +78,8 @@ citizens.commands.npc.speed.modifier-above-limit=Speed is above the limit.
citizens.commands.npc.speed.set=NPC speed modifier set to [[{0}]]. citizens.commands.npc.speed.set=NPC speed modifier set to [[{0}]].
citizens.commands.npc.tp.teleported=You teleported to [[{0}]]. citizens.commands.npc.tp.teleported=You teleported to [[{0}]].
citizens.commands.npc.tphere.teleported=[[{0}]] was teleported to your location. citizens.commands.npc.tphere.teleported=[[{0}]] was teleported to your location.
citizens.commands.npc.type.set=[[{0}]]''s type set to [[{1}]].
citizens.commands.npc.type.invalid=[[{0}]] is not a valid type.
citizens.commands.npc.vulnerable.set=[[{0}]] is now vulnerable. citizens.commands.npc.vulnerable.set=[[{0}]] is now vulnerable.
citizens.commands.npc.vulnerable.stopped=[[{0}]] is no longer vulnerable. citizens.commands.npc.vulnerable.stopped=[[{0}]] is no longer vulnerable.
citizens.commands.page-missing=The page [[{0}]] does not exist. citizens.commands.page-missing=The page [[{0}]] does not exist.
@ -190,7 +192,6 @@ citizens.notifications.npc-not-found=No NPC could be found.
citizens.notifications.npcs-loaded=Loaded {0} NPCs. citizens.notifications.npcs-loaded=Loaded {0} NPCs.
citizens.notifications.reloaded=Citizens reloaded. citizens.notifications.reloaded=Citizens reloaded.
citizens.notifications.reloading=Reloading Citizens... citizens.notifications.reloading=Reloading Citizens...
citizens.notifications.save-method-set=Save method set to {0}.
citizens.notifications.saved=Citizens saved. citizens.notifications.saved=Citizens saved.
citizens.notifications.saving=Saving Citizens... citizens.notifications.saving=Saving Citizens...
citizens.notifications.skipping-broken-trait=Skipped broken or missing trait {0} while loading ID {1}. Has the name changed? citizens.notifications.skipping-broken-trait=Skipped broken or missing trait {0} while loading ID {1}. Has the name changed?