Skip to content

Commit b31d29c

Browse files
committed
/lrmobcontrol command
1 parent df80343 commit b31d29c

File tree

8 files changed

+298
-0
lines changed

8 files changed

+298
-0
lines changed

docs/user-guide.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Refer to the [Configuration Guide](configurations.html).
2020
- `/lrattack [prio] [target_thru_walls] [entity_class]` - makes all raiders of the current wave attack an entity type
2121
- `/lrconfig spawn <x> <y> <z>` - for the current large raid, make all future waves spawn from this location
2222
- `/lrconfig target <x> <y> <z> <radius> <navSpeed>` - for the current large raid, make raiders path-find to this location within range of this radius
23+
- `/lrmobcontrol <entity_class> <range_from_player> <x> <y> <z> <radius> <navSpeed> <prio> <pathfind_once>` - selects all of the entity type in range of the player, and makes them path-find to the radius of the target. If pathfind_once is true, the target will be removed on arrival.
2324

2425
## Placeholders
2526

src/main/java/com/solarrabbit/largeraids/LargeRaids.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ private void loadCommands() {
143143
getCommand("lrattack").setExecutor(new RaiderAttackCommand(this));
144144
getCommand("lrconfig").setExecutor(new RaidConfigCommand(this));
145145
getCommand("lrconfig").setTabCompleter(new RaidConfigCommandCompleter());
146+
getCommand("lrmobcontrol").setExecutor(new MobControlCommand(this));
146147
}
147148

