Skip to content

Commit 06a5855

Browse files
authored
example(internal): persistent npcs (#280)
1 parent 46cb2d7 commit 06a5855

15 files changed

Lines changed: 625 additions & 19 deletions

File tree

example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/listener/ApolloPlayerApiListener.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,10 @@
3232
import com.lunarclient.apollo.example.module.impl.CooldownExample;
3333
import com.lunarclient.apollo.example.module.impl.CosmeticExample;
3434
import com.lunarclient.apollo.example.module.impl.ServerLinkExample;
35+
import com.lunarclient.apollo.example.nms.CommandCosmetic;
3536
import com.lunarclient.apollo.example.nms.NpcManager;
3637
import com.lunarclient.apollo.example.nms.PlayerNpc;
3738
import com.lunarclient.apollo.player.ApolloPlayer;
38-
import java.util.Optional;
3939
import org.bukkit.entity.Player;
4040

4141
public class ApolloPlayerApiListener implements ApolloListener {
@@ -79,8 +79,11 @@ private void onApolloRegister(ApolloRegisterPlayerEvent event) {
7979
CosmeticExample cosmeticExample = this.example.getCosmeticExample();
8080
NpcManager npcManager = this.example.getNpcManager();
8181

82-
Optional<PlayerNpc> npc = npcManager.findByName("Apollo");
83-
npc.ifPresent(playerNpc -> cosmeticExample.equipNpcCosmeticsExample(player, playerNpc.getUuid()));
82+
for (PlayerNpc npc : npcManager.getNpcs()) {
83+
for (CommandCosmetic spec : npc.getCosmetics()) {
84+
cosmeticExample.equipNpcCosmeticToViewer(player, npc.getUuid(), spec);
85+
}
86+
}
8487
}
8588

8689
}

example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/module/CosmeticApiExample.java

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,14 @@
2727
import com.lunarclient.apollo.Apollo;
2828
import com.lunarclient.apollo.common.location.ApolloBlockLocation;
2929
import com.lunarclient.apollo.example.module.impl.CosmeticExample;
30+
import com.lunarclient.apollo.example.nms.CommandCosmetic;
3031
import com.lunarclient.apollo.module.cosmetic.Cosmetic;
3132
import com.lunarclient.apollo.module.cosmetic.CosmeticModule;
3233
import com.lunarclient.apollo.module.cosmetic.Spray;
34+
import com.lunarclient.apollo.module.cosmetic.options.BodyOptions;
3335
import com.lunarclient.apollo.module.cosmetic.options.CloakOptions;
36+
import com.lunarclient.apollo.module.cosmetic.options.CosmeticOptions;
37+
import com.lunarclient.apollo.module.cosmetic.options.HatOptions;
3438
import com.lunarclient.apollo.module.cosmetic.options.PetOptions;
3539
import com.lunarclient.apollo.module.packetenrichment.raytrace.Direction;
3640
import com.lunarclient.apollo.player.ApolloPlayer;
@@ -87,6 +91,54 @@ public void equipNpcCosmeticsInternal(Player viewer, UUID npcUuid, List<Integer>
8791
this.cosmeticModule.equipNpcCosmetics(Recipients.ofEveryone(), npcUuid, cosmetics);
8892
}
8993

94+
@Override
95+
public void equipNpcCosmeticInternal(Player viewer, UUID npcUuid, CommandCosmetic spec) {
96+
this.cosmeticModule.equipNpcCosmetics(Recipients.ofEveryone(), npcUuid, Lists.newArrayList(this.toApiCosmetic(spec)));
97+
}
98+
99+
@Override
100+
public void equipNpcCosmeticToViewer(Player viewer, UUID npcUuid, CommandCosmetic spec) {
101+
Apollo.getPlayerManager().getPlayer(viewer.getUniqueId()).ifPresent(apolloPlayer ->
102+
this.cosmeticModule.equipNpcCosmetics(apolloPlayer, npcUuid, Lists.newArrayList(this.toApiCosmetic(spec))));
103+
}
104+
105+
private Cosmetic toApiCosmetic(CommandCosmetic spec) {
106+
return Cosmetic.builder()
107+
.id(spec.getId())
108+
.options(this.toApiOptions(spec.getOptions()))
109+
.build();
110+
}
111+
112+
private CosmeticOptions toApiOptions(CommandCosmetic.Options options) {
113+
if (options instanceof CommandCosmetic.Hat) {
114+
CommandCosmetic.Hat hat = (CommandCosmetic.Hat) options;
115+
return HatOptions.builder()
116+
.showOverHelmet(hat.isShowOverHelmet())
117+
.showOverSkinLayer(hat.isShowOverSkinLayer())
118+
.heightOffset(hat.getHeightOffset())
119+
.build();
120+
} else if (options instanceof CommandCosmetic.Cloak) {
121+
CommandCosmetic.Cloak cloak = (CommandCosmetic.Cloak) options;
122+
return CloakOptions.builder()
123+
.useClothPhysics(cloak.isUseClothPhysics())
124+
.build();
125+
} else if (options instanceof CommandCosmetic.Pet) {
126+
CommandCosmetic.Pet pet = (CommandCosmetic.Pet) options;
127+
return PetOptions.builder()
128+
.flipShoulder(pet.isFlipShoulder())
129+
.build();
130+
} else if (options instanceof CommandCosmetic.Body) {
131+
CommandCosmetic.Body body = (CommandCosmetic.Body) options;
132+
return BodyOptions.builder()
133+
.showOverChestplate(body.isShowOverChestplate())
134+
.showOverLeggings(body.isShowOverLeggings())
135+
.showOverBoots(body.isShowOverBoots())
136+
.build();
137+
}
138+
139+
return null;
140+
}
141+
90142
@Override
91143
public void unequipNpcCosmeticsExample(Player viewer, UUID npcUuid) {
92144
Optional<ApolloPlayer> apolloPlayerOpt = Apollo.getPlayerManager().getPlayer(viewer.getUniqueId());

example/bukkit/api/src/main/resources/plugin.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ folia-supported: true
99
commands:
1010
npc:
1111
description: "NPCs!"
12+
aliases: [apollonpc]
1213
apollodebug:
1314
description: "Apollo Debug!"
1415
modstatus:

example/bukkit/common/src/main/java/com/lunarclient/apollo/example/ApolloExamplePlugin.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ public void onEnable() {
152152
@Override
153153
public void onDisable() {
154154
if (this.npcManager != null) {
155-
this.npcManager.removeAll();
155+
this.npcManager.despawnAll();
156156
}
157157
}
158158

example/bukkit/common/src/main/java/com/lunarclient/apollo/example/command/CosmeticCommand.java

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,17 @@
2525

2626
import com.lunarclient.apollo.example.ApolloExamplePlugin;
2727
import com.lunarclient.apollo.example.module.impl.CosmeticExample;
28+
import com.lunarclient.apollo.example.nms.CommandCosmetic;
2829
import com.lunarclient.apollo.example.nms.PlayerNpc;
30+
import com.lunarclient.apollo.example.util.CommandUtil;
2931
import java.util.ArrayList;
32+
import java.util.Collections;
33+
import java.util.HashMap;
3034
import java.util.List;
35+
import java.util.Map;
3136
import java.util.Optional;
3237
import java.util.UUID;
38+
import java.util.stream.Collectors;
3339
import org.bukkit.ChatColor;
3440
import org.bukkit.command.Command;
3541
import org.bukkit.command.CommandExecutor;
@@ -82,21 +88,31 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command
8288

8389
switch (args[0].toLowerCase()) {
8490
case "equip": {
91+
if (args.length >= 3 && this.isCosmeticType(args[2])) {
92+
this.handleTypedEquip(player, example, uuid, npcName, args);
93+
break;
94+
}
95+
8596
List<Integer> cosmeticIds = this.parseCosmeticIds(args);
8697
example.equipNpcCosmeticsInternal(player, uuid, cosmeticIds);
98+
this.persistEquipped(uuid, cosmeticIds.stream()
99+
.map(id -> CommandCosmetic.builder().id(id).build())
100+
.collect(Collectors.toList()));
87101
player.sendMessage(ChatColor.GREEN + "Equipped cosmetics " + cosmeticIds + " on NPC " + npcName);
88102
break;
89103
}
90104

91105
case "unequip": {
92106
List<Integer> cosmeticIds = this.parseCosmeticIds(args);
93107
example.unequipNpcCosmeticsInternal(player, uuid, cosmeticIds);
108+
this.persistUnequipped(uuid, cosmeticIds);
94109
player.sendMessage(ChatColor.GREEN + "Unequipped cosmetics " + cosmeticIds + " from NPC " + npcName);
95110
break;
96111
}
97112

98113
case "reset": {
99114
example.resetNpcCosmeticsExample(player, uuid);
115+
this.persistReset(uuid);
100116
player.sendMessage(ChatColor.GREEN + "Reset all cosmetics on NPC " + npcName);
101117
break;
102118
}
@@ -170,6 +186,125 @@ private boolean handleSpray(Player player, CosmeticExample example, String[] arg
170186
return true;
171187
}
172188

189+
private boolean isCosmeticType(String type) {
190+
String lower = type.toLowerCase();
191+
return "hat".equals(lower) || "cloak".equals(lower) || "pet".equals(lower) || "body".equals(lower);
192+
}
193+
194+
private void handleTypedEquip(Player player, CosmeticExample example, UUID uuid, String npcName, String[] args) {
195+
if (args.length < 4) {
196+
this.sendUsage(player);
197+
return;
198+
}
199+
200+
int cosmeticId;
201+
try {
202+
cosmeticId = Integer.parseInt(args[3]);
203+
} catch (NumberFormatException ex) {
204+
player.sendMessage(ChatColor.RED + "Cosmetic id must be an integer.");
205+
return;
206+
}
207+
208+
Map<String, String> options = this.parseOptions(args, 4);
209+
String type = args[2].toLowerCase();
210+
211+
CommandCosmetic.Options optionsSpec;
212+
switch (type) {
213+
case "hat": {
214+
optionsSpec = CommandCosmetic.Hat.builder()
215+
.showOverHelmet(CommandUtil.parseBoolean(options.get("showoverhelmet"), true))
216+
.showOverSkinLayer(CommandUtil.parseBoolean(options.get("showoverskinlayer"), true))
217+
.heightOffset(CommandUtil.parseFloat(options.get("heightoffset"), 0f))
218+
.build();
219+
break;
220+
}
221+
case "cloak": {
222+
optionsSpec = CommandCosmetic.Cloak.builder()
223+
.useClothPhysics(CommandUtil.parseBoolean(options.get("useclothphysics"), false))
224+
.build();
225+
break;
226+
}
227+
case "pet": {
228+
optionsSpec = CommandCosmetic.Pet.builder()
229+
.flipShoulder(CommandUtil.parseBoolean(options.get("flipshoulder"), false))
230+
.build();
231+
break;
232+
}
233+
case "body": {
234+
optionsSpec = CommandCosmetic.Body.builder()
235+
.showOverChestplate(CommandUtil.parseBoolean(options.get("showoverchestplate"), true))
236+
.showOverLeggings(CommandUtil.parseBoolean(options.get("showoverleggings"), true))
237+
.showOverBoots(CommandUtil.parseBoolean(options.get("showoverboots"), true))
238+
.build();
239+
break;
240+
}
241+
default: {
242+
this.sendUsage(player);
243+
return;
244+
}
245+
}
246+
247+
CommandCosmetic spec = CommandCosmetic.builder()
248+
.id(cosmeticId)
249+
.options(optionsSpec)
250+
.build();
251+
252+
example.equipNpcCosmeticInternal(player, uuid, spec);
253+
this.persistEquipped(uuid, Collections.singletonList(spec));
254+
player.sendMessage(ChatColor.GREEN + "Equipped " + type + " cosmetic " + cosmeticId + " on NPC " + npcName);
255+
}
256+
257+
private void persistEquipped(UUID npcUuid, List<CommandCosmetic> equipped) {
258+
Optional<PlayerNpc> npcOpt = ApolloExamplePlugin.getInstance().getNpcManager().findByUuid(npcUuid);
259+
if (!npcOpt.isPresent()) {
260+
return;
261+
}
262+
263+
List<CommandCosmetic> cosmetics = npcOpt.get().getCosmetics();
264+
for (CommandCosmetic cosmetic : equipped) {
265+
cosmetics.removeIf(existing -> existing.getId() == cosmetic.getId());
266+
cosmetics.add(cosmetic);
267+
}
268+
269+
ApolloExamplePlugin.getInstance().getNpcManager().save();
270+
}
271+
272+
private void persistUnequipped(UUID npcUuid, List<Integer> cosmeticIds) {
273+
Optional<PlayerNpc> npcOpt = ApolloExamplePlugin.getInstance().getNpcManager().findByUuid(npcUuid);
274+
if (!npcOpt.isPresent()) {
275+
return;
276+
}
277+
278+
npcOpt.get().getCosmetics().removeIf(cosmetic -> cosmeticIds.contains(cosmetic.getId()));
279+
ApolloExamplePlugin.getInstance().getNpcManager().save();
280+
}
281+
282+
private void persistReset(UUID npcUuid) {
283+
Optional<PlayerNpc> npcOpt = ApolloExamplePlugin.getInstance().getNpcManager().findByUuid(npcUuid);
284+
if (!npcOpt.isPresent()) {
285+
return;
286+
}
287+
288+
npcOpt.get().getCosmetics().clear();
289+
ApolloExamplePlugin.getInstance().getNpcManager().save();
290+
}
291+
292+
private Map<String, String> parseOptions(String[] args, int startIndex) {
293+
Map<String, String> options = new HashMap<>();
294+
for (int i = startIndex; i < args.length; i++) {
295+
String token = args[i];
296+
int index = token.indexOf('=');
297+
298+
if (index <= 0 || index == token.length() - 1) {
299+
continue;
300+
}
301+
302+
options.put(token.substring(0, index).toLowerCase(), token.substring(index + 1));
303+
}
304+
305+
return options;
306+
}
307+
173308
private List<Integer> parseCosmeticIds(String[] args) {
174309
List<Integer> ids = new ArrayList<>();
175310
for (int i = 2; i < args.length; i++) {
@@ -184,8 +319,26 @@ private List<Integer> parseCosmeticIds(String[] args) {
184319
private void sendUsage(Player player) {
185320
player.sendMessage("Usage:");
186321
player.sendMessage(" - /cosmetic equip <npc_name> [cosmeticIds]");
322+
player.sendMessage(ChatColor.ITALIC + " /cosmetic equip Apollo 434 3654 3977");
323+
player.sendMessage("");
324+
player.sendMessage(" - /cosmetic equip <npc_name> hat <id>");
325+
player.sendMessage(ChatColor.ITALIC + " /cosmetic equip Apollo hat 434 showOverHelmet=true showOverSkinLayer=true heightOffset=0.0");
326+
player.sendMessage("");
327+
player.sendMessage(" - /cosmetic equip <npc_name> cloak <id>");
328+
player.sendMessage(ChatColor.ITALIC + " /cosmetic equip Apollo cloak 3 useClothPhysics=true");
329+
player.sendMessage("");
330+
player.sendMessage(" - /cosmetic equip <npc_name> pet <id>");
331+
player.sendMessage(ChatColor.ITALIC + " /cosmetic equip Apollo pet 5095 flipShoulder=true");
332+
player.sendMessage("");
333+
player.sendMessage(" - /cosmetic equip <npc_name> body <id>");
334+
player.sendMessage(ChatColor.ITALIC + " /cosmetic equip Apollo body 3977 showOverChestplate=true showOverLeggings=true showOverBoots=true");
335+
player.sendMessage("");
187336
player.sendMessage(" - /cosmetic unequip <npc_name> [cosmeticIds]");
337+
player.sendMessage(ChatColor.ITALIC + " /cosmetic unequip Apollo 434 3654");
338+
player.sendMessage("");
188339
player.sendMessage(" - /cosmetic reset <npc_name>");
340+
player.sendMessage(ChatColor.ITALIC + " /cosmetic reset Apollo");
341+
player.sendMessage("");
189342
this.sendSprayUsage(player);
190343
}
191344

example/bukkit/common/src/main/java/com/lunarclient/apollo/example/command/NpcCommand.java

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -86,17 +86,6 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command
8686
break;
8787
}
8888

89-
case "reset": {
90-
if (npcManager.getNpcs().isEmpty()) {
91-
player.sendMessage(ChatColor.YELLOW + "There are no NPCs to remove.");
92-
break;
93-
}
94-
95-
npcManager.removeAll();
96-
player.sendMessage(ChatColor.GREEN + "Removed all tracked NPCs.");
97-
break;
98-
}
99-
10089
default: {
10190
this.sendUsage(player);
10291
break;
@@ -109,7 +98,6 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command
10998
private void sendUsage(Player player) {
11099
player.sendMessage("Usage: /npc spawn <name>");
111100
player.sendMessage("Usage: /npc remove <name>");
112-
player.sendMessage("Usage: /npc reset");
113101
}
114102

115103
}

example/bukkit/common/src/main/java/com/lunarclient/apollo/example/module/impl/CosmeticExample.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
package com.lunarclient.apollo.example.module.impl;
2525

2626
import com.lunarclient.apollo.example.module.ApolloModuleExample;
27+
import com.lunarclient.apollo.example.nms.CommandCosmetic;
28+
import java.util.Collections;
2729
import java.util.List;
2830
import java.util.UUID;
2931
import org.bukkit.entity.Player;
@@ -34,6 +36,14 @@ public abstract class CosmeticExample extends ApolloModuleExample {
3436

3537
public abstract void equipNpcCosmeticsInternal(Player viewer, UUID npcUuid, List<Integer> cosmeticIds);
3638

39+
public void equipNpcCosmeticInternal(Player viewer, UUID npcUuid, CommandCosmetic cosmetic) {
40+
this.equipNpcCosmeticsInternal(viewer, npcUuid, Collections.singletonList(cosmetic.getId()));
41+
}
42+
43+
public void equipNpcCosmeticToViewer(Player viewer, UUID npcUuid, CommandCosmetic cosmetic) {
44+
this.equipNpcCosmeticInternal(viewer, npcUuid, cosmetic);
45+
}
46+
3747
public abstract void unequipNpcCosmeticsExample(Player viewer, UUID npcUuid);
3848

3949
public abstract void unequipNpcCosmeticsInternal(Player viewer, UUID npcUuid, List<Integer> cosmeticIds);

0 commit comments

Comments
 (0)