Implement new /template generate command, bugfixes

This commit is contained in:
fullwall 2024-05-30 12:00:17 +08:00
parent e8eadbfa36
commit 224b43d75b
11 changed files with 77 additions and 22 deletions

View File

@ -405,8 +405,10 @@ public class Citizens extends JavaPlugin implements CitizensPlugin {
traitFactory = new CitizensTraitFactory(this); traitFactory = new CitizensTraitFactory(this);
traitFactory.registerTrait(TraitInfo.create(ShopTrait.class).withSupplier(() -> new ShopTrait(shops))); traitFactory.registerTrait(TraitInfo.create(ShopTrait.class).withSupplier(() -> new ShopTrait(shops)));
selector = new NPCSelector(this); selector = new NPCSelector(this);
templateRegistry = new TemplateRegistry(new File(this.getDataFolder(), "templates").toPath()); templateRegistry = new TemplateRegistry(new File(getDataFolder(), "templates").toPath());
if (!new File(getDataFolder(), "skins").exists()) {
new File(getDataFolder(), "skins").mkdir();
}
Bukkit.getPluginManager().registerEvents(new EventListen(), this); Bukkit.getPluginManager().registerEvents(new EventListen(), this);
Bukkit.getPluginManager().registerEvents(new Placeholders(), this); Bukkit.getPluginManager().registerEvents(new Placeholders(), this);

View File

@ -456,6 +456,7 @@ public class EventListen implements Listener {
@EventHandler @EventHandler
public void onNPCKnockback(NPCKnockbackEvent event) { public void onNPCKnockback(NPCKnockbackEvent event) {
event.setCancelled(event.getNPC().isProtected());
if (event.getNPC().data().has(NPC.Metadata.KNOCKBACK)) { if (event.getNPC().data().has(NPC.Metadata.KNOCKBACK)) {
event.setCancelled(!event.getNPC().data().get(NPC.Metadata.KNOCKBACK, true)); event.setCancelled(!event.getNPC().data().get(NPC.Metadata.KNOCKBACK, true));
} }

View File

@ -110,7 +110,7 @@ public class Settings {
"npc.pathfinding.default-distance-margin", 1), "npc.pathfinding.default-distance-margin", 1),
DEFAULT_HOLOGRAM_RENDERER( DEFAULT_HOLOGRAM_RENDERER(
"The default renderer for holograms, must be one of the following:<br>interaction - matches inbuilt nametags most closely<br>display - allows for different colored backgrounds<br>armorstand - creates an armorstand and teleports it to the player", "The default renderer for holograms, must be one of the following:<br>interaction - matches inbuilt nametags most closely<br>display - allows for different colored backgrounds<br>armorstand - creates an armorstand and teleports it to the player",
"npc.hologram.default-renderer", "interaction"), "npc.hologram.default-renderer", "display"),
DEFAULT_LOOK_CLOSE("Enable look close by default", "npc.default.look-close.enabled", false), DEFAULT_LOOK_CLOSE("Enable look close by default", "npc.default.look-close.enabled", false),
DEFAULT_LOOK_CLOSE_RANGE("Default look close range in blocks", "npc.default.look-close.range", 10), DEFAULT_LOOK_CLOSE_RANGE("Default look close range in blocks", "npc.default.look-close.range", 10),
DEFAULT_NPC_HOLOGRAM_LINE_HEIGHT("Default distance between hologram lines", "npc.hologram.default-line-height", DEFAULT_NPC_HOLOGRAM_LINE_HEIGHT("Default distance between hologram lines", "npc.hologram.default-line-height",

View File

@ -23,6 +23,7 @@ import org.bukkit.DyeColor;
import org.bukkit.GameMode; import org.bukkit.GameMode;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.OfflinePlayer; import org.bukkit.OfflinePlayer;
import org.bukkit.Rotation; import org.bukkit.Rotation;
import org.bukkit.Sound; import org.bukkit.Sound;
@ -893,7 +894,9 @@ public class NPCCommands {
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
for (String part : parts) { for (String part : parts) {
if (part.contains(":")) { if (part.contains(":")) {
Template template = templateRegistry.getTemplateByNamespacedKey(part); int idx = part.indexOf(':');
Template template = templateRegistry
.getTemplateByKey(new NamespacedKey(part.substring(0, idx), part.substring(idx + 1)));
if (template == null) if (template == null)
continue; continue;
template.apply(npc); template.apply(npc);
@ -3347,7 +3350,8 @@ public class NPCCommands {
modifiers = { "tpto" }, modifiers = { "tpto" },
min = 2, min = 2,
max = 3, max = 3,
permission = "citizens.npc.tpto") permission = "citizens.npc.tpto",
parsePlaceholders = true)
@Requirements @Requirements
public void tpto(CommandContext args, CommandSender sender, NPC npc) throws CommandException { public void tpto(CommandContext args, CommandSender sender, NPC npc) throws CommandException {
Entity from = null, to = null; Entity from = null, to = null;

View File

@ -3,6 +3,7 @@ package net.citizensnpcs.commands;
import java.util.Collection; import java.util.Collection;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.bukkit.NamespacedKey;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import com.google.common.base.Joiner; import com.google.common.base.Joiner;
@ -36,25 +37,47 @@ public class TemplateCommands {
min = 2, min = 2,
permission = "citizens.templates.apply") permission = "citizens.templates.apply")
public void apply(CommandContext args, CommandSender sender, NPC npc, public void apply(CommandContext args, CommandSender sender, NPC npc,
@Arg(value = 1, completionsProvider = TemplateCompletions.class) String templateName) @Arg(value = 1, completionsProvider = TemplateCompletions.class) String templateKey)
throws CommandException { throws CommandException {
Template template = null; Template template = null;
if (templateName.contains(":")) { if (templateKey.contains(":")) {
template = registry.getTemplateByNamespacedKey(templateName); int idx = templateKey.indexOf(':');
template = registry
.getTemplateByKey(new NamespacedKey(templateKey.substring(0, idx), templateKey.substring(idx + 1)));
} else { } else {
Collection<Template> templates = registry.getTemplates(templateName); Collection<Template> templates = registry.getTemplates(templateKey);
if (templates.isEmpty()) if (templates.isEmpty())
throw new CommandException(Messages.TEMPLATE_MISSING); throw new CommandException(Messages.TEMPLATE_MISSING);
if (templates.size() > 1) if (templates.size() > 1)
throw new CommandException(Messages.TEMPLATE_PICKER, templateName, Joiner.on(", ").join(templates throw new CommandException(Messages.TEMPLATE_PICKER, templateKey,
.stream().map(t -> t.getNamespace() + ":" + t.getName()).collect(Collectors.toList()))); Joiner.on(", ").join(templates.stream().map(t -> t.getKey()).collect(Collectors.toList())));
template = templates.iterator().next(); template = templates.iterator().next();
} }
if (template == null) if (template == null)
throw new CommandException(Messages.TEMPLATE_MISSING); throw new CommandException(Messages.TEMPLATE_MISSING);
template.apply(npc); template.apply(npc);
Messaging.sendTr(sender, Messages.TEMPLATE_APPLIED, template.getName(), npc.getName()); Messaging.sendTr(sender, Messages.TEMPLATE_APPLIED, template.getKey().getKey(), npc.getName());
}
@Command(
aliases = { "template", "tpl" },
usage = "generate (template namespace:)[name]",
desc = "",
modifiers = { "generate" },
min = 2,
max = 2,
permission = "citizens.templates.generate")
public void generate(CommandContext args, CommandSender sender, NPC npc,
@Arg(value = 1, completionsProvider = TemplateCompletions.class) String templateName)
throws CommandException {
int idx = templateName.indexOf(':');
NamespacedKey key = idx == -1 ? new NamespacedKey("generated", templateName)
: new NamespacedKey(templateName.substring(0, idx), templateName.substring(idx + 1));
if (registry.getTemplateByKey(key) != null)
throw new CommandException(Messages.TEMPLATE_CONFLICT);
registry.generateTemplateFromNPC(key, npc);
Messaging.sendTr(sender, Messages.TEMPLATE_GENERATED, npc.getName());
} }
@Command( @Command(
@ -68,7 +91,7 @@ public class TemplateCommands {
public void list(CommandContext args, CommandSender sender, NPC npc) throws CommandException { public void list(CommandContext args, CommandSender sender, NPC npc) throws CommandException {
Messaging.sendTr(sender, Messages.TEMPLATE_LIST_HEADER); Messaging.sendTr(sender, Messages.TEMPLATE_LIST_HEADER);
for (Template template : registry.getAllTemplates()) { for (Template template : registry.getAllTemplates()) {
Messaging.send(sender, "[[-]] " + template.getNamespace() + ":" + template.getName()); Messaging.send(sender, "[[-]] " + template.getKey());
} }
} }
@ -81,7 +104,7 @@ public class TemplateCommands {
@Override @Override
public Collection<String> getCompletions(CommandContext args, CommandSender sender, NPC npc) { public Collection<String> getCompletions(CommandContext args, CommandSender sender, NPC npc) {
return templateRegistry.getAllTemplates().stream().map(t -> t.getNamespace() + ":" + t.getName()) return templateRegistry.getAllTemplates().stream().map(t -> t.getKey().toString())
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
} }

View File

@ -9,7 +9,8 @@ import net.citizensnpcs.api.util.DataKey;
import net.citizensnpcs.util.NMS; import net.citizensnpcs.util.NMS;
/** /**
* Persists the current {@link Location} of the {@link NPC}. Will cache last known location if despawned. * Persists the current {@link Location} of the {@link net.citizensnpcs.api.npc.NPC}. Will cache last known location if
* despawned.
*/ */
@TraitName("location") @TraitName("location")
public class CurrentLocation extends Trait { public class CurrentLocation extends Trait {

View File

@ -28,6 +28,8 @@ import net.citizensnpcs.api.util.SpigotUtil;
import net.citizensnpcs.util.InventoryMultiplexer; import net.citizensnpcs.util.InventoryMultiplexer;
import net.citizensnpcs.util.NMS; import net.citizensnpcs.util.NMS;
import net.citizensnpcs.util.Util; import net.citizensnpcs.util.Util;
import net.kyori.adventure.platform.bukkit.BukkitComponentSerializer;
import net.kyori.adventure.text.Component;
public class ItemAction extends NPCShopAction { public class ItemAction extends NPCShopAction {
@Persist @Persist
@ -53,11 +55,11 @@ public class ItemAction extends NPCShopAction {
@Override @Override
public String describe() { public String describe() {
if (items.size() == 1) if (items.size() == 1)
return items.get(0).getAmount() + " " + Util.prettyEnum(items.get(0).getType()); return stringify(items.get(0));
String description = items.size() + " items"; String description = items.size() + " items";
for (int i = 0; i < items.size(); i++) { for (int i = 0; i < items.size(); i++) {
ItemStack item = items.get(i); ItemStack item = items.get(i);
description += "\n" + item.getAmount() + " " + Util.prettyEnum(item.getType()); description += "\n" + stringify(item);
if (i == 3) { if (i == 3) {
description += "..."; description += "...";
break; break;
@ -155,6 +157,7 @@ public class ItemAction extends NPCShopAction {
return true; return true;
} }
@SuppressWarnings("unchecked")
private boolean metaMatches(ItemStack needle, ItemStack haystack, List<String> meta) { private boolean metaMatches(ItemStack needle, ItemStack haystack, List<String> meta) {
Map<String, Object> source = NMS.getComponentMap(needle); Map<String, Object> source = NMS.getComponentMap(needle);
Map<String, Object> compare = NMS.getComponentMap(haystack); Map<String, Object> compare = NMS.getComponentMap(haystack);
@ -178,6 +181,14 @@ public class ItemAction extends NPCShopAction {
return true; return true;
} }
private String stringify(ItemStack item) {
if (SUPPORT_TRANSLATABLE) {
return BukkitComponentSerializer.legacy().serialize(Component.text(item.getAmount() + " ")
.append(Component.translatable().key(item.getTranslationKey())));
}
return item.getAmount() + " " + Util.prettyEnum(item.getType());
}
@Override @Override
public Transaction take(Entity entity, InventoryMultiplexer im, int repeats) { public Transaction take(Entity entity, InventoryMultiplexer im, int repeats) {
if (!(entity instanceof InventoryHolder)) if (!(entity instanceof InventoryHolder))
@ -314,4 +325,14 @@ public class ItemAction extends NPCShopAction {
return action instanceof ItemAction; return action instanceof ItemAction;
} }
} }
private static boolean SUPPORT_TRANSLATABLE = true;
static {
try {
Class.forName("org.bukkit.Translatable");
} catch (ClassNotFoundException e) {
SUPPORT_TRANSLATABLE = false;
}
}
} }

View File

@ -414,6 +414,7 @@ public class Messages {
public static final String TEMPLATE_APPLIED = "citizens.commands.template.applied"; public static final String TEMPLATE_APPLIED = "citizens.commands.template.applied";
public static final String TEMPLATE_CONFLICT = "citizens.commands.template.conflict"; public static final String TEMPLATE_CONFLICT = "citizens.commands.template.conflict";
public static final String TEMPLATE_DELETED = "citizens.commands.template.delete.deleted"; public static final String TEMPLATE_DELETED = "citizens.commands.template.delete.deleted";
public static final String TEMPLATE_GENERATED = "citizens.commands.template.generate.generated";
public static final String TEMPLATE_LIST_HEADER = "citizens.commands.template.list.header"; public static final String TEMPLATE_LIST_HEADER = "citizens.commands.template.list.header";
public static final String TEMPLATE_MISSING = "citizens.commands.template.missing"; public static final String TEMPLATE_MISSING = "citizens.commands.template.missing";
public static final String TEMPLATE_NAMESPACE_EXISTS = "citizens.commands.template.namespace-already-exists"; public static final String TEMPLATE_NAMESPACE_EXISTS = "citizens.commands.template.namespace-already-exists";

View File

@ -30,7 +30,7 @@ public class MojangSkinGenerator {
con.setRequestProperty("User-Agent", "Citizens/2.0"); con.setRequestProperty("User-Agent", "Citizens/2.0");
con.setRequestProperty("Cache-Control", "no-cache"); con.setRequestProperty("Cache-Control", "no-cache");
con.setRequestProperty("Content-Type", "multipart/form-data;boundary=*****"); con.setRequestProperty("Content-Type", "multipart/form-data;boundary=*****");
con.setConnectTimeout(1000); con.setConnectTimeout(2000);
con.setReadTimeout(30000); con.setReadTimeout(30000);
out = new DataOutputStream(con.getOutputStream()); out = new DataOutputStream(con.getOutputStream());
out.writeBytes("--*****\r\n"); out.writeBytes("--*****\r\n");
@ -91,7 +91,7 @@ public class MojangSkinGenerator {
con.setRequestProperty("Cache-Control", "no-cache"); con.setRequestProperty("Cache-Control", "no-cache");
con.setRequestProperty("Accept", "application/json"); con.setRequestProperty("Accept", "application/json");
con.setRequestProperty("Content-Type", "application/json"); con.setRequestProperty("Content-Type", "application/json");
con.setConnectTimeout(1000); con.setConnectTimeout(2000);
con.setReadTimeout(30000); con.setReadTimeout(30000);
out = new DataOutputStream(con.getOutputStream()); out = new DataOutputStream(con.getOutputStream());
JSONObject req = new JSONObject(); JSONObject req = new JSONObject();

View File

@ -618,6 +618,8 @@
"citizens.commands.requirements.must-have-selected" : "You must have an NPC selected to execute that command.", "citizens.commands.requirements.must-have-selected" : "You must have an NPC selected to execute that command.",
"citizens.commands.requirements.too-few-arguments" : "Too few arguments.", "citizens.commands.requirements.too-few-arguments" : "Too few arguments.",
"citizens.commands.requirements.too-many-arguments" : "Too many arguments. ", "citizens.commands.requirements.too-many-arguments" : "Too many arguments. ",
"citizens.commands.template.generate.generated" : "Generated template from [[{0}]]",
"citizens.commands.template.generate.description" : "Generate a template from the selected NPC",
"citizens.commands.template.applied" : "Applied [[{0}]] to [[{1}]].", "citizens.commands.template.applied" : "Applied [[{0}]] to [[{1}]].",
"citizens.commands.template.apply.description" : "Applies a template to the selected NPC", "citizens.commands.template.apply.description" : "Applies a template to the selected NPC",
"citizens.commands.template.apply.help" : "", "citizens.commands.template.apply.help" : "",

View File

@ -700,7 +700,8 @@ public class NMSImpl implements NMSBridge {
@Override @Override
public float getRidingHeightOffset(org.bukkit.entity.Entity entity, org.bukkit.entity.Entity mount) { public float getRidingHeightOffset(org.bukkit.entity.Entity entity, org.bukkit.entity.Entity mount) {
return (float) getHandle(entity).getPassengerRidingPosition(getHandle(mount)).y; Entity handle = getHandle(entity);
return (float) (handle.getPassengerRidingPosition(getHandle(mount)).y - handle.position().y);
} }
@Override @Override
@ -911,8 +912,7 @@ public class NMSImpl implements NMSBridge {
@Override @Override
public void linkTextInteraction(org.bukkit.entity.Player player, org.bukkit.entity.Entity entity, public void linkTextInteraction(org.bukkit.entity.Player player, org.bukkit.entity.Entity entity,
org.bukkit.entity.Entity mount, double offset) { org.bukkit.entity.Entity mount, double offset) {
Interaction handle = (Interaction) getHandle(entity); offset += getRidingHeightOffset(entity, mount);
offset += handle.getPassengerRidingPosition(getHandle(mount)).y;
sendPacket(player, sendPacket(player,
new ClientboundBundlePacket(List.of( new ClientboundBundlePacket(List.of(
new ClientboundSetEntityDataPacket(entity.getEntityId(), new ClientboundSetEntityDataPacket(entity.getEntityId(),