148149
private void loadCustomConfigs() {
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
package com.solarrabbit.largeraids.command;
2+
3+
import org.bukkit.ChatColor;
4+
import org.bukkit.Location;
5+
import org.bukkit.command.Command;
6+
import org.bukkit.command.CommandExecutor;
7+
import org.bukkit.command.CommandSender;
8+
import org.bukkit.entity.Player;
9+
10+
import com.solarrabbit.largeraids.LargeRaids;
11+
import com.solarrabbit.largeraids.nms.AbstractBlockPositionWrapper;
12+
import com.solarrabbit.largeraids.nms.AbstractWorldServerWrapper;
13+
import com.solarrabbit.largeraids.util.VersionUtil;
14+
15+
/**
16+
* Makes the entity type in range of the player path-find to a given location.
17+
*/
18+
public class MobControlCommand implements CommandExecutor {
19+
private final LargeRaids plugin;
20+
21+
public MobControlCommand(LargeRaids plugin) {
22+
this.plugin = plugin;
23+
}
24+
25+
@Override
26+
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
27+
if (!(sender instanceof Player)) {
28+
sender.sendMessage(ChatColor.RED + this.plugin.getMessage("sender-player"));
29+
return false;
30+
}
31+
32+
if (args.length < 3)
33+
return false;
34+
35+
Class<?> entityClass = null;
36+
try {
37+
entityClass = Class.forName(args[0]);
38+
} catch (ClassNotFoundException e) {
39+
sender.sendMessage(ChatColor.RED + this.plugin.getMessage("mob-control.class-not-found"));
40+
return false;
41+
}
42+
43+
Location search = ((Player) sender).getLocation();
44+
double range = 0;
45+
try {
46+
range = Double.parseDouble(args[1]);
47+
if (range <= 0) {
48+
sender.sendMessage(ChatColor.RED + this.plugin.getMessage("mob-control.invalid-range"));
49+
return false;
50+
}
51+
} catch (NumberFormatException e) {
52+
sender.sendMessage(ChatColor.RED + this.plugin.getMessage("mob-control.invalid-range"));
53+
return false;
54+
}
55+
56+
if (args.length == 3 && args[2].equalsIgnoreCase("clear")) {
57+
AbstractWorldServerWrapper world = VersionUtil.getCraftWorldWrapper(search.getWorld()).getHandle();
58+
AbstractBlockPositionWrapper searchWrapper = VersionUtil.getBlockPositionWrapper(search);
59+
int result = world.clearMobTargets(entityClass, searchWrapper, range);
60+
if (result == -1) {
61+
sender.sendMessage(ChatColor.RED + this.plugin.getMessage("mob-control.invalid-entity-class"));
62+
} else if (result == 0) {
63+
sender.sendMessage(ChatColor.YELLOW + String.format(this.plugin.getMessage("mob-control.no-mobs-found"),
64+
entityClass.getSimpleName(), range, (int)Math.floor(search.getX()),
65+
(int)Math.floor(search.getY()), (int)Math.floor(search.getZ())));
66+
} else {
67+
sender.sendMessage(ChatColor.GREEN + String.format(this.plugin.getMessage("mob-control.mobs-cleared"), result,
68+
entityClass.getSimpleName()));
69+
}
70+
return true;
71+
}
72+
73+
if (args.length != 9)
74+
return false;
75+
76+
Location target;
77+
try {
78+
double x = parseDoubleOrRelative(args[2], search, 0);
79+
double y = parseDoubleOrRelative(args[3], search, 1);
80+
double z = parseDoubleOrRelative(args[4], search, 2);
81+
target = new Location(search.getWorld(), x, y, z);
82+
} catch (NumberFormatException e) {
83+
sender.sendMessage(ChatColor.RED + this.plugin.getMessage("mob-control.invalid-coordinates"));
84+
return false;
85+
}
86+
87+
double radius;
88+
try {
89+
radius = Double.parseDouble(args[5]);
90+
if (radius < 0) {
91+
sender.sendMessage(ChatColor.RED + this.plugin.getMessage("mob-control.invalid-radius"));
92+
return false;
93+
}
94+
} catch (NumberFormatException e) {
95+
sender.sendMessage(ChatColor.RED + this.plugin.getMessage("mob-control.invalid-radius"));
96+
return false;
97+
}
98+
99+
double navSpeed;
100+
try {
101+
navSpeed = Double.parseDouble(args[6]);
102+
if (navSpeed <= 0) {
103+
sender.sendMessage(ChatColor.RED + this.plugin.getMessage("mob-control.invalid-navspeed"));
104+
return false;
105+
}
106+
} catch (NumberFormatException e) {
107+
sender.sendMessage(ChatColor.RED + this.plugin.getMessage("mob-control.invalid-navspeed"));
108+
return false;
109+
}
110+
111+
int prio;
112+
try {
113+
prio = Integer.parseInt(args[7]);
114+
if (prio < 0) {
115+
sender.sendMessage(ChatColor.RED + this.plugin.getMessage("mob-control.invalid-prio"));
116+
return false;
117+
}
118+
} catch (NumberFormatException e) {
119+
sender.sendMessage(ChatColor.RED + this.plugin.getMessage("mob-control.invalid-prio"));
120+
return false;
121+
}
122+
123+
boolean pathfindOnce;
124+
if (!args[8].equalsIgnoreCase("true") && !args[8].equalsIgnoreCase("false")) {
125+
sender.sendMessage(ChatColor.RED + this.plugin.getMessage("mob-control.pathfind-once-boolean"));
126+
return false;
127+
}
128+
pathfindOnce = Boolean.parseBoolean(args[8]);
129+
130+
AbstractWorldServerWrapper world = VersionUtil.getCraftWorldWrapper(search.getWorld()).getHandle();
131+
AbstractBlockPositionWrapper searchWrapper = VersionUtil.getBlockPositionWrapper(search);
132+
AbstractBlockPositionWrapper targetWrapper = VersionUtil.getBlockPositionWrapper(target);
133+
int result = world.setMobTargets(entityClass, searchWrapper, range, targetWrapper, radius, navSpeed, prio, pathfindOnce);
134+
135+
if (result == -1) {
136+
sender.sendMessage(ChatColor.RED + this.plugin.getMessage("mob-control.invalid-entity-class"));
137+
} else if (result == 0) {
138+
sender.sendMessage(ChatColor.YELLOW + String.format(this.plugin.getMessage("mob-control.no-mobs-found"),
139+
entityClass.getSimpleName(), range, (int)Math.floor(search.getX()),
140+
(int)Math.floor(search.getY()), (int)Math.floor(search.getZ())));
141+
} else {
142+
sender.sendMessage(ChatColor.GREEN + String.format(this.plugin.getMessage("mob-control.mobs-set"), result,
143+
entityClass.getSimpleName(), radius, (int)Math.floor(target.getX()),
144+
(int)Math.floor(target.getY()), (int)Math.floor(target.getZ()), navSpeed));
145+
}
146+
147+
return true;
148+
}
149+
150+
private double parseDoubleOrRelative(String pos, Location loc, int type) {
151+
if (loc == null || pos.length() == 0 || pos.charAt(0) != '~')
152+
return Double.parseDouble(pos);
153+
double relative = pos.length() == 1 ? 0 : Double.parseDouble(pos.substring(1));
154+
switch (type) {
155+
case 0:
156+
return relative + loc.getX();
157+
case 1:
158+
return relative + loc.getY();
159+
case 2:
160+
return relative + loc.getZ();
161+
default:
162+
return 0;
163+
}
164+
}
165+
}

