diff --git a/api/src/main/java/com/lunarclient/apollo/module/cosmetic/CosmeticModule.java b/api/src/main/java/com/lunarclient/apollo/module/cosmetic/CosmeticModule.java index c7a77f33..45783a13 100644 --- a/api/src/main/java/com/lunarclient/apollo/module/cosmetic/CosmeticModule.java +++ b/api/src/main/java/com/lunarclient/apollo/module/cosmetic/CosmeticModule.java @@ -58,6 +58,19 @@ public abstract class CosmeticModule extends ApolloModule { */ public abstract void equipNpcCosmetics(Recipients recipients, UUID npcUuid, List cosmetics); + /** + * Equips the provided cosmetics on an NPC for the given {@link Recipients}, optionally layered on + * top of each recipient's own equipped cosmetics. + * + * @param recipients the recipients that are receiving the packet + * @param npcUuid the {@link UUID} of the NPC to equip the cosmetics on + * @param cosmetics the cosmetics to equip, including optional {@link CosmeticOptions} per entry + * ({@link HatOptions}, {@link CloakOptions}, {@link PetOptions}, or {@link BodyOptions}) + * @param copyLocalCosmetics {@code true} to copy each recipient's own equipped cosmetics onto the NPC first + * @since 1.2.7 + */ + public abstract void equipNpcCosmetics(Recipients recipients, UUID npcUuid, List cosmetics, boolean copyLocalCosmetics); + /** * Unequips the provided cosmetics from an NPC for the given {@link Recipients}. * @@ -77,6 +90,35 @@ public abstract class CosmeticModule extends ApolloModule { */ public abstract void resetNpcCosmetics(Recipients recipients, UUID npcUuid); + /** + * Starts an emote on an NPC for the given {@link Recipients}. + * + *

Starting an emote replaces any emote already playing on the NPC.

+ * + * @param recipients the recipients that are receiving the packet + * @param npcUuid the {@link UUID} of the NPC to play the emote on + * @param emote the emote to play + * @since 1.2.7 + */ + public abstract void startNpcEmote(Recipients recipients, UUID npcUuid, Emote emote); + + /** + * Stops the emote currently playing on an NPC for the given {@link Recipients}. + * + * @param recipients the recipients that are receiving the packet + * @param npcUuid the {@link UUID} of the NPC to stop the emote on + * @since 1.2.7 + */ + public abstract void stopNpcEmote(Recipients recipients, UUID npcUuid); + + /** + * Stops all emotes playing on every NPC for the given {@link Recipients}. + * + * @param recipients the recipients that are receiving the packet + * @since 1.2.7 + */ + public abstract void resetNpcEmotes(Recipients recipients); + /** * Displays a spray for the given {@link Recipients}. * diff --git a/api/src/main/java/com/lunarclient/apollo/module/cosmetic/Emote.java b/api/src/main/java/com/lunarclient/apollo/module/cosmetic/Emote.java new file mode 100644 index 00000000..c44405c3 --- /dev/null +++ b/api/src/main/java/com/lunarclient/apollo/module/cosmetic/Emote.java @@ -0,0 +1,61 @@ +/* + * This file is part of Apollo, licensed under the MIT License. + * + * Copyright (c) 2026 Moonsworth + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.lunarclient.apollo.module.cosmetic; + +import lombok.Builder; +import lombok.Getter; +import org.jetbrains.annotations.Range; + +/** + * Represents an emote that can be played on an NPC. + * + * @since 1.2.7 + */ +@Getter +@Builder +public final class Emote { + + /** + * Returns the Lunar Client emote id. + * + *

The value must be greater than 0.

+ * + * @return the emote id + * @since 1.2.7 + */ + @Range(from = 1, to = Integer.MAX_VALUE) int id; + + /** + * Returns the metadata used to select a variant for emotes that support one. + * + *

The value is ignored by emotes that do not use it. For example, a coin flip emote may use + * {@code 1} for heads and {@code 2} for tails, while a rock paper scissors emote may use {@code 1} + * for rock, {@code 2} for paper, and {@code 3} for scissors.

+ * + * @return the emote metadata + * @since 1.2.7 + */ + int metadata; + +} diff --git a/common/src/main/java/com/lunarclient/apollo/module/cosmetic/CosmeticModuleImpl.java b/common/src/main/java/com/lunarclient/apollo/module/cosmetic/CosmeticModuleImpl.java index 2189d3a7..23af53d3 100644 --- a/common/src/main/java/com/lunarclient/apollo/module/cosmetic/CosmeticModuleImpl.java +++ b/common/src/main/java/com/lunarclient/apollo/module/cosmetic/CosmeticModuleImpl.java @@ -28,7 +28,10 @@ import com.lunarclient.apollo.cosmetic.v1.EquipNpcCosmeticsMessage; import com.lunarclient.apollo.cosmetic.v1.RemoveSprayMessage; import com.lunarclient.apollo.cosmetic.v1.ResetNpcCosmeticsMessage; +import com.lunarclient.apollo.cosmetic.v1.ResetNpcEmotesMessage; import com.lunarclient.apollo.cosmetic.v1.ResetSpraysMessage; +import com.lunarclient.apollo.cosmetic.v1.StartNpcEmoteMessage; +import com.lunarclient.apollo.cosmetic.v1.StopNpcEmoteMessage; import com.lunarclient.apollo.cosmetic.v1.UnequipNpcCosmeticsMessage; import com.lunarclient.apollo.module.cosmetic.options.BodyOptions; import com.lunarclient.apollo.module.cosmetic.options.CloakOptions; @@ -55,6 +58,11 @@ public final class CosmeticModuleImpl extends CosmeticModule { @Override public void equipNpcCosmetics(@NonNull Recipients recipients, @NonNull UUID npcUuid, @NonNull List cosmetics) { + this.equipNpcCosmetics(recipients, npcUuid, cosmetics, false); + } + + @Override + public void equipNpcCosmetics(@NonNull Recipients recipients, @NonNull UUID npcUuid, @NonNull List cosmetics, boolean copyLocalCosmetics) { List cosmeticsProto = cosmetics.stream() .map(this::toProtobuf) .collect(Collectors.toList()); @@ -62,6 +70,7 @@ public void equipNpcCosmetics(@NonNull Recipients recipients, @NonNull UUID npcU EquipNpcCosmeticsMessage message = EquipNpcCosmeticsMessage.newBuilder() .setNpcUuid(NetworkTypes.toProtobuf(npcUuid)) .addAllCosmetics(cosmeticsProto) + .setCopyLocalCosmetics(copyLocalCosmetics) .build(); recipients.forEach(player -> ((AbstractApolloPlayer) player).sendPacket(message)); @@ -90,6 +99,34 @@ public void resetNpcCosmetics(@NonNull Recipients recipients, @NonNull UUID npcU recipients.forEach(player -> ((AbstractApolloPlayer) player).sendPacket(message)); } + @Override + public void startNpcEmote(@NonNull Recipients recipients, @NonNull UUID npcUuid, @NonNull Emote emote) { + StartNpcEmoteMessage message = StartNpcEmoteMessage.newBuilder() + .setNpcUuid(NetworkTypes.toProtobuf(npcUuid)) + .setEmote(com.lunarclient.apollo.cosmetic.v1.Emote.newBuilder() + .setId(checkStrictlyPositive(emote.getId(), "Emote#id")) + .setMetadata(emote.getMetadata()) + .build()) + .build(); + + recipients.forEach(player -> ((AbstractApolloPlayer) player).sendPacket(message)); + } + + @Override + public void stopNpcEmote(@NonNull Recipients recipients, @NonNull UUID npcUuid) { + StopNpcEmoteMessage message = StopNpcEmoteMessage.newBuilder() + .setNpcUuid(NetworkTypes.toProtobuf(npcUuid)) + .build(); + + recipients.forEach(player -> ((AbstractApolloPlayer) player).sendPacket(message)); + } + + @Override + public void resetNpcEmotes(@NonNull Recipients recipients) { + ResetNpcEmotesMessage message = ResetNpcEmotesMessage.getDefaultInstance(); + recipients.forEach(player -> ((AbstractApolloPlayer) player).sendPacket(message)); + } + @Override public void displaySpray(@NonNull Recipients recipients, @NonNull Spray spray) { DisplaySprayMessage message = DisplaySprayMessage.newBuilder() diff --git a/docs/developers/lightweight/protobuf.mdx b/docs/developers/lightweight/protobuf.mdx index 0d43206e..d18643bf 100644 --- a/docs/developers/lightweight/protobuf.mdx +++ b/docs/developers/lightweight/protobuf.mdx @@ -26,7 +26,7 @@ Available fields for each message, including their types, are available on the B com.lunarclient apollo-protos - 0.1.7 + 0.1.8 ``` @@ -41,7 +41,7 @@ Available fields for each message, including their types, are available on the B } dependencies { - api 'com.lunarclient:apollo-protos:0.1.7' + api 'com.lunarclient:apollo-protos:0.1.8' } ``` @@ -55,7 +55,7 @@ Available fields for each message, including their types, are available on the B } dependencies { - api("com.lunarclient:apollo-protos:0.1.7") + api("com.lunarclient:apollo-protos:0.1.8") } ``` diff --git a/docs/developers/private-modules/cosmetic.mdx b/docs/developers/private-modules/cosmetic.mdx index 6b18d76f..6f04ce8a 100644 --- a/docs/developers/private-modules/cosmetic.mdx +++ b/docs/developers/private-modules/cosmetic.mdx @@ -9,6 +9,9 @@ The cosmetic module allows you to interact and utilize all the various cosmetic - Add wearable cosmetics to NPC entities - Grants the ability to put any cosmetic on an NPC - Uniquely customize various cosmetic options + - Copy the viewer's own equipped cosmetics onto an NPC +- Play emotes on NPC entities + - Start, stop, and reset emotes on an NPC - Add sprays to any location - Grants the ability to put any spray on a block - Customize the rotation, direction options, and duration @@ -69,6 +72,14 @@ public void equipNpcCosmeticsExample(Player viewer, UUID npcUuid) { } ``` +**Equip cosmetics on an NPC, copying the recipient's own cosmetics** + +```java +public void equipNpcCosmeticsCopyLocalExample(UUID npcUuid) { + this.cosmeticModule.equipNpcCosmetics(Recipients.ofEveryone(), npcUuid, new ArrayList<>(), true); +} +``` + **Unequip cosmetics from an NPC** ```java @@ -89,6 +100,34 @@ public void resetNpcCosmeticsExample(UUID npcUuid) { } ``` +**Start an emote on an NPC** + +```java +public void startNpcEmoteExample(UUID npcUuid) { + Emote emote = Emote.builder() + .id(56) + .build(); + + this.cosmeticModule.startNpcEmote(Recipients.ofEveryone(), npcUuid, emote); +} +``` + +**Stop an emote on an NPC** + +```java +public void stopNpcEmoteExample(UUID npcUuid) { + this.cosmeticModule.stopNpcEmote(Recipients.ofEveryone(), npcUuid); +} +``` + +**Reset NPC emotes** + +```java +public void resetNpcEmotesExample() { + this.cosmeticModule.resetNpcEmotes(Recipients.ofEveryone()); +} +``` + **Display spray** ```java @@ -188,6 +227,20 @@ public void resetSpraysExample() { .build()) ``` +### `Emote` Options + +`.id(int)` is the Lunar Client emote id. + +```java +.id(56) +``` + +`.metadata(int)` selects a variant for emotes that support one, and is ignored otherwise (default `0`). For example, a coin flip emote may use `1` for heads and `2` for tails, while a rock paper scissors emote may use `1` for rock, `2` for paper, and `3` for scissors. + +```java +.metadata(1) +``` + ### `Spray` Options `.sprayId(int)` is the Lunar Client spray cosmetic id. @@ -270,6 +323,19 @@ public void equipNpcCosmeticsExample(Player viewer, UUID npcUuid) { } ``` +**Equip cosmetics on an NPC, copying the recipient's own cosmetics** + +```java +public void equipNpcCosmeticsCopyLocalExample(UUID npcUuid) { + EquipNpcCosmeticsMessage message = EquipNpcCosmeticsMessage.newBuilder() + .setNpcUuid(ProtobufUtil.createUuidProto(npcUuid)) + .setCopyLocalCosmetics(true) + .build(); + + ProtobufPacketUtil.broadcastPacket(message); +} +``` + **Unequip cosmetics from an NPC** ```java @@ -297,6 +363,42 @@ public void resetNpcCosmeticsExample(Player viewer, UUID npcUuid) { } ``` +**Start an emote on an NPC** + +```java +public void startNpcEmoteExample(UUID npcUuid) { + StartNpcEmoteMessage message = StartNpcEmoteMessage.newBuilder() + .setNpcUuid(ProtobufUtil.createUuidProto(npcUuid)) + .setEmote(Emote.newBuilder() + .setId(56) + .build()) + .build(); + + ProtobufPacketUtil.broadcastPacket(message); +} +``` + +**Stop an emote on an NPC** + +```java +public void stopNpcEmoteExample(UUID npcUuid) { + StopNpcEmoteMessage message = StopNpcEmoteMessage.newBuilder() + .setNpcUuid(ProtobufUtil.createUuidProto(npcUuid)) + .build(); + + ProtobufPacketUtil.broadcastPacket(message); +} +``` + +**Reset NPC emotes** + +```java +public void resetNpcEmotesExample() { + ResetNpcEmotesMessage message = ResetNpcEmotesMessage.getDefaultInstance(); + ProtobufPacketUtil.broadcastPacket(message); +} +``` + **Display spray** ```java @@ -395,6 +497,19 @@ public void equipNpcCosmeticsExample(Player viewer, UUID npcUuid) { } ``` +**Equip cosmetics on an NPC, copying the recipient's own cosmetics** + +```java +public void equipNpcCosmeticsCopyLocalExample(UUID npcUuid) { + JsonObject message = new JsonObject(); + message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.cosmetic.v1.EquipNpcCosmeticsMessage"); + message.add("npc_uuid", JsonUtil.createUuidObject(npcUuid)); + message.addProperty("copy_local_cosmetics", true); + + JsonPacketUtil.broadcastPacket(message); +} +``` + **Unequip cosmetics from an NPC** ```java @@ -425,6 +540,45 @@ public void resetNpcCosmeticsExample(Player viewer, UUID npcUuid) { } ``` +**Start an emote on an NPC** + +```java +public void startNpcEmoteExample(UUID npcUuid) { + JsonObject emote = new JsonObject(); + emote.addProperty("id", 56); + + JsonObject message = new JsonObject(); + message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.cosmetic.v1.StartNpcEmoteMessage"); + message.add("npc_uuid", JsonUtil.createUuidObject(npcUuid)); + message.add("emote", emote); + + JsonPacketUtil.broadcastPacket(message); +} +``` + +**Stop an emote on an NPC** + +```java +public void stopNpcEmoteExample(UUID npcUuid) { + JsonObject message = new JsonObject(); + message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.cosmetic.v1.StopNpcEmoteMessage"); + message.add("npc_uuid", JsonUtil.createUuidObject(npcUuid)); + + JsonPacketUtil.broadcastPacket(message); +} +``` + +**Reset NPC emotes** + +```java +public void resetNpcEmotesExample() { + JsonObject message = new JsonObject(); + message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.cosmetic.v1.ResetNpcEmotesMessage"); + + JsonPacketUtil.broadcastPacket(message); +} +``` + **Display spray** ```java diff --git a/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/module/CosmeticApiExample.java b/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/module/CosmeticApiExample.java index 3e500a0c..0c82c2ca 100644 --- a/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/module/CosmeticApiExample.java +++ b/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/module/CosmeticApiExample.java @@ -30,6 +30,7 @@ import com.lunarclient.apollo.example.nms.CommandCosmetic; import com.lunarclient.apollo.module.cosmetic.Cosmetic; import com.lunarclient.apollo.module.cosmetic.CosmeticModule; +import com.lunarclient.apollo.module.cosmetic.Emote; import com.lunarclient.apollo.module.cosmetic.Spray; import com.lunarclient.apollo.module.cosmetic.options.BodyOptions; import com.lunarclient.apollo.module.cosmetic.options.CloakOptions; @@ -40,6 +41,7 @@ import com.lunarclient.apollo.player.ApolloPlayer; import com.lunarclient.apollo.recipients.Recipients; import java.time.Duration; +import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -82,6 +84,11 @@ public void equipNpcCosmeticsExample(Player viewer, UUID npcUuid) { apolloPlayerOpt.ifPresent(apolloPlayer -> this.cosmeticModule.equipNpcCosmetics(apolloPlayer, npcUuid, cosmetics)); } + @Override + public void equipNpcCosmeticsCopyLocalExample(Player viewer, UUID npcUuid) { + this.cosmeticModule.equipNpcCosmetics(Recipients.ofEveryone(), npcUuid, new ArrayList<>(), true); + } + @Override public void equipNpcCosmeticsInternal(Player viewer, UUID npcUuid, List cosmeticIds) { List cosmetics = cosmeticIds.stream() @@ -158,6 +165,35 @@ public void resetNpcCosmeticsExample(Player viewer, UUID npcUuid) { this.cosmeticModule.resetNpcCosmetics(Recipients.ofEveryone(), npcUuid); } + @Override + public void startNpcEmoteExample(Player viewer, UUID npcUuid) { + Emote emote = Emote.builder() + .id(56) + .build(); + + this.cosmeticModule.startNpcEmote(Recipients.ofEveryone(), npcUuid, emote); + } + + @Override + public void startNpcEmoteInternal(Player viewer, UUID npcUuid, int emoteId, int metadata) { + Emote emote = Emote.builder() + .id(emoteId) + .metadata(metadata) + .build(); + + this.cosmeticModule.startNpcEmote(Recipients.ofEveryone(), npcUuid, emote); + } + + @Override + public void stopNpcEmoteExample(Player viewer, UUID npcUuid) { + this.cosmeticModule.stopNpcEmote(Recipients.ofEveryone(), npcUuid); + } + + @Override + public void resetNpcEmotesExample() { + this.cosmeticModule.resetNpcEmotes(Recipients.ofEveryone()); + } + @Override public void displaySprayExample(Player viewer, int sprayId) { Block block = viewer.getTargetBlockExact(10); diff --git a/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/command/CosmeticCommand.java b/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/command/CosmeticCommand.java index d970d1d8..ad8057e8 100644 --- a/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/command/CosmeticCommand.java +++ b/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/command/CosmeticCommand.java @@ -65,27 +65,21 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command return this.handleSpray(player, example, args); } + if ("emote".equalsIgnoreCase(args[0])) { + return this.handleEmote(player, example, args); + } + if (args.length < 2) { this.sendUsage(player); return true; } String npcName = args[1]; - boolean uuidParam = npcName.contains("-"); - - Optional npcOpt = ApolloExamplePlugin.getInstance().getNpcManager().findByName(npcName); - if (!npcOpt.isPresent() && !uuidParam) { - player.sendMessage(ChatColor.RED + "No NPC found with name: " + npcName); + UUID uuid = this.resolveNpcUuid(player, npcName); + if (uuid == null) { return true; } - UUID uuid; - if (uuidParam) { - uuid = UUID.fromString(npcName); - } else { - uuid = npcOpt.get().getUuid(); - } - switch (args[0].toLowerCase()) { case "equip": { if (args.length >= 3 && this.isCosmeticType(args[2])) { @@ -102,6 +96,12 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command break; } + case "equiplocal": { + example.equipNpcCosmeticsCopyLocalExample(player, uuid); + player.sendMessage(ChatColor.GREEN + "Equip local cosmetics on NPC " + npcName); + break; + } + case "unequip": { List cosmeticIds = this.parseCosmeticIds(args); example.unequipNpcCosmeticsInternal(player, uuid, cosmeticIds); @@ -186,6 +186,88 @@ private boolean handleSpray(Player player, CosmeticExample example, String[] arg return true; } + private boolean handleEmote(Player player, CosmeticExample example, String[] args) { + if (args.length < 2) { + this.sendEmoteUsage(player); + return true; + } + + switch (args[1].toLowerCase()) { + case "start": { + if (args.length < 4) { + this.sendEmoteUsage(player); + return true; + } + + UUID uuid = this.resolveNpcUuid(player, args[2]); + if (uuid == null) { + return true; + } + + int emoteId; + try { + emoteId = Integer.parseInt(args[3]); + } catch (NumberFormatException e) { + player.sendMessage(ChatColor.RED + "Emote id must be an integer."); + return true; + } + + int metadata = 0; + if (args.length >= 5) { + try { + metadata = Integer.parseInt(args[4]); + } catch (NumberFormatException e) { + player.sendMessage(ChatColor.RED + "Emote metadata must be an integer."); + return true; + } + } + + example.startNpcEmoteInternal(player, uuid, emoteId, metadata); + player.sendMessage(ChatColor.GREEN + "Started emote " + emoteId + " on NPC " + args[2]); + break; + } + + case "stop": { + if (args.length < 3) { + this.sendEmoteUsage(player); + return true; + } + + UUID uuid = this.resolveNpcUuid(player, args[2]); + if (uuid == null) { + return true; + } + + example.stopNpcEmoteExample(player, uuid); + player.sendMessage(ChatColor.GREEN + "Stopped emote on NPC " + args[2]); + break; + } + + case "reset": { + example.resetNpcEmotesExample(); + player.sendMessage(ChatColor.GREEN + "Reset all NPC emotes"); + break; + } + + default: { + this.sendEmoteUsage(player); + break; + } + } + + return true; + } + + private UUID resolveNpcUuid(Player player, String npcName) { + Optional npcOpt = ApolloExamplePlugin.getInstance().getNpcManager().findByName(npcName); + if (!npcOpt.isPresent()) { + player.sendMessage(ChatColor.RED + "No NPC found with name: " + npcName); + return null; + } + + return npcOpt.get().getUuid(); + } + private boolean isCosmeticType(String type) { String lower = type.toLowerCase(); return "hat".equals(lower) || "cloak".equals(lower) || "pet".equals(lower) || "body".equals(lower); @@ -318,6 +400,14 @@ private List parseCosmeticIds(String[] args) { private void sendUsage(Player player) { player.sendMessage("Usage:"); + this.sendCosmeticUsage(player); + player.sendMessage(""); + this.sendEmoteUsage(player); + player.sendMessage(""); + this.sendSprayUsage(player); + } + + private void sendCosmeticUsage(Player player) { player.sendMessage(" - /cosmetic equip [cosmeticIds]"); player.sendMessage(ChatColor.ITALIC + " /cosmetic equip Apollo 434 3654 3977"); player.sendMessage(""); @@ -333,13 +423,21 @@ private void sendUsage(Player player) { player.sendMessage(" - /cosmetic equip body "); player.sendMessage(ChatColor.ITALIC + " /cosmetic equip Apollo body 3977 showOverChestplate=true showOverLeggings=true showOverBoots=true"); player.sendMessage(""); + player.sendMessage(" - /cosmetic equiplocal "); + player.sendMessage(ChatColor.ITALIC + " /cosmetic equiplocal Apollo"); + player.sendMessage(""); player.sendMessage(" - /cosmetic unequip [cosmeticIds]"); player.sendMessage(ChatColor.ITALIC + " /cosmetic unequip Apollo 434 3654"); player.sendMessage(""); player.sendMessage(" - /cosmetic reset "); player.sendMessage(ChatColor.ITALIC + " /cosmetic reset Apollo"); - player.sendMessage(""); - this.sendSprayUsage(player); + } + + private void sendEmoteUsage(Player player) { + player.sendMessage(" - /cosmetic emote start [metadata]"); + player.sendMessage(ChatColor.ITALIC + " /cosmetic emote start Apollo 342 1 (Coin Flip, metadata 1 = heads)"); + player.sendMessage(" - /cosmetic emote stop "); + player.sendMessage(" - /cosmetic emote reset"); } private void sendSprayUsage(Player player) { diff --git a/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/module/impl/CosmeticExample.java b/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/module/impl/CosmeticExample.java index 2a060b20..0e56b672 100644 --- a/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/module/impl/CosmeticExample.java +++ b/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/module/impl/CosmeticExample.java @@ -34,6 +34,8 @@ public abstract class CosmeticExample extends ApolloModuleExample { public abstract void equipNpcCosmeticsExample(Player viewer, UUID npcUuid); + public abstract void equipNpcCosmeticsCopyLocalExample(Player viewer, UUID npcUuid); + public abstract void equipNpcCosmeticsInternal(Player viewer, UUID npcUuid, List cosmeticIds); public void equipNpcCosmeticInternal(Player viewer, UUID npcUuid, CommandCosmetic cosmetic) { @@ -50,6 +52,14 @@ public void equipNpcCosmeticToViewer(Player viewer, UUID npcUuid, CommandCosmeti public abstract void resetNpcCosmeticsExample(Player viewer, UUID npcUuid); + public abstract void startNpcEmoteExample(Player viewer, UUID npcUuid); + + public abstract void startNpcEmoteInternal(Player viewer, UUID npcUuid, int emoteId, int metadata); + + public abstract void stopNpcEmoteExample(Player viewer, UUID npcUuid); + + public abstract void resetNpcEmotesExample(); + public abstract void displaySprayExample(Player viewer, int sprayId); public abstract void removeSprayExample(int sprayId); diff --git a/example/bukkit/json/src/main/java/com/lunarclient/apollo/example/json/module/CosmeticJsonExample.java b/example/bukkit/json/src/main/java/com/lunarclient/apollo/example/json/module/CosmeticJsonExample.java index 6fe78acc..cff28169 100644 --- a/example/bukkit/json/src/main/java/com/lunarclient/apollo/example/json/module/CosmeticJsonExample.java +++ b/example/bukkit/json/src/main/java/com/lunarclient/apollo/example/json/module/CosmeticJsonExample.java @@ -76,6 +76,16 @@ public void equipNpcCosmeticsExample(Player viewer, UUID npcUuid) { JsonPacketUtil.sendPacket(viewer, message); } + @Override + public void equipNpcCosmeticsCopyLocalExample(Player viewer, UUID npcUuid) { + JsonObject message = new JsonObject(); + message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.cosmetic.v1.EquipNpcCosmeticsMessage"); + message.add("npc_uuid", JsonUtil.createUuidObject(npcUuid)); + message.addProperty("copy_local_cosmetics", true); + + JsonPacketUtil.broadcastPacket(message); + } + @Override public void equipNpcCosmeticsInternal(Player viewer, UUID npcUuid, List cosmeticIds) { JsonArray cosmeticsArray = new JsonArray(); @@ -130,6 +140,50 @@ public void resetNpcCosmeticsExample(Player viewer, UUID npcUuid) { JsonPacketUtil.broadcastPacket(message); } + @Override + public void startNpcEmoteExample(Player viewer, UUID npcUuid) { + JsonObject emote = new JsonObject(); + emote.addProperty("id", 56); + + JsonObject message = new JsonObject(); + message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.cosmetic.v1.StartNpcEmoteMessage"); + message.add("npc_uuid", JsonUtil.createUuidObject(npcUuid)); + message.add("emote", emote); + + JsonPacketUtil.broadcastPacket(message); + } + + @Override + public void startNpcEmoteInternal(Player viewer, UUID npcUuid, int emoteId, int metadata) { + JsonObject emote = new JsonObject(); + emote.addProperty("id", emoteId); + emote.addProperty("metadata", metadata); + + JsonObject message = new JsonObject(); + message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.cosmetic.v1.StartNpcEmoteMessage"); + message.add("npc_uuid", JsonUtil.createUuidObject(npcUuid)); + message.add("emote", emote); + + JsonPacketUtil.broadcastPacket(message); + } + + @Override + public void stopNpcEmoteExample(Player viewer, UUID npcUuid) { + JsonObject message = new JsonObject(); + message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.cosmetic.v1.StopNpcEmoteMessage"); + message.add("npc_uuid", JsonUtil.createUuidObject(npcUuid)); + + JsonPacketUtil.broadcastPacket(message); + } + + @Override + public void resetNpcEmotesExample() { + JsonObject message = new JsonObject(); + message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.cosmetic.v1.ResetNpcEmotesMessage"); + + JsonPacketUtil.broadcastPacket(message); + } + @Override public void displaySprayExample(Player viewer, int sprayId) { Block block = viewer.getTargetBlockExact(10); diff --git a/example/bukkit/proto/src/main/java/com/lunarclient/apollo/example/proto/module/CosmeticProtoExample.java b/example/bukkit/proto/src/main/java/com/lunarclient/apollo/example/proto/module/CosmeticProtoExample.java index 4454da14..e83851d7 100644 --- a/example/bukkit/proto/src/main/java/com/lunarclient/apollo/example/proto/module/CosmeticProtoExample.java +++ b/example/bukkit/proto/src/main/java/com/lunarclient/apollo/example/proto/module/CosmeticProtoExample.java @@ -27,11 +27,15 @@ import com.lunarclient.apollo.cosmetic.v1.CloakOptions; import com.lunarclient.apollo.cosmetic.v1.Cosmetic; import com.lunarclient.apollo.cosmetic.v1.DisplaySprayMessage; +import com.lunarclient.apollo.cosmetic.v1.Emote; import com.lunarclient.apollo.cosmetic.v1.EquipNpcCosmeticsMessage; import com.lunarclient.apollo.cosmetic.v1.PetOptions; import com.lunarclient.apollo.cosmetic.v1.RemoveSprayMessage; import com.lunarclient.apollo.cosmetic.v1.ResetNpcCosmeticsMessage; +import com.lunarclient.apollo.cosmetic.v1.ResetNpcEmotesMessage; import com.lunarclient.apollo.cosmetic.v1.ResetSpraysMessage; +import com.lunarclient.apollo.cosmetic.v1.StartNpcEmoteMessage; +import com.lunarclient.apollo.cosmetic.v1.StopNpcEmoteMessage; import com.lunarclient.apollo.cosmetic.v1.UnequipNpcCosmeticsMessage; import com.lunarclient.apollo.example.module.impl.CosmeticExample; import com.lunarclient.apollo.example.proto.util.ProtobufPacketUtil; @@ -81,6 +85,16 @@ public void equipNpcCosmeticsExample(Player viewer, UUID npcUuid) { ProtobufPacketUtil.sendPacket(viewer, message); } + @Override + public void equipNpcCosmeticsCopyLocalExample(Player viewer, UUID npcUuid) { + EquipNpcCosmeticsMessage message = EquipNpcCosmeticsMessage.newBuilder() + .setNpcUuid(ProtobufUtil.createUuidProto(npcUuid)) + .setCopyLocalCosmetics(true) + .build(); + + ProtobufPacketUtil.broadcastPacket(message); + } + @Override public void equipNpcCosmeticsInternal(Player viewer, UUID npcUuid, List cosmeticIds) { List cosmetics = cosmeticIds.stream() @@ -126,6 +140,46 @@ public void resetNpcCosmeticsExample(Player viewer, UUID npcUuid) { ProtobufPacketUtil.broadcastPacket(message); } + @Override + public void startNpcEmoteExample(Player viewer, UUID npcUuid) { + StartNpcEmoteMessage message = StartNpcEmoteMessage.newBuilder() + .setNpcUuid(ProtobufUtil.createUuidProto(npcUuid)) + .setEmote(Emote.newBuilder() + .setId(56) + .build()) + .build(); + + ProtobufPacketUtil.broadcastPacket(message); + } + + @Override + public void startNpcEmoteInternal(Player viewer, UUID npcUuid, int emoteId, int metadata) { + StartNpcEmoteMessage message = StartNpcEmoteMessage.newBuilder() + .setNpcUuid(ProtobufUtil.createUuidProto(npcUuid)) + .setEmote(Emote.newBuilder() + .setId(emoteId) + .setMetadata(metadata) + .build()) + .build(); + + ProtobufPacketUtil.broadcastPacket(message); + } + + @Override + public void stopNpcEmoteExample(Player viewer, UUID npcUuid) { + StopNpcEmoteMessage message = StopNpcEmoteMessage.newBuilder() + .setNpcUuid(ProtobufUtil.createUuidProto(npcUuid)) + .build(); + + ProtobufPacketUtil.broadcastPacket(message); + } + + @Override + public void resetNpcEmotesExample() { + ResetNpcEmotesMessage message = ResetNpcEmotesMessage.getDefaultInstance(); + ProtobufPacketUtil.broadcastPacket(message); + } + @Override public void displaySprayExample(Player viewer, int sprayId) { Block block = viewer.getTargetBlockExact(10); diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f2b33187..9389be29 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,7 +11,7 @@ geantyref = "1.3.11" idea = "1.1.7" jetbrains = "24.0.1" lombok = "1.18.38" -protobuf = "0.1.7" +protobuf = "0.1.8" gson = "2.10.1" shadow = "9.4.1" spotless = "8.4.0"