/
WanderGoal.java
253 lines (220 loc) · 7.94 KB
/
WanderGoal.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
package net.citizensnpcs.api.ai.goals;
import java.util.Random;
import java.util.function.Function;
import java.util.function.Supplier;
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.event.Listener;
import com.sk89q.worldedit.bukkit.BukkitAdapter;
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
import ch.ethz.globis.phtree.PhTreeSolid;
import net.citizensnpcs.api.ai.Goal;
import net.citizensnpcs.api.ai.tree.Behavior;
import net.citizensnpcs.api.ai.tree.BehaviorGoalAdapter;
import net.citizensnpcs.api.ai.tree.BehaviorStatus;
import net.citizensnpcs.api.astar.pathfinder.MinecraftBlockExaminer;
import net.citizensnpcs.api.npc.NPC;
/**
* A sample {@link Goal}/{@link Behavior} that will wander within a certain radius or {@link QuadTree}.
*/
public class WanderGoal extends BehaviorGoalAdapter implements Listener {
private int delay;
private int delayedTicks;
private final Function<NPC, Location> fallback;
private boolean forceFinish;
private int movingTicks;
private final NPC npc;
private boolean pathfind;
private boolean paused;
private Location target;
private final Supplier<PhTreeSolid<Boolean>> tree;
private final Supplier<Object> worldguardRegion;
private int xrange;
private int yrange;
private WanderGoal(NPC npc, boolean pathfind, int xrange, int yrange, Supplier<PhTreeSolid<Boolean>> tree,
Function<NPC, Location> fallback, Supplier<Object> worldguardRegion, int delay) {
this.npc = npc;
this.pathfind = pathfind;
this.worldguardRegion = worldguardRegion;
this.xrange = xrange;
this.yrange = yrange;
this.tree = tree;
this.fallback = fallback;
this.delay = delay;
}
public Function<Block, Boolean> blockFilter() {
return block -> {
if ((MinecraftBlockExaminer.isLiquidOrInLiquid(block.getRelative(BlockFace.UP))
|| MinecraftBlockExaminer.isLiquidOrInLiquid(block.getRelative(0, 2, 0)))
&& npc.getNavigator().getDefaultParameters().avoidWater()) {
return false;
}
if (worldguardRegion != null) {
Object region = worldguardRegion.get();
if (region != null) {
try {
if (!((ProtectedRegion) region).contains(BukkitAdapter.asBlockVector(block.getLocation())))
return false;
} catch (Throwable t) {
t.printStackTrace();
}
}
}
if (tree != null) {
long[] pt = { block.getX(), block.getY(), block.getZ() };
if (tree.get() != null && !tree.get().queryIntersect(pt, pt).hasNext())
return false;
}
return true;
};
}
private Location findRandomPosition() {
Location found = MinecraftBlockExaminer.findRandomValidLocation(npc.getStoredLocation(), pathfind ? xrange : 1,
pathfind ? yrange : 1, blockFilter(), RANDOM);
if (found == null && fallback != null) {
return fallback.apply(npc);
}
return found;
}
public void pause() {
this.paused = true;
if (target != null) {
if (pathfind) {
npc.getNavigator().cancelNavigation();
} else {
npc.setMoveDestination(null);
}
}
}
@Override
public void reset() {
target = null;
movingTicks = 0;
delayedTicks = delay;
forceFinish = false;
}
@Override
public BehaviorStatus run() {
if (paused)
return BehaviorStatus.SUCCESS;
if (pathfind) {
if (!npc.getNavigator().isNavigating() || forceFinish) {
return BehaviorStatus.SUCCESS;
}
} else {
if (npc.getEntity().getLocation().distance(target) >= 0.1) {
npc.setMoveDestination(target);
} else {
return BehaviorStatus.SUCCESS;
}
if (movingTicks-- <= 0) {
npc.setMoveDestination(null);
return BehaviorStatus.SUCCESS;
}
}
return BehaviorStatus.RUNNING;
}
public void setDelay(int delayTicks) {
this.delay = delayTicks;
this.delayedTicks = delayTicks;
forceFinish = true;
}
public void setPathfind(boolean pathfind) {
this.pathfind = pathfind;
forceFinish = true;
}
public void setXYRange(int xrange, int yrange) {
this.xrange = xrange;
this.yrange = yrange;
forceFinish = true;
}
@Override
public boolean shouldExecute() {
if (!npc.isSpawned() || npc.getNavigator().isNavigating() || paused)
return false;
if (delayedTicks-- > 0)
return false;
Location dest = findRandomPosition();
if (dest == null)
return false;
if (pathfind) {
npc.getNavigator().setTarget(dest);
npc.getNavigator().getLocalParameters().stuckAction(null);
npc.getNavigator().getLocalParameters().addSingleUseCallback((reason) -> forceFinish = true);
} else {
Random random = new Random();
dest.setX(dest.getX() + random.nextDouble() * 0.5);
dest.setZ(dest.getZ() + random.nextDouble() * 0.5);
movingTicks = 20 + random.nextInt(20);
}
this.target = dest;
return true;
}
public void unpause() {
this.paused = false;
}
public static class Builder {
private int delay = 10;
private Function<NPC, Location> fallback;
private final NPC npc;
private boolean pathfind = true;
private Supplier<PhTreeSolid<Boolean>> tree;
private Supplier<Object> worldguardRegion;
private int xrange = 10;
private int yrange = 2;
private Builder(NPC npc) {
this.npc = npc;
this.tree = null;
this.fallback = null;
this.worldguardRegion = null;
}
public WanderGoal build() {
return new WanderGoal(npc, pathfind, xrange, yrange, tree, fallback, worldguardRegion, delay);
}
public Builder delay(int delay) {
this.delay = delay;
return this;
}
public Builder fallback(Function<NPC, Location> fallback) {
this.fallback = fallback;
return this;
}
public Builder pathfind(boolean pathfind) {
this.pathfind = pathfind;
return this;
}
public Builder regionCentres(Supplier<Iterable<Location>> supplier) {
this.tree = () -> {
PhTreeSolid<Boolean> tree = PhTreeSolid.create(3);
for (Location loc : supplier.get()) {
long[] lower = { loc.getBlockX() - xrange, loc.getBlockY() - yrange, loc.getBlockZ() - xrange };
long[] upper = { loc.getBlockX() + xrange, loc.getBlockY() + yrange, loc.getBlockZ() + xrange };
tree.put(lower, upper, true);
}
return tree;
};
return this;
}
public Builder tree(Supplier<PhTreeSolid<Boolean>> supplier) {
this.tree = supplier;
return this;
}
public Builder worldguardRegion(Supplier<Object> worldguardRegion) {
this.worldguardRegion = worldguardRegion;
return this;
}
public Builder xrange(int xrange) {
this.xrange = xrange;
return this;
}
public Builder yrange(int yrange) {
this.yrange = yrange;
return this;
}
}
public static Builder builder(NPC npc) {
return new Builder(npc);
}
private static final Random RANDOM = new Random();
}