-
-
Notifications
You must be signed in to change notification settings - Fork 390
/
Copy pathEffSecShoot.java
316 lines (289 loc) · 12.6 KB
/
EffSecShoot.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
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
package ch.njol.skript.sections;
import ch.njol.skript.Skript;
import ch.njol.skript.config.SectionNode;
import ch.njol.skript.doc.Description;
import ch.njol.skript.doc.Examples;
import ch.njol.skript.doc.Name;
import ch.njol.skript.doc.Since;
import ch.njol.skript.entity.EntityData;
import ch.njol.skript.lang.EffectSection;
import ch.njol.skript.lang.Expression;
import ch.njol.skript.lang.SkriptParser.ParseResult;
import ch.njol.skript.lang.Trigger;
import ch.njol.skript.lang.TriggerItem;
import ch.njol.skript.registrations.EventValues;
import ch.njol.skript.util.Direction;
import ch.njol.skript.variables.Variables;
import ch.njol.util.Kleenean;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Fireball;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Projectile;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Method;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
@Name("Shoot")
@Description("Shoots a projectile (or any other entity) from a given entity or location.")
@Examples({
"shoot arrow from all players at speed 2",
"shoot a pig from all players:",
"\tadd event-entity to {_projectiles::*}"
})
@Since("2.10")
public class EffSecShoot extends EffectSection {
// '#shootHandlers' should only return an Entity when there is no 'Trigger' / Consumer
// Returning an Entity allows the velocity and 'lastSpawned' to be set
// Consumers set velocity and 'lastSpawned'
private enum CaseUsage {
NOT_PROJECTILE_NO_TRIGGER {
@Override
public Entity shootHandler(EntityData<?> entityData, LivingEntity shooter, Location location, Class<? extends Entity> type, Vector vector, Consumer<?> consumer) {
return entityData.spawn(location);
}
},
NOT_PROJECTILE_TRIGGER {
@Override
public @Nullable Entity shootHandler(EntityData<?> entityData, LivingEntity shooter, Location location, Class<? extends Entity> type, Vector vector, Consumer<?> consumer) {
//noinspection unchecked,rawtypes
entityData.spawn(location, (Consumer) consumer);
return null;
}
},
PROJECTILE_NO_WORLD_NO_TRIGGER {
@Override
public Entity shootHandler(EntityData<?> entityData, LivingEntity shooter, Location location, Class<? extends Entity> type, Vector vector, Consumer<?> consumer) {
//noinspection unchecked
Projectile projectile = shooter.launchProjectile((Class<? extends Projectile>) type);
set(projectile, entityData);
return projectile;
}
},
PROJECTILE_NO_WORLD_TRIGGER_BUKKIT {
@Override
public @Nullable Entity shootHandler(EntityData<?> entityData, LivingEntity shooter, Location location, Class<? extends Entity> type, Vector vector, Consumer<?> consumer) {
return null;
}
},
PROJECTILE_NO_WORLD_TRIGGER {
@Override
public @Nullable Entity shootHandler(EntityData<?> entityData, LivingEntity shooter, Location location, Class<? extends Entity> type, Vector vector, Consumer<?> consumer) {
//noinspection unchecked,rawtypes
shooter.launchProjectile((Class<? extends Projectile>) type, vector, (Consumer) consumer);
return null;
}
},
PROJECTILE_WORLD_NO_TRIGGER {
@Override
public Entity shootHandler(EntityData<?> entityData, LivingEntity shooter, Location location, Class<? extends Entity> type, Vector vector, Consumer<?> consumer) {
Projectile projectile = (Projectile) shooter.getWorld().spawn(location, type);
projectile.setShooter(shooter);
return projectile;
}
},
PROJECTILE_WORLD_TRIGGER_BUKKIT {
@Override
public @Nullable Entity shootHandler(EntityData<?> entityData, LivingEntity shooter, Location location, Class<? extends Entity> type, Vector vector, Consumer<?> consumer) {
return null;
}
},
PROJECTILE_WORLD_TRIGGER {
@Override
public @Nullable Entity shootHandler(EntityData<?> entityData, LivingEntity shooter, Location location, Class<? extends Entity> type, Vector vector, Consumer<?> consumer) {
//noinspection unchecked,rawtypes
shooter.getWorld().spawn(location, type, (Consumer) consumer);
return null;
}
};
/**
* Handles spawning the entity/projectile based on the conditions of the effect and the server
* @param entityData The {@link EntityData} to be spawned
* @param shooter The {@link LivingEntity} that is being used to shoot {@code entityData} from
* @param location The {@link Location} to spawn the {@code entityData} if {@code shooter} is not to be used
* @param type The {@link Class<org.bukkit.entity.EntityType>} from {@code entityData} used to be spawned if {@code entityData} is not to be used
* @param vector The {@link Vector} to set the vector of the spawned entity from {@code entityData} or {@code type}
* @param consumer The {@link Consumer} to be used when spawning the entity from {@code entityDataa} or {@code type}
* @return The spawned {@link Entity}
*/
public abstract @Nullable Entity shootHandler(EntityData<?> entityData, LivingEntity shooter, Location location, Class<? extends Entity> type, Vector vector, Consumer<?> consumer);
}
public static class ShootEvent extends Event {
private Entity projectile;
private @Nullable LivingEntity shooter;
public ShootEvent(Entity projectile, @Nullable LivingEntity shooter) {
this.projectile = projectile;
this.shooter = shooter;
}
public Entity getProjectile() {
return projectile;
}
public @Nullable LivingEntity getShooter() {
return shooter;
}
@Override
public @NotNull HandlerList getHandlers() {
throw new IllegalStateException();
}
}
private static final boolean RUNNING_PAPER;
// TODO: Remove 'Method's after 1.20.2+ is the minimum version supported
private static Method launchWithBukkitConsumer;
private static Method worldSpawnWithBukkitConsumer;
static {
Skript.registerSection(EffSecShoot.class,
"shoot %entitydatas% [from %livingentities/locations%] [(at|with) (speed|velocity) %-number%] [%-direction%]",
"(make|let) %livingentities/locations% shoot %entitydatas% [(at|with) (speed|velocity) %-number%] [%-direction%]"
);
EventValues.registerEventValue(ShootEvent.class, Entity.class, ShootEvent::getProjectile);
EventValues.registerEventValue(ShootEvent.class, Projectile.class,
shootEvent -> shootEvent.getProjectile() instanceof Projectile projectile ? projectile : null);
if (!Skript.isRunningMinecraft(1, 20, 2)) {
try {
launchWithBukkitConsumer = LivingEntity.class.getMethod("launchProjectile", Class.class, Vector.class, org.bukkit.util.Consumer.class);
} catch (NoSuchMethodException ignored) {}
try {
worldSpawnWithBukkitConsumer = World.class.getMethod("spawn", Location.class, Class.class, org.bukkit.util.Consumer.class);
} catch (NoSuchMethodException ignored) {}
}
boolean launchHasJavaConsumer = Skript.methodExists(LivingEntity.class, "launchProjectile", Class.class, Vector.class, Consumer.class);
RUNNING_PAPER = launchWithBukkitConsumer != null || launchHasJavaConsumer;
}
private final static Double DEFAULT_SPEED = 5.;
private Expression<EntityData<?>> types;
private Expression<?> shooters;
private @Nullable Expression<Number> velocity;
private @Nullable Expression<Direction> direction;
public static Entity lastSpawned = null;
private @Nullable Trigger trigger;
@Override
public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult, @Nullable SectionNode sectionNode, @Nullable List<TriggerItem> triggerItems) {
//noinspection unchecked
types = (Expression<EntityData<?>>) exprs[matchedPattern];
shooters = exprs[1 - matchedPattern];
//noinspection unchecked
velocity = (Expression<Number>) exprs[2];
//noinspection unchecked
direction = (Expression<Direction>) exprs[3];
if (sectionNode != null) {
AtomicBoolean delayed = new AtomicBoolean(false);
Runnable afterLoading = () -> delayed.set(!getParser().getHasDelayBefore().isFalse());
trigger = loadCode(sectionNode, "shoot", afterLoading, ShootEvent.class);
if (delayed.get()) {
Skript.error("Delays cannot be used within a 'shoot' effect section");
return false;
}
}
return true;
}
@Override
protected @Nullable TriggerItem walk(Event event) {
lastSpawned = null;
Number finalVelocity = velocity != null ? velocity.getSingle(event) : DEFAULT_SPEED;
Direction finalDirection = direction != null ? direction.getSingle(event) : Direction.IDENTITY;
if (finalVelocity == null || finalDirection == null)
return null;
EntityData<?>[] data = types.getArray(event);
for (Object shooter : shooters.getArray(event)) {
for (EntityData<?> entityData : data) {
Entity finalProjectile = null;
Vector vector;
if (shooter instanceof LivingEntity livingShooter) {
vector = finalDirection.getDirection(livingShooter.getLocation()).multiply(finalVelocity.doubleValue());
//noinspection rawtypes
Consumer afterSpawn = afterSpawn(event, entityData, livingShooter, vector);
Class<? extends Entity> type = entityData.getType();
Location shooterLoc = livingShooter.getLocation();
shooterLoc.setY(shooterLoc.getY() + livingShooter.getEyeHeight() / 2);
boolean isProjectile = false, useWorldSpawn = false;
if (Fireball.class.isAssignableFrom(type)) {
shooterLoc = livingShooter.getEyeLocation().add(vector.clone().normalize().multiply(0.5));
isProjectile = true;
useWorldSpawn = true;
} else if (Projectile.class.isAssignableFrom(type)) {
isProjectile = true;
if (trigger != null && !RUNNING_PAPER) {
useWorldSpawn = true;
}
}
CaseUsage caseUsage = getCaseUsage(isProjectile, useWorldSpawn, trigger != null);
if (caseUsage == CaseUsage.PROJECTILE_NO_WORLD_TRIGGER_BUKKIT) {
try {
//noinspection removal
launchWithBukkitConsumer.invoke(livingShooter, type, vector, (org.bukkit.util.Consumer<? extends Entity>) afterSpawn::accept);
} catch (Exception ignored) {}
} else if (caseUsage == CaseUsage.PROJECTILE_WORLD_TRIGGER_BUKKIT) {
try {
//noinspection removal
worldSpawnWithBukkitConsumer.invoke(livingShooter.getWorld(), type, (org.bukkit.util.Consumer<? extends Entity>) afterSpawn::accept);
} catch (Exception ignored) {}
} else {
finalProjectile = caseUsage.shootHandler(entityData, livingShooter, shooterLoc, type, vector, afterSpawn);
}
} else {
vector = finalDirection.getDirection((Location) shooter).multiply(finalVelocity.doubleValue());
if (trigger != null) {
//noinspection unchecked,rawtypes
entityData.spawn((Location) shooter, (Consumer) afterSpawn(event, entityData, null, vector));
} else {
finalProjectile = entityData.spawn((Location) shooter);
}
}
if (finalProjectile != null) {
finalProjectile.setVelocity(vector);
lastSpawned = finalProjectile;
}
}
}
return super.walk(event, false);
}
@Override
public String toString(@Nullable Event event, boolean debug) {
return "shoot " + types.toString(event, debug) + " from " + shooters.toString(event, debug)
+ (velocity != null ? " at speed " + velocity.toString(event, debug) : "")
+ (direction != null ? " " + direction.toString(event, debug) : "");
}
private static <E extends Entity> void set(Entity entity, EntityData<E> entityData) {
//noinspection unchecked
entityData.set((E) entity);
}
private CaseUsage getCaseUsage(Boolean isProjectile, Boolean useWorldSpawn, Boolean hasTrigger) {
if (!isProjectile) {
if (!hasTrigger)
return CaseUsage.NOT_PROJECTILE_NO_TRIGGER;
return CaseUsage.NOT_PROJECTILE_TRIGGER;
}
if (!useWorldSpawn) {
if (!hasTrigger)
return CaseUsage.PROJECTILE_NO_WORLD_NO_TRIGGER;
if (launchWithBukkitConsumer != null)
return CaseUsage.PROJECTILE_NO_WORLD_TRIGGER_BUKKIT;
return CaseUsage.PROJECTILE_NO_WORLD_TRIGGER;
}
if (!hasTrigger)
return CaseUsage.PROJECTILE_WORLD_NO_TRIGGER;
if (worldSpawnWithBukkitConsumer != null)
return CaseUsage.PROJECTILE_WORLD_TRIGGER_BUKKIT;
return CaseUsage.PROJECTILE_WORLD_TRIGGER;
}
private Consumer<? extends Entity> afterSpawn(Event event, EntityData<?> entityData, @Nullable LivingEntity shooter, Vector vector) {
return entity -> {
entity.setVelocity(vector);
if (entity instanceof Fireball fireball)
fireball.setShooter(shooter);
else if (entity instanceof Projectile projectile) {
projectile.setShooter(shooter);
set(projectile, entityData);
}
ShootEvent shootEvent = new ShootEvent(entity, shooter);
lastSpawned = entity;
Variables.withLocalVariables(event, shootEvent, () -> TriggerItem.walk(trigger, shootEvent));
};
}
}