Merge branch 'master' into master

This commit is contained in:
lilingfengdev 2024-02-25 16:41:03 +08:00 committed by GitHub
commit 09543183f1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
45 changed files with 360 additions and 181 deletions

View File

@ -60,7 +60,6 @@ import net.citizensnpcs.api.trait.Trait;
import net.citizensnpcs.api.trait.TraitFactory;
import net.citizensnpcs.api.trait.TraitInfo;
import net.citizensnpcs.api.util.Messaging;
import net.citizensnpcs.api.util.NBTStorage;
import net.citizensnpcs.api.util.Placeholders;
import net.citizensnpcs.api.util.SpigotUtil;
import net.citizensnpcs.api.util.Storage;
@ -177,14 +176,7 @@ public class Citizens extends JavaPlugin implements CitizensPlugin {
}
private NPCDataStore createStorage(File folder) {
Storage saves = null;
String type = Setting.STORAGE_TYPE.asString();
if (type.equalsIgnoreCase("nbt")) {
saves = new NBTStorage(new File(folder, Setting.STORAGE_FILE.asString()), "Citizens NPC Storage");
}
if (saves == null) {
saves = new YamlStorage(new File(folder, Setting.STORAGE_FILE.asString()), "Citizens NPC Storage");
}
Storage saves = new YamlStorage(new File(folder, Setting.STORAGE_FILE.asString()), "Citizens NPC Storage");
if (!saves.load())
return null;
@ -612,7 +604,7 @@ public class Citizens extends JavaPlugin implements CitizensPlugin {
Messaging.logTr(Messages.NUM_LOADED_NOTIFICATION, Iterables.size(npcRegistry), "?");
startMetrics();
scheduleSaveTask(Setting.SAVE_TASK_DELAY.asTicks());
scheduleSaveTask(Setting.SAVE_TASK_FREQUENCY.asTicks());
Bukkit.getPluginManager().callEvent(new CitizensEnableEvent());
new PlayerUpdateTask().runTaskTimer(Citizens.this, 0, 1);
enabled = true;

View File

@ -106,6 +106,7 @@ import net.citizensnpcs.api.util.Messaging;
import net.citizensnpcs.editor.Editor;
import net.citizensnpcs.npc.ai.NPCHolder;
import net.citizensnpcs.npc.skin.SkinUpdateTracker;
import net.citizensnpcs.npc.skin.SkinnableEntity;
import net.citizensnpcs.trait.ClickRedirectTrait;
import net.citizensnpcs.trait.CommandTrait;
import net.citizensnpcs.trait.Controllable;
@ -462,6 +463,12 @@ public class EventListen implements Listener {
@EventHandler(ignoreCancelled = true)
public void onNPCLinkToPlayer(NPCLinkToPlayerEvent event) {
NPC npc = event.getNPC();
if (npc.getEntity() instanceof SkinnableEntity) {
SkinnableEntity skinnable = (SkinnableEntity) npc.getEntity();
if (skinnable.getSkinTracker().getSkin() != null) {
skinnable.getSkinTracker().getSkin().apply(skinnable);
}
}
if (npc.isSpawned() && npc.getEntity().getType() == EntityType.PLAYER) {
onNPCPlayerLinkToPlayer(event);
}
@ -483,7 +490,6 @@ public class EventListen implements Listener {
NMS.sendPositionUpdateNearby(tracker, false, null, null, NMS.getHeadYaw(tracker));
}, Setting.TABLIST_REMOVE_PACKET_DELAY.asTicks() + 1);
boolean resetYaw = event.getNPC().data().get(NPC.Metadata.RESET_YAW_ON_SPAWN,
Setting.RESET_YAW_ON_SPAWN.asBoolean());
boolean sendTabRemove = NMS.sendTabListAdd(event.getPlayer(), (Player) tracker);

View File

@ -140,7 +140,7 @@ public class Settings {
"The distance in blocks where the NPC will switch to walking straight towards the target instead of pathfinding<br>Currently only for dynamic targets like entities",
"npc.pathfinding.straight-line-targeting-distance", 5),
DEFAULT_STUCK_ACTION(
"The default action to perform when NPCs are unable to find a path or are stuck in the same block for too long. Supported options are: 'teleport to destination' or 'none'",
"The default action to perform when NPCs are unable to find a path or are stuck in the same block for too long<br>Supported options are: 'teleport to destination' or 'none'",
"npc.pathfinding.default-stuck-action", "none"),
DEFAULT_TALK_CLOSE("npc.default.talk-close.enabled", false),
DEFAULT_TALK_CLOSE_RANGE("Default talk close range in blocks", "npc.default.talk-close.range", 5),
@ -167,7 +167,7 @@ public class Settings {
"npc.pathfinding.disable-mc-fallback-navigation", true),
DISABLE_TABLIST("Whether to remove NPCs from the tablist", "npc.tablist.disable", true),
DISPLAY_ENTITY_HOLOGRAMS(
"Whether to use display entities for holograms by default. In theory more performant than armor stands. Requires 1.19.4 or above. Defaults to false",
"Whether to use display entities for holograms by default (in theory more performant than armor stands)<br>Requires 1.19.4 or above, defaults to false",
"npc.hologram.use-display-entities", false),
ENTITY_SPAWN_WAIT_DURATION(
"Entities are no longer spawned until the chunks are loaded from disk<br>Wait for chunk loading for one second by default, increase if your disk is slow",
@ -176,7 +176,7 @@ public class Settings {
FOLLOW_ACROSS_WORLDS("Whether /npc follow will teleport across worlds to follow its target",
"npc.follow.teleport-across-worlds", false),
HIGHLIGHT_COLOUR("general.color-scheme.message-highlight", "yellow"),
HOLOGRAM_ALWAYS_UPDATE_POSITION("Whether to always update the hologram position every tick.",
HOLOGRAM_ALWAYS_UPDATE_POSITION("Whether to always update the hologram position every tick",
"npc.hologram.always-update-position", false),
HOLOGRAM_UPDATE_RATE("How often to update hologram names (including placeholders)",
"npc.hologram.update-rate-ticks", "npc.hologram.update-rate", "1s"),
@ -184,7 +184,7 @@ public class Settings {
INITIAL_PLAYER_JOIN_SKIN_PACKET_DELAY("How long to wait before sending skins to joined players",
"npc.skins.player-join-update-delay-ticks", "npc.skins.player-join-update-delay", "1s"),
KEEP_CHUNKS_LOADED("Whether to keep NPC chunks loaded", "npc.chunks.always-keep-loaded", false),
LOCALE("Controls translation files - defaults to your system language, set to en if English required",
LOCALE("Controls translation files - defaults to your system language, set to 'en' if English required",
"general.translation.locale", ""),
MAX_CONTROLLABLE_FLIGHT_SPEED(
"The maximum flying speed that controllable NPCs can reach, in Minecraft velocity units",
@ -228,7 +228,7 @@ public class Settings {
"Please wait for {minutes} minutes and {seconds_over} seconds."),
NPC_COST("The default cost to create an NPC", "economy.npc.cost", 100D),
NPC_SKIN_FETCH_DEFAULT(
"Whether to try and look for the player skin for all new NPCs. If this is set to false and you create an NPC named Dinnerbone, the NPC will have the default (steve/alex/...) skin rather than trying to fetch the Dinnerbone skin.",
"Whether to try and look for the player skin for all new NPCs<br>If this is set to false and you create an NPC named Dinnerbone, the NPC will have the default (steve/alex/etc) skin rather than trying to fetch the Dinnerbone skin",
"npc.skins.try-fetch-default-skin", true),
NPC_SKIN_RETRY_DELAY("How long before retrying skin requests (typically due to Mojang rate limiting)",
"npc.skins.retry-delay", "5s"),
@ -248,20 +248,19 @@ public class Settings {
REMOVE_PLAYERS_FROM_PLAYER_LIST("Whether to remove NPCs from the Java list of players",
"npc.player.remove-from-list", true),
RESET_YAW_ON_SPAWN(
"Whether to reset NPC yaw on spawn. Currently this is implemented by an arm swing animation due to Minecraft limitations.",
"Whether to reset NPC yaw on spawn<br>Currently this is implemented by an arm swing animation due to Minecraft limitations",
"npc.default.reset-yaw-on-spawn", true),
RESOURCE_PACK_PATH("The resource pack path to save resource packs to", "general.resource-pack-path",
"plugins/Citizens/resourcepack"),
SAVE_TASK_DELAY("How often to save NPCs to disk", "storage.save-task.delay", "1hr"),
SAVE_TASK_FREQUENCY("How often to save NPCs to disk", "storage.save-task.delay", "storage.save-task-frequency",
"1hr"),
SELECTION_ITEM("The default item in hand to select an NPC", "npc.selection.item", "stick"),
SELECTION_MESSAGE("npc.selection.message", "Selected [[<npc>]] (ID [[<id>]])."),
SERVER_OWNS_NPCS("Whether the server owns NPCs rather than individual players", "npc.server-ownership", false),
SHOP_GLOBAL_VIEW_PERMISSION(
"The global view permission that players need to view any NPC shop. Defaults to empty (no permission required).",
"The global view permission that players need to view any NPC shop<br>Defaults to empty (no permission required).",
"npc.shops.global-view-permission", ""),
STORAGE_FILE("storage.file", "saves.yml"),
STORAGE_TYPE("Although technically Citizens can use NBT storage, it is not well tested and YAML is recommended",
"storage.type", "yaml"),
TABLIST_REMOVE_PACKET_DELAY("How long to wait before sending the tablist remove packet",
"npc.tablist.remove-packet-delay", "1t"),
TALK_CLOSE_TO_NPCS("Whether to talk to NPCs (and therefore bystanders) as well as players",
@ -270,15 +269,15 @@ public class Settings {
USE_BOAT_CONTROLS("Whether to change vehicle direction with movement instead of strafe controls",
"npc.controllable.use-boat-controls", true),
USE_NEW_PATHFINDER(
"Whether to use the Citizens pathfinder instead of the Minecraft pathfinder<br>Much more flexible, but may have different performance characteristics",
"Whether to use the Citizens pathfinder instead of the Minecraft pathfinder<br>Much more flexible, but may have different performance to Minecraft's pathfinder",
"npc.pathfinding.use-new-finder", false),
USE_SCOREBOARD_TEAMS("npc.scoreboard-teams.enable", true),
WARN_ON_RELOAD("general.reload-warning-enabled", true),;
protected String comments;
private String comments;
private Duration duration;
protected String migrate;
protected String path;
private String migrate;
private final String path;
protected Object value;
Setting(String path, Object value) {
@ -348,14 +347,7 @@ public class Settings {
}
protected void loadFromKey(DataKey root) {
if (SUPPORTS_SET_COMMENTS && root.keyExists(path)) {
try {
((YamlKey) root).getSection("").setComments(path,
comments == null ? null : Arrays.asList(comments.split("<br>")));
} catch (Throwable t) {
SUPPORTS_SET_COMMENTS = false;
}
}
setComments(root);
if (migrate != null && root.keyExists(migrate) && !root.keyExists(path)) {
value = root.getRaw(migrate);
root.removeKey(migrate);
@ -366,6 +358,18 @@ public class Settings {
protected void setAtKey(DataKey root) {
root.setRaw(path, value);
setComments(root);
}
private void setComments(DataKey root) {
if (SUPPORTS_SET_COMMENTS && root.keyExists(path)) {
try {
((YamlKey) root).getSection("").setComments(path,
comments == null ? null : Arrays.asList(comments.split("<br>")));
} catch (Throwable t) {
SUPPORTS_SET_COMMENTS = false;
}
}
}
}

View File

@ -339,7 +339,7 @@ public class NPCCommands {
@Command(
aliases = { "npc" },
usage = "armorstand --visible [visible] --small [small] --gravity [gravity] --arms [arms] --baseplate [baseplate] --(body|leftarm|leftleg|rightarm|rightleg)pose [angle x,y,z]",
usage = "armorstand --visible [visible] --small [small] --marker [marker] --gravity [gravity] --arms [arms] --baseplate [baseplate] --(head|body|leftarm|leftleg|rightarm|rightleg)pose [angle x,y,z]",
desc = "编辑盔甲架属性",
modifiers = { "armorstand" },
min = 1,
@ -349,7 +349,7 @@ public class NPCCommands {
@Requirements(selected = true, ownership = true, types = EntityType.ARMOR_STAND)
public void armorstand(CommandContext args, CommandSender sender, NPC npc, @Flag("visible") Boolean visible,
@Flag("small") Boolean small, @Flag("gravity") Boolean gravity, @Flag("arms") Boolean arms,
@Flag("baseplate") Boolean baseplate) throws CommandException {
@Flag("marker") Boolean marker, @Flag("baseplate") Boolean baseplate) throws CommandException {
ArmorStandTrait trait = npc.getOrAddTrait(ArmorStandTrait.class);
if (visible != null) {
trait.setVisible(visible);
@ -360,6 +360,9 @@ public class NPCCommands {
if (gravity != null) {
trait.setGravity(gravity);
}
if (marker != null) {
trait.setMarker(marker);
}
if (arms != null) {
trait.setHasArms(arms);
}
@ -367,6 +370,9 @@ public class NPCCommands {
trait.setHasBaseplate(baseplate);
}
ArmorStand ent = (ArmorStand) npc.getEntity();
if (args.hasValueFlag("headpose")) {
ent.setHeadPose(args.parseEulerAngle(args.getFlag("headpose")));
}
if (args.hasValueFlag("bodypose")) {
ent.setBodyPose(args.parseEulerAngle(args.getFlag("bodypose")));
}
@ -455,7 +461,7 @@ public class NPCCommands {
@Command(
aliases = { "npc" },
usage = "command|cmd (add [command] | remove [id|all] | permissions [permissions] | sequential | random | clearerror [type] (name|uuid) | errormsg [type] [msg] | persistsequence [true|false] | cost [cost] (id) | expcost [cost] (id) | itemcost (id)) (-s(hift)) (-l[eft]/-r[ight]) (-p[layer] -o[p]), --cooldown --gcooldown [seconds] --delay [ticks] --permissions [perms] --n [max # of uses]",
usage = "command|cmd (add [command] | remove [id|all] | permissions [permissions] | sequential | cycle | random | clearerror [type] (name|uuid) | errormsg [type] [msg] | persistsequence [true|false] | cost [cost] (id) | expcost [cost] (id) | itemcost (id)) (-s(hift)) (-l[eft]/-r[ight]) (-p[layer] -o[p]), --cooldown --gcooldown [seconds] --delay [ticks] --permissions [perms] --n [max # of uses]",
desc = "控制单击NPC时将运行的命令",
help = Messages.NPC_COMMAND_HELP,
modifiers = { "command", "cmd" },
@ -469,7 +475,7 @@ public class NPCCommands {
@Flag(value = "delay", defValue = "0") Duration delay,
@Arg(
value = 1,
completions = { "add", "remove", "permissions", "persistsequence", "sequential", "random",
completions = { "add", "remove", "permissions", "persistsequence", "sequential", "cycle", "random",
"hideerrors", "errormsg", "clearerror", "expcost", "itemcost", "cost" }) String action)
throws CommandException {
CommandTrait commands = npc.getOrAddTrait(CommandTrait.class);
@ -521,6 +527,11 @@ public class NPCCommands {
Messaging.sendTr(sender,
commands.getExecutionMode() == ExecutionMode.SEQUENTIAL ? Messages.COMMANDS_SEQUENTIAL_SET
: Messages.COMMANDS_SEQUENTIAL_UNSET);
} else if (action.equalsIgnoreCase("cycle")) {
commands.setExecutionMode(
commands.getExecutionMode() == ExecutionMode.CYCLE ? ExecutionMode.LINEAR : ExecutionMode.CYCLE);
Messaging.sendTr(sender, commands.getExecutionMode() == ExecutionMode.CYCLE ? Messages.COMMANDS_CYCLE_SET
: Messages.COMMANDS_CYCLE_UNSET);
} else if (action.equalsIgnoreCase("persistsequence")) {
if (args.argsLength() == 2) {
commands.setPersistSequence(!commands.persistSequence());
@ -2078,11 +2089,12 @@ public class NPCCommands {
@Command(
aliases = { "npc" },
usage = "pathto me | here | cursor | [x] [y] [z] (--margin [distance margin])",
usage = "pathto me | here | cursor | [x] [y] [z] (--margin [distance margin]) (-s[traight line])",
desc = "开始寻路到某个位置",
modifiers = { "pathto" },
min = 2,
max = 4,
flags = "s",
permission = "citizens.npc.pathto")
public void pathto(CommandContext args, CommandSender sender, NPC npc,
@Arg(value = 1, completions = { "me", "here", "cursor" }) String option, @Flag("margin") Double margin)
@ -2100,7 +2112,11 @@ public class NPCCommands {
loc.setY(args.getDouble(2));
loc.setZ(args.getDouble(3));
}
npc.getNavigator().setTarget(loc);
if (args.hasFlag('s')) {
npc.getNavigator().setStraightLineTarget(loc);
} else {
npc.getNavigator().setTarget(loc);
}
if (margin != null) {
npc.getNavigator().getLocalParameters().distanceMargin(margin);
}

View File

@ -275,6 +275,11 @@ public class CitizensNPC extends AbstractNPC {
}
}
@Override
public void setSneaking(boolean sneaking) {
getOrAddTrait(SneakTrait.class).setSneaking(sneaking);
}
@Override
public boolean spawn(Location at) {
return spawn(at, SpawnReason.PLUGIN);
@ -301,7 +306,20 @@ public class CitizensNPC extends AbstractNPC {
entityController.create(at.clone(), this);
getEntity().setMetadata("NPC", new FixedMetadataValue(CitizensAPI.getPlugin(), true));
getEntity().setMetadata("NPC-ID", new FixedMetadataValue(CitizensAPI.getPlugin(), getId()));
// Spawning the entity will create an entity tracker that is not controlled by Citizens. This is fixed later in
// spawning; to avoid sending packets twice, try to hide the entity initially
if (SUPPORT_VISIBLE_BY_DEFAULT) {
try {
getEntity().setVisibleByDefault(false);
} catch (NoSuchMethodError err) {
SUPPORT_VISIBLE_BY_DEFAULT = false;
}
}
if (!SUPPORT_VISIBLE_BY_DEFAULT && getEntity().getType() == EntityType.PLAYER) {
for (Player player : Bukkit.getServer().getOnlinePlayers()) {
player.hidePlayer((Player) getEntity());
}
}
if (getEntity() instanceof SkinnableEntity && !hasTrait(SkinLayers.class)) {
((SkinnableEntity) getEntity()).setSkinFlags(EnumSet.allOf(SkinLayers.Layer.class));
}
@ -326,15 +344,12 @@ public class CitizensNPC extends AbstractNPC {
Bukkit.getPluginManager().callEvent(new NPCNeedsRespawnEvent(this, at));
return false;
}
// send skin packets, if applicable, before other NMS packets are sent
SkinnableEntity skinnable = getEntity() instanceof SkinnableEntity ? (SkinnableEntity) getEntity() : null;
if (skinnable != null) {
skinnable.getSkinTracker().onSpawnNPC();
}
NMS.setLocationDirectly(getEntity(), at);
NMS.setHeadYaw(getEntity(), at.getYaw());
NMS.setBodyYaw(getEntity(), at.getYaw());
// Paper now doesn't actually set entities as valid for a few ticks while adding entities to chunks
// Need to check the entity is really valid for a few ticks before finalising spawning
Location to = at;
Consumer<Runnable> postSpawn = new Consumer<Runnable>() {
private int timer;
@ -366,7 +381,6 @@ public class CitizensNPC extends AbstractNPC {
return;
}
navigator.onSpawn();
for (Trait trait : Iterables.toArray(traits.values(), Trait.class)) {
try {
trait.onSpawn();
@ -375,9 +389,16 @@ public class CitizensNPC extends AbstractNPC {
ex.printStackTrace();
}
}
EntityType type = getEntity().getType();
// Replace the entity tracker and attempt to show the entity
NMS.replaceTracker(getEntity());
if (SUPPORT_VISIBLE_BY_DEFAULT) {
getEntity().setVisibleByDefault(true);
} else if (getEntity().getType() == EntityType.PLAYER) {
for (Player player : Bukkit.getServer().getOnlinePlayers()) {
player.showPlayer((Player) getEntity());
}
}
EntityType type = getEntity().getType();
if (type.isAlive()) {
LivingEntity entity = (LivingEntity) getEntity();
entity.setRemoveWhenFarAway(false);
@ -532,9 +553,6 @@ public class CitizensNPC extends AbstractNPC {
}
if (getEntity() instanceof Player) {
updateUsingItemState((Player) getEntity());
if (data().has(NPC.Metadata.SNEAKING) && !hasTrait(SneakTrait.class)) {
addTrait(SneakTrait.class);
}
}
}
navigator.run();
@ -615,4 +633,5 @@ public class CitizensNPC extends AbstractNPC {
private static boolean SUPPORT_PICKUP_ITEMS = true;
private static boolean SUPPORT_SILENT = true;
private static boolean SUPPORT_USE_ITEM = true;
private static boolean SUPPORT_VISIBLE_BY_DEFAULT = true;
}

View File

@ -191,9 +191,7 @@ public class CitizensNPCRegistry implements NPCRegistry {
public NPC getNPC(Entity entity) {
if (entity == null)
return null;
if (entity instanceof NPCHolder)
return ((NPCHolder) entity).getNPC();
return NMS.getNPC(entity);
return entity instanceof NPCHolder ? ((NPCHolder) entity).getNPC() : NMS.getNPC(entity);
}
@Override

View File

@ -98,7 +98,7 @@ public class AStarNavigationStrategy extends AbstractPathStrategy {
}
Location loc = npc.getEntity().getLocation();
/* Proper door movement - gets stuck on corners at times
Block block = currLoc.getWorld().getBlockAt(vector.getBlockX(), vector.getBlockY(), vector.getBlockZ());
if (MinecraftBlockExaminer.isDoor(block.getType())) {
Door door = (Door) block.getState().getData();
@ -112,8 +112,8 @@ public class AStarNavigationStrategy extends AbstractPathStrategy {
double dX = dest.getX() - loc.getX();
double dZ = dest.getZ() - loc.getZ();
double dY = dest.getY() - loc.getY();
double xzDistance = dX * dX + dZ * dZ;
if (Math.abs(dY) < 1 && Math.sqrt(xzDistance) <= params.distanceMargin()) {
double xzDistance = Math.sqrt(dX * dX + dZ * dZ);
if (Math.abs(dY) < 1 && xzDistance <= params.distanceMargin()) {
plan.update(npc);
if (plan.isComplete())
return true;
@ -128,7 +128,7 @@ public class AStarNavigationStrategy extends AbstractPathStrategy {
} else {
Vector dir = dest.toVector().subtract(npc.getEntity().getLocation().toVector()).normalize().multiply(0.2);
boolean liquidOrInLiquid = MinecraftBlockExaminer.isLiquidOrInLiquid(loc.getBlock());
if (dY >= 1 && Math.sqrt(xzDistance) <= 0.4 || dY >= 0.2 && liquidOrInLiquid) {
if (dY >= 1 && xzDistance <= 0.4 || dY >= 0.2 && liquidOrInLiquid) {
dir.add(new Vector(0, 0.75, 0));
}
npc.getEntity().setVelocity(dir);

View File

@ -154,6 +154,9 @@ public class CitizensNavigator implements Navigator, Runnable {
if (root.keyExists("pathfindingrange")) {
defaultParams.range((float) root.getDouble("pathfindingrange"));
}
if (root.keyExists("usenewpathfinder")) {
defaultParams.useNewPathfinder(root.getBoolean("usenewpathfinder"));
}
if (root.keyExists("stationaryticks")) {
defaultParams.stationaryTicks(root.getInt("stationaryticks"));
}
@ -334,12 +337,13 @@ public class CitizensNavigator implements Navigator, Runnable {
return;
}
setTarget(params -> {
if (npc.isFlyable())
if (npc.isFlyable()) {
return new FlyingAStarNavigationStrategy(npc, path, params);
else if (params.useNewPathfinder() || !(npc.getEntity() instanceof LivingEntity))
} else if (params.useNewPathfinder() || !(npc.getEntity() instanceof LivingEntity)) {
return new AStarNavigationStrategy(npc, path, params);
else
} else {
return new MCNavigationStrategy(npc, path, params);
}
});
}
@ -353,12 +357,13 @@ public class CitizensNavigator implements Navigator, Runnable {
}
Location target = targetIn.clone();
setTarget(params -> {
if (npc.isFlyable())
if (npc.isFlyable()) {
return new FlyingAStarNavigationStrategy(npc, target, params);
else if (params.useNewPathfinder() || !(npc.getEntity() instanceof LivingEntity))
} else if (params.useNewPathfinder() || !(npc.getEntity() instanceof LivingEntity)) {
return new AStarNavigationStrategy(npc, target, params);
else
} else {
return new MCNavigationStrategy(npc, target, params);
}
});
}

View File

@ -62,9 +62,12 @@ public class StraightLineNavigationStrategy extends AbstractPathStrategy {
return true;
Location currLoc = npc.getEntity().getLocation();
if (currLoc.distance(destination) <= params.distanceMargin())
if (currLoc.distance(destination) <= params.distanceMargin()) {
if (npc.isFlyable()) {
npc.getEntity().setVelocity(new Vector(0, 0, 0));
}
return true;
}
if (target != null) {
destination = params.entityTargetLocationMapper().apply(target);
}

View File

@ -99,9 +99,9 @@ public class Skin {
&& !npc.getOrAddTrait(SkinTrait.class).fetchDefaultSkin())
return false;
if (hasFetched)
if (hasFetched) {
return true;
else {
} else {
if (!fetching) {
fetch();
}

View File

@ -191,7 +191,7 @@ public class SkinPacketTracker {
inProgress.put(player.getUniqueId(), entry);
skin.apply(entity);
if (NMS.sendTabListAdd(player, entity.getBukkitEntity())) {
scheduleRemovePacket(entry, 2);
scheduleRemovePacket(entry, Setting.TABLIST_REMOVE_PACKET_DELAY.asTicks());
}
}

View File

@ -279,7 +279,7 @@ public class SkinUpdateTracker {
}
/**
* Update a player with skin related packets from nearby skinnable NPC's.
* Update a player with skin related packets from nearby skinnable NPCs.
*
* @param player
* The player to update.

View File

@ -77,6 +77,7 @@ public class CommandTrait extends Trait {
private boolean hideErrorMessages;
@Persist
private final List<ItemStack> itemRequirements = Lists.newArrayList();
private int lastUsedId = -1;
@Persist
private boolean persistSequence = false;
@Persist(keyType = UUID.class, reify = true, value = "cooldowns")
@ -278,7 +279,7 @@ public class CommandTrait extends Trait {
return;
}
int max = -1;
if (executionMode == ExecutionMode.SEQUENTIAL) {
if (executionMode == ExecutionMode.SEQUENTIAL || executionMode == ExecutionMode.CYCLE) {
Collections.sort(commandList, Comparator.comparing(o1 -> o1.id));
max = commandList.size() > 0 ? commandList.get(commandList.size() - 1).id : -1;
}
@ -287,6 +288,13 @@ public class CommandTrait extends Trait {
}
for (NPCCommand command : commandList) {
PlayerNPCCommand info = null;
if (executionMode == ExecutionMode.CYCLE) {
if (command.id <= lastUsedId) {
if (lastUsedId != max)
continue;
lastUsedId = -1;
}
}
if (executionMode == ExecutionMode.SEQUENTIAL
&& (info = playerTracking.get(player.getUniqueId())) != null) {
if (info.lastUsedHand != hand) {
@ -300,7 +308,8 @@ public class CommandTrait extends Trait {
}
}
runCommand(player, hand, command);
if (executionMode == ExecutionMode.SEQUENTIAL || (charged != null && !charged))
if (executionMode == ExecutionMode.SEQUENTIAL || executionMode == ExecutionMode.CYCLE
|| (charged != null && !charged))
break;
}
}
@ -508,6 +517,7 @@ public class CommandTrait extends Trait {
}
public enum ExecutionMode {
CYCLE,
LINEAR,
RANDOM,
SEQUENTIAL;

View File

@ -13,10 +13,19 @@ import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.event.inventory.InventoryType.SlotType;
import org.bukkit.event.inventory.TradeSelectEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryView;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.Merchant;
import org.bukkit.inventory.MerchantRecipe;
import org.bukkit.inventory.meta.ItemMeta;
import com.google.common.base.Joiner;
@ -92,7 +101,6 @@ public class ShopTrait extends Trait {
@Override
public void onRemove() {
Messaging.debug("Removing", npc, "default shop due to onRemove");
shops.deleteShop(getDefaultShop());
}
@ -114,7 +122,7 @@ public class ShopTrait extends Trait {
@Persist
private String title;
@Persist
private ShopType type = ShopType.COMMAND;
private ShopType type = ShopType.DEFAULT;
@Persist
private String viewPermission;
@ -141,7 +149,11 @@ public class ShopTrait extends Trait {
Messaging.sendError(sender, "Empty shop");
return;
}
InventoryMenu.createSelfRegistered(new NPCShopViewer(this, sender)).present(sender);
if (type == ShopType.TRADER) {
CitizensAPI.registerEvents(new NPCTraderShopViewer(this, sender));
} else {
InventoryMenu.createSelfRegistered(new NPCShopViewer(this, sender)).present(sender);
}
}
public void displayEditor(ShopTrait trait, Player sender) {
@ -163,6 +175,14 @@ public class ShopTrait extends Trait {
return viewPermission;
}
public ShopType getShopType() {
return type == null ? type = ShopType.DEFAULT : type;
}
public String getTitle() {
return title == null ? "" : title;
}
public void removePage(int index) {
for (int i = 0; i < pages.size(); i++) {
if (pages.get(i).index == index) {
@ -180,6 +200,14 @@ public class ShopTrait extends Trait {
viewPermission = null;
}
}
public void setShopType(ShopType type) {
this.type = type;
}
public void setTitle(String title) {
this.title = title;
}
}
@Menu(title = "NPC Shop Contents Editor", type = InventoryType.CHEST, dimensions = { 5, 9 })
@ -236,11 +264,12 @@ public class ShopTrait extends Trait {
}));
});
}
InventoryMenuSlot prev = ctx.getSlot(4 * 9 + 3);
InventoryMenuSlot edit = ctx.getSlot(4 * 9 + 4);
InventoryMenuSlot next = ctx.getSlot(4 * 9 + 5);
InventoryMenuSlot prev = ctx.getSlot(shop.getShopType().prevSlotIndex);
InventoryMenuSlot edit = ctx.getSlot(shop.getShopType().editSlotIndex);
InventoryMenuSlot next = ctx.getSlot(shop.getShopType().nextSlotIndex);
if (page > 0) {
prev.setItemStack(shopPage.getNextPageItem(null, 4 * 9 + 3), "Previous page (" + newPage + ")");
prev.setItemStack(shopPage.getNextPageItem(null, shop.getShopType().prevSlotIndex),
"Previous page (" + newPage + ")");
Consumer<CitizensInventoryClickEvent> prevItemEditor = prev.getClickHandlers().get(0);
prev.setClickHandler(evt -> {
if (evt.isShiftClick()) {
@ -251,7 +280,7 @@ public class ShopTrait extends Trait {
changePage(page - 1);
});
}
next.setItemStack(shopPage.getNextPageItem(null, 4 * 9 + 5),
next.setItemStack(shopPage.getNextPageItem(null, shop.getShopType().nextSlotIndex),
page + 1 >= shop.pages.size() ? "New page" : "Next page (" + (newPage + 1) + ")");
Consumer<CitizensInventoryClickEvent> nextItemEditor = next.getClickHandlers().get(0);
next.setClickHandler(evt -> {
@ -274,6 +303,11 @@ public class ShopTrait extends Trait {
});
}
@Override
public Inventory createInventory(String title) {
return Bukkit.createInventory(null, shop.getShopType().inventorySize, "NPC Shop Contents Editor");
}
@Override
public void initialise(MenuContext ctx) {
this.ctx = ctx;
@ -398,23 +432,22 @@ public class ShopTrait extends Trait {
}
}
public void onClick(NPCShop shop, InventoryClickEvent event, boolean secondClick) {
Player player = (Player) event.getWhoClicked();
public void onClick(NPCShop shop, Player player, boolean shiftClick, boolean secondClick) {
if (purchases.containsKey(player.getUniqueId()) && timesPurchasable > 0
&& purchases.get(player.getUniqueId()) == timesPurchasable) {
if (alreadyPurchasedMessage != null) {
Messaging.sendColorless(event.getWhoClicked(), placeholders(alreadyPurchasedMessage, player));
Messaging.sendColorless(player, placeholders(alreadyPurchasedMessage, player));
}
return;
}
if (clickToConfirmMessage != null && !secondClick) {
Messaging.sendColorless(event.getWhoClicked(), placeholders(clickToConfirmMessage, player));
Messaging.sendColorless(player, placeholders(clickToConfirmMessage, player));
return;
}
int max = Integer.MAX_VALUE;
if (maxRepeatsOnShiftClick && event.isShiftClick()) {
if (maxRepeatsOnShiftClick && shiftClick) {
for (NPCShopAction action : cost) {
int r = action.getMaxRepeats(event.getWhoClicked());
int r = action.getMaxRepeats(player);
if (r != -1) {
max = Math.min(max, r);
}
@ -423,19 +456,19 @@ public class ShopTrait extends Trait {
return;
}
int repeats = max == Integer.MAX_VALUE ? 1 : max;
List<Transaction> take = apply(cost, action -> action.take(event.getWhoClicked(), repeats));
List<Transaction> take = apply(cost, action -> action.take(player, repeats));
if (take == null) {
if (costMessage != null) {
Messaging.sendColorless(event.getWhoClicked(), placeholders(costMessage, player));
Messaging.sendColorless(player, placeholders(costMessage, player));
}
return;
}
if (apply(result, action -> action.grant(event.getWhoClicked(), repeats)) == null) {
if (apply(result, action -> action.grant(player, repeats)) == null) {
take.forEach(Transaction::rollback);
return;
}
if (resultMessage != null) {
Messaging.sendColorless(event.getWhoClicked(), placeholders(resultMessage, player));
Messaging.sendColorless(player, placeholders(resultMessage, player));
}
if (timesPurchasable > 0) {
int timesPurchasedAlready = purchases.get(player.getUniqueId()) == null ? 0
@ -449,8 +482,11 @@ public class ShopTrait extends Trait {
StringBuffer sb = new StringBuffer();
Matcher matcher = PLACEHOLDER_REGEX.matcher(string);
while (matcher.find()) {
matcher.appendReplacement(sb, Joiner.on(", ").join(
Iterables.transform(matcher.group(1).equals("cost") ? cost : result, NPCShopAction::describe)));
matcher.appendReplacement(sb,
Joiner.on(", ")
.join(Iterables.transform(matcher.group(1).equalsIgnoreCase("cost") ? cost : result,
NPCShopAction::describe))
.replace("$", "\\$").replace("{", "\\{"));
}
matcher.appendTail(sb);
return sb.toString();
@ -712,42 +748,48 @@ public class ShopTrait extends Trait {
@Override
public void initialise(MenuContext ctx) {
this.ctx = ctx;
ctx.getSlot(2)
ctx.getSlot(0)
.setDescription("<f>Edit permission required to view shop<br>" + shop.getRequiredPermission());
ctx.getSlot(6).setDescription("<f>Edit shop title<br>" + shop.title);
ctx.getSlot(4).setDescription("<f>Edit shop title<br>" + shop.getTitle());
if (trait != null) {
ctx.getSlot(8).setDescription(
ctx.getSlot(6).setDescription(
"<f>Show shop on right click<br>" + shop.getName().equals(trait.rightClickShop));
}
}
@MenuSlot(slot = { 0, 4 }, material = Material.FEATHER, amount = 1, title = "<f>Edit shop items")
@MenuSlot(slot = { 0, 2 }, material = Material.FEATHER, amount = 1, title = "<f>Edit shop items")
public void onEditItems(InventoryMenuSlot slot, CitizensInventoryClickEvent event) {
ctx.getMenu().transition(new NPCShopContentsEditor(shop));
}
@MenuSlot(slot = { 0, 2 }, compatMaterial = { "OAK_SIGN", "SIGN" }, amount = 1)
@MenuSlot(slot = { 0, 0 }, compatMaterial = { "OAK_SIGN", "SIGN" }, amount = 1)
public void onPermissionChange(InventoryMenuSlot slot, CitizensInventoryClickEvent event) {
ctx.getMenu().transition(InputMenus.stringSetter(shop::getRequiredPermission, shop::setPermission));
}
@MenuSlot(slot = { 0, 6 }, material = Material.NAME_TAG, amount = 1)
@MenuSlot(slot = { 0, 8 }, material = Material.CHEST, amount = 1, title = "<f>Set shop type")
public void onSetInventoryType(InventoryMenuSlot slot, CitizensInventoryClickEvent event) {
ctx.getMenu().transition(InputMenus.picker("Set shop type",
(Choice<ShopType> choice) -> shop.setShopType(choice.getValue()),
Choice.of(ShopType.DEFAULT, Material.CHEST, "Default (5x9 chest)",
shop.getShopType() == ShopType.DEFAULT),
Choice.of(ShopType.CHEST_4X9, Material.CHEST, "4x9 chest",
shop.getShopType() == ShopType.CHEST_4X9),
Choice.of(ShopType.CHEST_3X9, Material.CHEST, "3x9 chest",
shop.getShopType() == ShopType.CHEST_3X9),
Choice.of(ShopType.CHEST_2X9, Material.CHEST, "2x9 chest",
shop.getShopType() == ShopType.CHEST_2X9),
Choice.of(ShopType.CHEST_1X9, Material.CHEST, "1x9 chest",
shop.getShopType() == ShopType.CHEST_1X9),
Choice.of(ShopType.TRADER, Material.EMERALD, "Trader", shop.getShopType() == ShopType.TRADER)));
}
@MenuSlot(slot = { 0, 4 }, material = Material.NAME_TAG, amount = 1)
public void onSetTitle(InventoryMenuSlot slot, CitizensInventoryClickEvent event) {
ctx.getMenu().transition(InputMenus.stringSetter(() -> shop.title, newTitle -> shop.title = newTitle));
ctx.getMenu().transition(InputMenus.stringSetter(shop::getTitle, shop::setTitle));
}
@MenuSlot(slot = { 0, 0 }, material = Material.BOOK, amount = 1, title = "<f>Edit shop type")
public void onShopTypeChange(InventoryMenuSlot slot, CitizensInventoryClickEvent event) {
ctx.getMenu().transition(InputMenus.<ShopType> picker("Edit shop type",
chosen -> shop.type = chosen.getValue(),
Choice.<ShopType> of(ShopType.BUY, Material.DIAMOND, "Players buy items",
shop.type == ShopType.BUY),
Choice.of(ShopType.SELL, Material.EMERALD, "Players sell items", shop.type == ShopType.SELL),
Choice.of(ShopType.COMMAND, Util.getFallbackMaterial("ENDER_EYE", "ENDER_PEARL"),
"Clicks trigger commands only", shop.type == ShopType.COMMAND)));
}
@MenuSlot(slot = { 0, 8 }, compatMaterial = { "COMMAND_BLOCK", "COMMAND" }, amount = 1)
@MenuSlot(slot = { 0, 6 }, compatMaterial = { "COMMAND_BLOCK", "COMMAND" }, amount = 1)
public void onToggleRightClick(InventoryMenuSlot slot, CitizensInventoryClickEvent event) {
event.setCancelled(true);
if (trait == null)
@ -758,7 +800,7 @@ public class ShopTrait extends Trait {
} else {
trait.rightClickShop = shop.name;
}
ctx.getSlot(8)
ctx.getSlot(6)
.setDescription("<f>Show shop on right click<br>" + shop.getName().equals(trait.rightClickShop));
}
}
@ -793,15 +835,16 @@ public class ShopTrait extends Trait {
ctx.getSlot(i).setItemStack(item.getDisplayItem(player));
ctx.getSlot(i).setClickHandler(evt -> {
evt.setCancelled(true);
item.onClick(shop, evt, lastClickedItem == item);
item.onClick(shop, (Player) evt.getWhoClicked(), evt.isShiftClick(), lastClickedItem == item);
lastClickedItem = item;
});
}
InventoryMenuSlot prev = ctx.getSlot(4 * 9 + 3);
InventoryMenuSlot next = ctx.getSlot(4 * 9 + 5);
InventoryMenuSlot prev = ctx.getSlot(shop.getShopType().prevSlotIndex);
InventoryMenuSlot next = ctx.getSlot(shop.getShopType().nextSlotIndex);
if (currentPage > 0) {
prev.clear();
prev.setItemStack(page.getPreviousPageItem(player, 4 * 9 + 3), "Previous page (" + newPage + ")");
prev.setItemStack(page.getPreviousPageItem(player, shop.getShopType().prevSlotIndex),
"Previous page (" + newPage + ")");
prev.setClickHandler(evt -> {
evt.setCancelled(true);
changePage(currentPage - 1);
@ -809,7 +852,8 @@ public class ShopTrait extends Trait {
}
if (currentPage + 1 < shop.pages.size()) {
next.clear();
next.setItemStack(page.getNextPageItem(player, 4 * 9 + 5), "Next page (" + (newPage + 1) + ")");
next.setItemStack(page.getNextPageItem(player, shop.getShopType().nextSlotIndex),
"Next page (" + (newPage + 1) + ")");
next.setClickHandler(evt -> {
evt.setCancelled(true);
changePage(currentPage + 1);
@ -819,8 +863,8 @@ public class ShopTrait extends Trait {
@Override
public Inventory createInventory(String title) {
return Bukkit.createInventory(null, 45, shop.title == null || shop.title.isEmpty() ? "Shop"
: Messaging.parseComponents(Placeholders.replace(shop.title, player)));
return Bukkit.createInventory(null, shop.getShopType().inventorySize, shop.getTitle().isEmpty() ? "Shop"
: Messaging.parseComponents(Placeholders.replace(shop.getTitle(), player)));
}
@Override
@ -830,10 +874,104 @@ public class ShopTrait extends Trait {
}
}
public static class NPCTraderShopViewer implements Listener {
private int lastClickedTrade = -1;
private final Player player;
private int selectedTrade = -1;
private final NPCShop shop;
private final Map<Integer, NPCShopItem> trades;
private final InventoryView view;
public NPCTraderShopViewer(NPCShop shop, Player player) {
this.shop = shop;
this.player = player;
Map<Integer, NPCShopItem> tradesMap = Maps.newHashMap();
Merchant merchant = Bukkit.createMerchant(shop.getTitle());
List<MerchantRecipe> recipes = Lists.newArrayList();
for (NPCShopPage page : shop.pages) {
for (NPCShopItem item : page.items.values()) {
ItemStack result = item.getDisplayItem(player);
if (result == null)
continue;
MerchantRecipe recipe = new MerchantRecipe(result.clone(), 100000000);
for (NPCShopAction action : item.cost) {
if (action instanceof ItemAction) {
for (ItemStack stack : ((ItemAction) action).items) {
recipe.addIngredient(stack.clone());
if (recipe.getIngredients().size() == 2)
break;
}
}
}
if (recipe.getIngredients().size() == 0)
continue;
tradesMap.put(recipes.size(), item);
recipes.add(recipe);
}
}
merchant.setRecipes(recipes);
trades = tradesMap;
view = player.openMerchant(merchant, true);
}
@EventHandler
public void onInventoryClick(InventoryClickEvent evt) {
if (!evt.getView().equals(view))
return;
evt.setCancelled(true);
if (evt.getSlotType() != SlotType.RESULT || !evt.getAction().name().contains("PICKUP"))
return;
// TODO: work around crafting slot limitations in minecraft
player.getInventory().addItem(evt.getClickedInventory().getItem(0));
evt.getClickedInventory().setItem(0, null);
if (evt.getClickedInventory().getItem(1) != null) {
player.getInventory().addItem(evt.getClickedInventory().getItem(1));
evt.getClickedInventory().setItem(1, null);
}
trades.get(selectedTrade).onClick(shop, player, evt.getClick().isShiftClick(),
lastClickedTrade == selectedTrade);
lastClickedTrade = selectedTrade;
}
@EventHandler
public void onInventoryClose(InventoryCloseEvent evt) {
if (!evt.getPlayer().equals(player))
return;
HandlerList.unregisterAll(this);
}
@EventHandler
public void onTradeSelect(TradeSelectEvent evt) {
if (!evt.getView().equals(view))
return;
selectedTrade = evt.getIndex();
lastClickedTrade = -1;
}
}
public enum ShopType {
BUY,
COMMAND,
SELL;
CHEST_1X9(1 * 9, 7, 6, 8),
CHEST_2X9(2 * 9),
CHEST_3X9(3 * 9),
CHEST_4X9(4 * 9),
DEFAULT(5 * 9),
TRADER(5 * 9);
private final int editSlotIndex;
private final int inventorySize;
private final int nextSlotIndex;
private final int prevSlotIndex;
ShopType(int inventorySize) {
this(inventorySize, inventorySize - 9 + 3, inventorySize - 9 + 4, inventorySize - 9 + 5);
}
ShopType(int inventorySize, int prevSlotIndex, int editSlotIndex, int nextSlotIndex) {
this.inventorySize = inventorySize;
this.prevSlotIndex = prevSlotIndex;
this.editSlotIndex = editSlotIndex;
this.nextSlotIndex = nextSlotIndex;
}
}
static {

View File

@ -1,6 +1,5 @@
package net.citizensnpcs.trait;
import net.citizensnpcs.api.npc.NPC;
import net.citizensnpcs.api.persistence.Persist;
import net.citizensnpcs.api.trait.Trait;
import net.citizensnpcs.api.trait.TraitName;
@ -30,14 +29,6 @@ public class SneakTrait extends Trait {
apply();
}
@Override
public void run() {
if (npc.data().has(NPC.Metadata.SNEAKING)) {
setSneaking(npc.data().get(NPC.Metadata.SNEAKING));
npc.data().remove(NPC.Metadata.SNEAKING);
}
}
public void setSneaking(boolean sneak) {
sneaking = sneak;
apply();

View File

@ -56,18 +56,18 @@ public class ItemAction extends NPCShopAction {
ItemStack[] contents = source.getContents();
for (int i = 0; i < contents.length; i++) {
ItemStack toMatch = contents[i];
if (toMatch == null || toMatch.getType() == Material.AIR || tooDamaged(toMatch)) {
if (toMatch == null || toMatch.getType() == Material.AIR || tooDamaged(toMatch))
continue;
}
toMatch = toMatch.clone();
for (int j = 0; j < items.size(); j++) {
if (toMatch == null) {
if (toMatch == null)
break;
}
ItemStack item = items.get(j);
if (req.get(j) <= 0 || !matches(item, toMatch)) {
if (req.get(j) <= 0 || !matches(item, toMatch))
continue;
}
int remaining = req.get(j);
int taken = toMatch.getAmount() > remaining ? remaining : toMatch.getAmount();

View File

@ -271,14 +271,15 @@ public class GuidedWaypointProvider implements EnumerableWaypointProvider {
tree.clear();
treePlusDestinations.clear();
for (Waypoint waypoint : guides) {
tree.put(new long[] { waypoint.getLocation().getBlockX(), waypoint.getLocation().getBlockY(),
waypoint.getLocation().getBlockZ() }, waypoint);
treePlusDestinations.put(new long[] { waypoint.getLocation().getBlockX(),
waypoint.getLocation().getBlockY(), waypoint.getLocation().getBlockZ() }, waypoint);
Location location = waypoint.getLocation();
tree.put(new long[] { location.getBlockX(), location.getBlockY(), location.getBlockZ() }, waypoint);
treePlusDestinations.put(new long[] { location.getBlockX(), location.getBlockY(), location.getBlockZ() },
waypoint);
}
for (Waypoint waypoint : destinations) {
treePlusDestinations.put(new long[] { waypoint.getLocation().getBlockX(),
waypoint.getLocation().getBlockY(), waypoint.getLocation().getBlockZ() }, waypoint);
Location location = waypoint.getLocation();
treePlusDestinations.put(new long[] { location.getBlockX(), location.getBlockY(), location.getBlockZ() },
waypoint);
}
if (currentGoal != null) {
currentGoal.onProviderChanged();

View File

@ -79,6 +79,8 @@ public class Messages {
public static final String COMMAND_TRIGGER_PROMPT = "citizens.editors.waypoints.triggers.command.prompt";
public static final String COMMAND_UNKNOWN_COMMAND_ID = "citizens.commands.npc.command.unknown-id";
public static final String COMMANDS_CLEARED = "citizens.commands.npc.command.cleared";
public static final String COMMANDS_CYCLE_SET = "citizens.commands.npc.command.cycle-set";
public static final String COMMANDS_CYCLE_UNSET = "citizens.commands.npc.command.cycle-unset";
public static final String COMMANDS_PERSIST_SEQUENCE_SET = "citizens.commands.npc.command.persist-sequence-set";
public static final String COMMANDS_PERSIST_SEQUENCE_UNSET = "citizens.commands.npc.command.persist-sequence-unset";
public static final String COMMANDS_RANDOM_SET = "citizens.commands.npc.commands.random-set";

View File

@ -716,7 +716,7 @@ public class NMS {
}
public static void openHorseScreen(Tameable horse, Player equipper) {
BRIDGE.openHorseScreen(horse, equipper);
BRIDGE.openHorseInventory(horse, equipper);
}
public static void playAnimation(PlayerAnimation animation, Player player, Iterable<Player> to) {
@ -986,9 +986,6 @@ public class NMS {
} catch (ClassNotFoundException e) {
PAPER_KNOCKBACK_EVENT_EXISTS = false;
}
}
static {
giveReflectiveAccess(Field.class, NMS.class);
MODIFIERS_FIELD = NMS.getField(Field.class, "modifiers", false);
}

View File

@ -151,7 +151,7 @@ public interface NMSBridge {
public InventoryView openAnvilInventory(Player player, Inventory anvil, String title);
public void openHorseScreen(Tameable horse, Player equipper);
public void openHorseInventory(Tameable horse, Player equipper);
public void playAnimation(PlayerAnimation animation, Player player, Iterable<Player> to);

View File

@ -52,6 +52,8 @@
"citizens.commands.npc.chunkload.unset" : "[[{0}]] will no longer force chunks to be loaded.",
"citizens.commands.npc.collidable.set" : "[[{0}]] will now collide with entities.",
"citizens.commands.npc.collidable.unset" : "[[{0}]] will no longer collide with entities.",
"citizens.commands.npc.command.cycle-set": "[[{0}]] will now cycle through commands on player click",
"citizens.commands.npc.command.cycle-unset": "[[{0}]] will no longer cycle through commands on player click",
"citizens.commands.npc.command.command-added" : "Command [[{0}]] added with id [[{1}]].",
"citizens.commands.npc.command.command-removed" : "Command [[{0}]] removed.",
"citizens.commands.npc.command.cleared" : "[[{0}]]''s commands cleared.",
@ -60,7 +62,7 @@
"citizens.commands.npc.command.describe-format" : "<br> - {0} [{1}s] [cost:{2}] [exp:{3}] [<click:run_command:/npc cmd remove {4}><hover:show_text:Remove this command><red><u>-</hover></click>]",
"citizens.commands.npc.command.errors-cleared" : "Errors cleared for [[{0}]].",
"citizens.commands.npc.command.experience-cost-set" : "Set xp level cost per click to [[{0}]].",
"citizens.commands.npc.command.help" : "<br>Use the [[-l]] flag to make the command run on left click, [[-r]] on right click (default).<br>Set the per-player cooldown before the command can be used again using [[--cooldown]] (in [[seconds]]).<br>Set the server-wide cooldown in seconds using [[--gcooldown]].<br>[[--delay]] will wait the specified amount in [[ticks]] before executing the command.<br>[[--permissions]] will set the command to require specific permissions (separate multiple with commas).<br>[[--n]] will only let the player run the command that number of times.<br>Use [[-o]] to temporarily execute the command as an op and [[-p]] to run the command as the clicking player instead of the server.<br>To give the player temporary permissions instead of op, use [[/npc command permissions]].<br>Set the cost of each click with [[/npc command cost/expcost/itemcost]].<br>Commands can be executed one by one instead of all at once by using [[/npc command sequential]].",
"citizens.commands.npc.command.help" : "<br>Use the [[-l]] flag to make the command run on left click, [[-r]] on right click (default).<br>Set the per-player cooldown before the command can be used again using [[--cooldown]] (in [[seconds]]).<br>Set the server-wide cooldown in seconds using [[--gcooldown]].<br>[[--delay]] will wait the specified amount in [[ticks]] before executing the command.<br>[[--permissions]] will set the command to require specific permissions (separate multiple with commas).<br>[[--n]] will only let the player run the command that number of times.<br>Use [[-o]] to temporarily execute the command as an op and [[-p]] to run the command as the clicking player instead of the server.<br>To give the player temporary permissions instead of op, use [[/npc command permissions]].<br>Set the cost of each click with [[/npc command cost/expcost/itemcost]].<br>Commands can be executed one by one instead of all at once by using [[/npc command sequential]] or [[/npc command cycle]].",
"citizens.commands.npc.command.hide-error-messages-set" : "Now hiding error messages.",
"citizens.commands.npc.command.hide-error-messages-unset" : "No longer hiding error messages.",
"citizens.commands.npc.command.individual-cost-set" : "Set cost per click to [[{0}]] for command id [[{1}]].",

View File

@ -179,7 +179,7 @@ public class ShulkerController extends MobEntityController {
@Override
protected EntityAIBodyControl s() {
return new EntityAIBodyControl(this);
return npc == null ? super.s() : new EntityAIBodyControl(this);
}
@Override

View File

@ -949,7 +949,7 @@ public class NMSImpl implements NMSBridge {
}
@Override
public void openHorseScreen(Tameable horse, Player equipper) {
public void openHorseInventory(Tameable horse, Player equipper) {
EntityLiving handle = NMSImpl.getHandle((LivingEntity) horse);
EntityLiving equipperHandle = NMSImpl.getHandle(equipper);
if (handle == null || equipperHandle == null)

View File

@ -1,7 +1,6 @@
package net.citizensnpcs.nms.v1_11_R1.entity;
import org.bukkit.Bukkit;
import org.bukkit.DyeColor;
import org.bukkit.craftbukkit.v1_11_R1.CraftServer;
import org.bukkit.craftbukkit.v1_11_R1.entity.CraftEntity;
import org.bukkit.craftbukkit.v1_11_R1.entity.CraftShulker;
@ -180,11 +179,7 @@ public class ShulkerController extends MobEntityController {
@Override
protected EntityAIBodyControl s() {
return new EntityAIBodyControl(this);
}
public void setColor(DyeColor color) {
this.datawatcher.set(bw, color.getWoolData());
return npc == null ? super.s() : new EntityAIBodyControl(this);
}
@Override

View File

@ -1002,7 +1002,7 @@ public class NMSImpl implements NMSBridge {
}
@Override
public void openHorseScreen(Tameable horse, Player equipper) {
public void openHorseInventory(Tameable horse, Player equipper) {
EntityLiving handle = NMSImpl.getHandle((LivingEntity) horse);
EntityLiving equipperHandle = NMSImpl.getHandle(equipper);
if (handle == null || equipperHandle == null)

View File

@ -186,7 +186,7 @@ public class ShulkerController extends MobEntityController {
@Override
protected EntityAIBodyControl s() {
return new EntityAIBodyControl(this);
return npc == null ? super.s() : new EntityAIBodyControl(this);
}
@Override

View File

@ -1009,7 +1009,7 @@ public class NMSImpl implements NMSBridge {
}
@Override
public void openHorseScreen(Tameable horse, Player equipper) {
public void openHorseInventory(Tameable horse, Player equipper) {
EntityLiving handle = NMSImpl.getHandle((LivingEntity) horse);
EntityLiving equipperHandle = NMSImpl.getHandle(equipper);
if (handle == null || equipperHandle == null)

View File

@ -206,7 +206,7 @@ public class ShulkerController extends MobEntityController {
@Override
protected EntityAIBodyControl o() {
return new EntityAIBodyControl(this);
return npc == null ? super.o() : new EntityAIBodyControl(this);
}
@Override

View File

@ -1045,7 +1045,7 @@ public class NMSImpl implements NMSBridge {
}
@Override
public void openHorseScreen(Tameable horse, Player equipper) {
public void openHorseInventory(Tameable horse, Player equipper) {
EntityLiving handle = NMSImpl.getHandle((LivingEntity) horse);
EntityLiving equipperHandle = NMSImpl.getHandle(equipper);
if (handle == null || equipperHandle == null)

View File

@ -202,7 +202,7 @@ public class ShulkerController extends MobEntityController {
@Override
protected EntityAIBodyControl o() {
return new EntityAIBodyControl(this);
return npc == null ? super.o() : new EntityAIBodyControl(this);
}
@Override

View File

@ -1106,7 +1106,7 @@ public class NMSImpl implements NMSBridge {
}
@Override
public void openHorseScreen(Tameable horse, Player equipper) {
public void openHorseInventory(Tameable horse, Player equipper) {
EntityLiving handle = NMSImpl.getHandle((LivingEntity) horse);
EntityLiving equipperHandle = NMSImpl.getHandle(equipper);
if (handle == null || equipperHandle == null)

View File

@ -203,7 +203,7 @@ public class ShulkerController extends MobEntityController {
@Override
protected EntityAIBodyControl o() {
return new EntityAIBodyControl(this);
return npc == null ? super.o() : new EntityAIBodyControl(this);
}
@Override

View File

@ -1123,7 +1123,7 @@ public class NMSImpl implements NMSBridge {
}
@Override
public void openHorseScreen(Tameable horse, Player equipper) {
public void openHorseInventory(Tameable horse, Player equipper) {
EntityLiving handle = NMSImpl.getHandle(horse);
EntityHuman equipperHandle = (EntityHuman) NMSImpl.getHandle(equipper);
if (handle == null || equipperHandle == null)

View File

@ -203,7 +203,7 @@ public class ShulkerController extends MobEntityController {
@Override
protected EntityAIBodyControl r() {
return new EntityAIBodyControl(this);
return npc == null ? super.r() : new EntityAIBodyControl(this);
}
@Override

View File

@ -1147,7 +1147,7 @@ public class NMSImpl implements NMSBridge {
}
@Override
public void openHorseScreen(Tameable horse, Player equipper) {
public void openHorseInventory(Tameable horse, Player equipper) {
EntityLiving handle = NMSImpl.getHandle(horse);
EntityHuman equipperHandle = (EntityHuman) NMSImpl.getHandle(equipper);
if (handle == null || equipperHandle == null)

View File

@ -91,7 +91,7 @@ public class ShulkerController extends MobEntityController {
@Override
protected BodyRotationControl createBodyControl() {
return new BodyRotationControl(this);
return npc == null ? super.createBodyControl() : new BodyRotationControl(this);
}
@Override

View File

@ -1153,7 +1153,7 @@ public class NMSImpl implements NMSBridge {
}
@Override
public void openHorseScreen(Tameable horse, Player equipper) {
public void openHorseInventory(Tameable horse, Player equipper) {
LivingEntity handle = NMSImpl.getHandle(horse);
ServerPlayer equipperHandle = (ServerPlayer) NMSImpl.getHandle(equipper);
if (handle == null || equipperHandle == null)

View File

@ -92,7 +92,7 @@ public class ShulkerController extends MobEntityController {
@Override
protected BodyRotationControl createBodyControl() {
return new BodyRotationControl(this);
return npc == null ? super.createBodyControl() : new BodyRotationControl(this);
}
@Override

View File

@ -1162,7 +1162,7 @@ public class NMSImpl implements NMSBridge {
}
@Override
public void openHorseScreen(Tameable horse, Player equipper) {
public void openHorseInventory(Tameable horse, Player equipper) {
LivingEntity handle = NMSImpl.getHandle(horse);
ServerPlayer equipperHandle = (ServerPlayer) NMSImpl.getHandle(equipper);
if (handle == null || equipperHandle == null)

View File

@ -93,7 +93,7 @@ public class ShulkerController extends MobEntityController {
@Override
protected BodyRotationControl createBodyControl() {
return new BodyRotationControl(this);
return npc == null ? super.createBodyControl() : new BodyRotationControl(this);
}
@Override

View File

@ -1289,7 +1289,7 @@ public class NMSImpl implements NMSBridge {
}
@Override
public void openHorseScreen(Tameable horse, Player equipper) {
public void openHorseInventory(Tameable horse, Player equipper) {
LivingEntity handle = getHandle(horse);
ServerPlayer equipperHandle = (ServerPlayer) getHandle(equipper);
if (handle == null || equipperHandle == null)

View File

@ -92,7 +92,7 @@ public class ShulkerController extends MobEntityController {
@Override
protected BodyRotationControl createBodyControl() {
return new BodyRotationControl(this);
return npc == null ? super.createBodyControl() : new BodyRotationControl(this);
}
@Override

View File

@ -1260,7 +1260,7 @@ public class NMSImpl implements NMSBridge {
}
@Override
public void openHorseScreen(Tameable horse, Player equipper) {
public void openHorseInventory(Tameable horse, Player equipper) {
LivingEntity handle = getHandle(horse);
ServerPlayer equipperHandle = (ServerPlayer) getHandle(equipper);
if (handle == null || equipperHandle == null)

View File

@ -378,11 +378,11 @@ public class EntityHumanNPC extends EntityPlayer implements NPCHolder, Skinnable
updateEffects = true;
boolean itemChanged = false;
for (int slot = 0; slot < this.inventory.armor.length; slot++) {
for (int slot = 0; slot < 5; slot++) {
ItemStack equipment = getEquipment(slot);
ItemStack cache = equipmentCache.get(slot);
if (((cache != null) || (equipment != null))
&& (cache == null ^ equipment == null || !ItemStack.equals(cache, equipment))) {
&& (cache == null ^ equipment == null || !ItemStack.matches(cache, equipment))) {
itemChanged = true;
if (cache != null) {
this.getAttributeMap().a(cache.B());
@ -396,8 +396,8 @@ public class EntityHumanNPC extends EntityPlayer implements NPCHolder, Skinnable
if (!itemChanged)
return;
Location current = getBukkitEntity().getLocation(packetLocationCache);
Packet<?>[] packets = new Packet[this.inventory.armor.length];
for (int i = 0; i < this.inventory.armor.length; i++) {
Packet<?>[] packets = new Packet[5];
for (int i = 0; i < 5; i++) {
packets[i] = new PacketPlayOutEntityEquipment(getId(), i, getEquipment(i));
}
NMSImpl.sendPacketsNearby(getBukkitEntity(), current, packets);

View File

@ -885,7 +885,7 @@ public class NMSImpl implements NMSBridge {
}
@Override
public void openHorseScreen(Tameable horse, Player equipper) {
public void openHorseInventory(Tameable horse, Player equipper) {
EntityLiving handle = NMSImpl.getHandle((LivingEntity) horse);
EntityLiving equipperHandle = NMSImpl.getHandle(equipper);
if (handle == null || equipperHandle == null)