src/main/java/com/solarrabbit/largeraids/nms/AbstractWorldServerWrapper.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
package com.solarrabbit.largeraids.nms;
22

33
public interface AbstractWorldServerWrapper {
4+
int clearMobTargets(Class<?> entityClass, AbstractBlockPositionWrapper search, double range);
5+
6+
int setMobTargets(Class<?> entityClass, AbstractBlockPositionWrapper search, double range,
7+
AbstractBlockPositionWrapper target, double radius, double navSpeed, int prio, boolean pathfindOnce);
8+
49
AbstractRaidWrapper getRaidAt(AbstractBlockPositionWrapper blockPos);
510

611
AbstractRaidsWrapper getRaids();
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package com.solarrabbit.largeraids.versioned.nms;
2+
3+
import java.util.EnumSet;
4+
5+
import net.minecraft.core.BlockPos;
6+
import net.minecraft.world.entity.Mob;
7+
import net.minecraft.world.entity.ai.goal.Goal;
8+
9+
public class PathfindToTargetGoal extends Goal {
10+
private final Mob mob;
11+
private BlockPos targetPos;
12+
private double targetRadius;
13+
private double navSpeed;
14+
private boolean runOnce;
15+
16+
public PathfindToTargetGoal(Mob mob) {
17+
this.mob = mob;
18+
this.setFlags(EnumSet.of(Goal.Flag.MOVE));
19+
}
20+
21+
public void setTargetPos(BlockPos pos, double radius, double navSpeed, boolean runOnce) {
22+
this.targetPos = pos;
23+
this.targetRadius = radius;
24+
this.navSpeed = navSpeed;
25+
this.runOnce = runOnce;
26+
}
27+
28+
private boolean isCloseToGoal() {
29+
return targetPos.closerToCenterThan(this.mob.position(), targetRadius);
30+
}
31+
32+
@Override
33+
public boolean canUse() {
34+
if (this.mob.getTarget() == null
35+
&& !this.mob.hasControllingPassenger()
36+
&& targetPos != null) {
37+
if (isCloseToGoal()) {
38+
if (runOnce) {
39+
targetPos = null;
40+
mob.goalSelector.removeGoal(this);
41+
return false;
42+
}
43+
} else
44+
return true;
45+
}
46+
47+
return false;
48+
}
49+
50+
@Override
51+
public boolean canContinueToUse() {
52+
return canUse();
53+
}
54+
55+
@Override
56+
public void tick() {
57+
if (targetPos != null) {
58+
this.mob.getNavigation().moveTo(targetPos.getX(), targetPos.getY(), targetPos.getZ(), navSpeed);
59+
}
60+
}
61+
}

src/main/java/com/solarrabbit/largeraids/versioned/nms/WorldServerWrapper.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
package com.solarrabbit.largeraids.versioned.nms;
22

3+
import java.util.List;
4+
35
import com.solarrabbit.largeraids.nms.AbstractBlockPositionWrapper;
46
import com.solarrabbit.largeraids.nms.AbstractWorldServerWrapper;
57

8+
import net.minecraft.core.BlockPos;
69
import net.minecraft.server.level.ServerLevel;
10+
import net.minecraft.world.entity.Mob;
11+
import net.minecraft.world.phys.AABB;
712

813
public class WorldServerWrapper implements AbstractWorldServerWrapper {
914
final ServerLevel server;
@@ -12,6 +17,45 @@ public class WorldServerWrapper implements AbstractWorldServerWrapper {
1217
this.server = server;
1318
}
1419

20+
@Override
21+
public int clearMobTargets(Class<?> entityClass, AbstractBlockPositionWrapper search, double range) {
22+
if (!(Mob.class.isAssignableFrom(entityClass)))
23+
return -1;
24+
25+
BlockPos searchPos = ((BlockPositionWrapper) search).blockPos;
26+
AABB aabb = new AABB(searchPos.getX() - range, searchPos.getY() - range, searchPos.getZ() - range,
27+
searchPos.getX() + range, searchPos.getY() + range, searchPos.getZ() + range);
28+
List<? extends Mob> mobs = server.getEntitiesOfClass((Class<? extends Mob>) entityClass, aabb);
29+
int count = 0;
30+
for (Mob mob : mobs) {
31+
if (mob.goalSelector.getAvailableGoals().removeIf(goal -> goal.getGoal() instanceof PathfindToTargetGoal))
32+
count++;
33+
}
34+
35+
return count;
36+
}
37+
38+
@Override
39+
public int setMobTargets(Class<?> entityClass, AbstractBlockPositionWrapper search, double range,
40+
AbstractBlockPositionWrapper target, double radius, double navSpeed, int prio, boolean pathfindOnce) {
41+
if (!(Mob.class.isAssignableFrom(entityClass)))
42+
return -1;
43+
44+
BlockPos searchPos = ((BlockPositionWrapper) search).blockPos;
45+
AABB aabb = new AABB(searchPos.getX() - range, searchPos.getY() - range, searchPos.getZ() - range,
46+
searchPos.getX() + range, searchPos.getY() + range, searchPos.getZ() + range);
47+
List<? extends Mob> mobs = server.getEntitiesOfClass((Class<? extends Mob>) entityClass, aabb);
48+
for (Mob mob : mobs) {
49+
mob.goalSelector.getAvailableGoals().removeIf(goal -> goal.getGoal() instanceof PathfindToTargetGoal);
50+
51+
PathfindToTargetGoal goal = new PathfindToTargetGoal(mob);
52+
goal.setTargetPos(((BlockPositionWrapper) target).blockPos, radius, navSpeed, pathfindOnce);
53+
mob.goalSelector.addGoal(prio, goal);
54+
}
55+
56+
return mobs.size();
57+
}
58+
1559
@Override
1660
public RaidWrapper getRaidAt(AbstractBlockPositionWrapper blockPos) {
1761
return new RaidWrapper(this.server.getRaidAt(((BlockPositionWrapper) blockPos).blockPos), this.server);

src/main/resources/messages.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,16 @@ raid-config:
9090
invalid-radius: Radius must be a positive number!
9191
invalid-navspeed: Path-find navigation speed must be a positive number!
9292
invalid-coordinates: Invalid coordinates entered!
93+
94+
mob-control:
95+
class-not-found: The specified class was not found!
96+
invalid-entity-class: The specified class is not of Mob type!
97+
invalid-range: Range must be a positive number!
98+
invalid-coordinates: Invalid coordinates entered!
99+
invalid-radius: Radius must be a positive number!
100+
invalid-navspeed: Path-find navigation speed must be a positive number!
101+
invalid-prio: Priority must be a non-negative integer!
102+
pathfind-once-boolean: Path-find once option must be a boolean!
103+
no-mobs-found: No mobs of class %s were found within %.1f blocks of (%d, %d, %d).
104+
mobs-set: "%d mobs of class %s were set to path-find to %.1f blocks of (%d, %d, %d) with speed %.1f."
105+
mobs-cleared: "%d mobs of class %s had their path-finding targets removed."

src/main/resources/plugin.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ commands:
4444
description: Configures some settings of the current large raid.
4545
usage: /<command> spawn <x> <y> <z>, /<command> target <x> <y> <z> <radius> <navSpeed>, /<command> [spawn | target] clear
4646
permission: largeraids.config
47+
lrmobcontrol:
48+
description: Makes the entity type in range of the player path-find to a given location. If pathfind_once is true, the target will be removed on arrival.
49+
usage: /<command> <entity_class> <range_from_player> <x> <y> <z> <radius> <navSpeed> <prio> <pathfind_once>, /<command> <entity_class> <range_from_player> clear
50+
permission: largeraids.mobcontrol
4751

4852
permissions:
4953
largeraids.*:
@@ -60,6 +64,7 @@ permissions:
6064
largeraids.show: true
6165
largeraids.attack: true
6266
largeraids.config: true
67+
largeraids.mobcontrol: true
6368
largeraids.reload:
6469
description: Allow access to lrreload command.
6570
default: op
@@ -90,3 +95,6 @@ permissions:
9095
largeraids.config:
9196
description: Allow access to lrconfig command.
9297
default: op
98+
largeraids.mobcontrol:
99+
description: Allow access to lrmobcontrol command.
100+
default: op

0 commit comments

Comments
 (0)