From 9df3e31aec269897c2fc77d29b7dd479ff638cbd Mon Sep 17 00:00:00 2001
From: sovde <10354869+sovdeeth@users.noreply.github.com>
Date: Fri, 23 Feb 2024 22:56:47 +0100
Subject: [PATCH 01/16] Initial Planning
---
.../bukkit/particles/ParticleEffect.java | 61 +++++++++++++++++++
.../skript/bukkit/particles/PlayerEffect.java | 26 ++++++++
.../elements/effects/EffPlayEffect.java | 13 ++++
.../expressions/ExprParticleEffect.java | 21 +++++++
.../expressions/ExprPlayerEffect.java | 6 ++
5 files changed, 127 insertions(+)
create mode 100644 src/main/java/org/skriptlang/skript/bukkit/particles/ParticleEffect.java
create mode 100644 src/main/java/org/skriptlang/skript/bukkit/particles/PlayerEffect.java
create mode 100644 src/main/java/org/skriptlang/skript/bukkit/particles/elements/effects/EffPlayEffect.java
create mode 100644 src/main/java/org/skriptlang/skript/bukkit/particles/elements/expressions/ExprParticleEffect.java
create mode 100644 src/main/java/org/skriptlang/skript/bukkit/particles/elements/expressions/ExprPlayerEffect.java
diff --git a/src/main/java/org/skriptlang/skript/bukkit/particles/ParticleEffect.java b/src/main/java/org/skriptlang/skript/bukkit/particles/ParticleEffect.java
new file mode 100644
index 00000000000..820f7b85357
--- /dev/null
+++ b/src/main/java/org/skriptlang/skript/bukkit/particles/ParticleEffect.java
@@ -0,0 +1,61 @@
+package org.skriptlang.skript.bukkit.particles;
+
+
+import org.bukkit.Particle;
+import org.bukkit.util.Vector;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * A class to hold particle metadata prior to spawning
+ */
+public class ParticleEffect {
+
+ /**
+ * The base {@link Particle} to use. This determines the properties and what data this {@link ParticleEffect} can accept.
+ */
+ private Particle particle;
+
+ /**
+ * This determines how many particles to spawn with the given properties. If set to 0, {@link ParticleEffect#offset} may
+ * be used to determine things like colour or velocity, rather than the area where the particles spawn.
+ */
+ private int count;
+
+ /**
+ * This, by default, determines a bounding box around the spawn location in which particles are randomly offset.
+ * Dimensions are multiplied by roughly 8, meaning an offset of (1, 1, 1) results in particles spawning in an
+ * 8x8x8 cuboid centered on the spawn location.
+ * Particles are distributed following a Gaussian distribution, clustering towards the center.
+ *
+ * When {@link ParticleEffect#count} is 0, however, this may instead act as a velocity vector, an RGB colour,
+ * or determine the colour of a note particle.
+ * See the wiki on the particle command for more information.
+ */
+ private Vector offset;
+
+ /**
+ * This, by default, determines the speed at which a particle moves. It must be positive.
+ *
+ * When {@link ParticleEffect#count} is 0, this instead acts as a multiplier to the velocity provided by {@link ParticleEffect#offset},
+ * or if {@link ParticleEffect#particle} is {@link Particle#SPELL_MOB_AMBIENT} or {@link Particle#SPELL_MOB}, then
+ * this acts as an exponent to the RGB value provided by {@link ParticleEffect#offset}.
+ */
+ private float extra;
+
+ /**
+ * This field contains extra data that some particles require. For example, {@link Particle#REDSTONE} requires {@link org.bukkit.Particle.DustOptions}
+ * to determine its size and colour.
+ */
+ @Nullable
+ private Object data;
+
+ ParticleEffect(Particle particle) {
+ this.particle = particle;
+ this.count = 1;
+ this.extra = 0;
+ this.offset = new Vector(0,0,0);
+ }
+
+ // TODO: Add parent interface for ParticleEffect, PlayerEffect, EntityEffect? Would make spawning easier, maybe.
+ // TODO: add getters, setters, maybe builder class? Add spawn method.
+}
diff --git a/src/main/java/org/skriptlang/skript/bukkit/particles/PlayerEffect.java b/src/main/java/org/skriptlang/skript/bukkit/particles/PlayerEffect.java
new file mode 100644
index 00000000000..d83c3c42a81
--- /dev/null
+++ b/src/main/java/org/skriptlang/skript/bukkit/particles/PlayerEffect.java
@@ -0,0 +1,26 @@
+package org.skriptlang.skript.bukkit.particles;
+
+import org.bukkit.Effect;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * A class to hold metadata about {@link org.bukkit.Effect}s before playing.
+ */
+public class PlayerEffect {
+ /**
+ * The {@link Effect} that this object represents
+ */
+ private Effect effect;
+
+ /**
+ * The optional extra data that some {@link Effect}s require.
+ */
+ @Nullable
+ private Object data;
+
+ PlayerEffect(Effect effect) {
+ this.effect = effect;
+ }
+
+ // TODO: add getters, setters, maybe builder class? Add spawn method.
+}
diff --git a/src/main/java/org/skriptlang/skript/bukkit/particles/elements/effects/EffPlayEffect.java b/src/main/java/org/skriptlang/skript/bukkit/particles/elements/effects/EffPlayEffect.java
new file mode 100644
index 00000000000..f9d23ef1d00
--- /dev/null
+++ b/src/main/java/org/skriptlang/skript/bukkit/particles/elements/effects/EffPlayEffect.java
@@ -0,0 +1,13 @@
+package org.skriptlang.skript.bukkit.particles.elements.effects;
+
+import ch.njol.skript.Skript;
+import ch.njol.skript.lang.Effect;
+
+// TODO: better terminology than "effects", as it's getting confusing.
+public class EffPlayEffect extends Effect {
+ static {
+ Skript.registerEffect(EffPlayEffect.class,
+ "[:force] (play|show|draw) [%number% [of]] %particles/player effects% (on|%directions%) %entities/locations% [(to %players%|in [a] (radius|range) of %number%)]",
+ "(play|show|draw) [%number% [of]] %particles/player effects/entity effects% (on|at) %entities%)");
+ }
+}
diff --git a/src/main/java/org/skriptlang/skript/bukkit/particles/elements/expressions/ExprParticleEffect.java b/src/main/java/org/skriptlang/skript/bukkit/particles/elements/expressions/ExprParticleEffect.java
new file mode 100644
index 00000000000..78d1d8e32b4
--- /dev/null
+++ b/src/main/java/org/skriptlang/skript/bukkit/particles/elements/expressions/ExprParticleEffect.java
@@ -0,0 +1,21 @@
+package org.skriptlang.skript.bukkit.particles.elements.expressions;
+
+import ch.njol.skript.lang.util.SimpleExpression;
+import org.skriptlang.skript.bukkit.particles.ParticleEffect;
+
+public class ExprParticleEffect extends SimpleExpression {
+ // TODO:
+ // Syntax:
+ // count + (name + "particle" + data) + offset + extra
+ // # count:
+ // %number% [of]
+ // # offset:
+ // [with [an]] offset (of|by) ((%number%, %number%(,|[,] and) %number%)|%vector%)
+ // # extra:
+ // [(at|with) [a]] (speed|extra [value]) [of] %number%
+ // This expression should handle the common elements between all particles
+ // Specific data should be handled by something more dynamic, since data can vary wildly.
+ // Consider VisualEffect approach, or SkBee approach of various functions.
+ // Prefer something like VisualEffect for better readability + grammar, but needs to be
+ // better documented this time.
+}
diff --git a/src/main/java/org/skriptlang/skript/bukkit/particles/elements/expressions/ExprPlayerEffect.java b/src/main/java/org/skriptlang/skript/bukkit/particles/elements/expressions/ExprPlayerEffect.java
new file mode 100644
index 00000000000..6cea0959a1f
--- /dev/null
+++ b/src/main/java/org/skriptlang/skript/bukkit/particles/elements/expressions/ExprPlayerEffect.java
@@ -0,0 +1,6 @@
+package org.skriptlang.skript.bukkit.particles.elements.expressions;
+
+public class ExprPlayerEffect {
+ // TODO: This class is for Bukkit's Effect enum, or at least the options that require data.
+ // Syntax unsure, since it still need to be dynamic like particles
+}
From 47ad2802118543b4d4dd34d44a9bee1b54de627b Mon Sep 17 00:00:00 2001
From: sovde <10354869+sovdeeth@users.noreply.github.com>
Date: Sat, 24 Feb 2024 19:45:45 +0100
Subject: [PATCH 02/16] rename to GameEffect, create syntaxes for those that
need data.
---
.../{PlayerEffect.java => GameEffect.java} | 4 +--
.../elements/effects/EffPlayEffect.java | 4 +--
.../elements/expressions/ExprGameEffect.java | 29 +++++++++++++++++++
.../expressions/ExprPlayerEffect.java | 6 ----
4 files changed, 33 insertions(+), 10 deletions(-)
rename src/main/java/org/skriptlang/skript/bukkit/particles/{PlayerEffect.java => GameEffect.java} (89%)
create mode 100644 src/main/java/org/skriptlang/skript/bukkit/particles/elements/expressions/ExprGameEffect.java
delete mode 100644 src/main/java/org/skriptlang/skript/bukkit/particles/elements/expressions/ExprPlayerEffect.java
diff --git a/src/main/java/org/skriptlang/skript/bukkit/particles/PlayerEffect.java b/src/main/java/org/skriptlang/skript/bukkit/particles/GameEffect.java
similarity index 89%
rename from src/main/java/org/skriptlang/skript/bukkit/particles/PlayerEffect.java
rename to src/main/java/org/skriptlang/skript/bukkit/particles/GameEffect.java
index d83c3c42a81..a6e440dabe7 100644
--- a/src/main/java/org/skriptlang/skript/bukkit/particles/PlayerEffect.java
+++ b/src/main/java/org/skriptlang/skript/bukkit/particles/GameEffect.java
@@ -6,7 +6,7 @@
/**
* A class to hold metadata about {@link org.bukkit.Effect}s before playing.
*/
-public class PlayerEffect {
+public class GameEffect {
/**
* The {@link Effect} that this object represents
*/
@@ -18,7 +18,7 @@ public class PlayerEffect {
@Nullable
private Object data;
- PlayerEffect(Effect effect) {
+ GameEffect(Effect effect) {
this.effect = effect;
}
diff --git a/src/main/java/org/skriptlang/skript/bukkit/particles/elements/effects/EffPlayEffect.java b/src/main/java/org/skriptlang/skript/bukkit/particles/elements/effects/EffPlayEffect.java
index f9d23ef1d00..1f521da8c99 100644
--- a/src/main/java/org/skriptlang/skript/bukkit/particles/elements/effects/EffPlayEffect.java
+++ b/src/main/java/org/skriptlang/skript/bukkit/particles/elements/effects/EffPlayEffect.java
@@ -7,7 +7,7 @@
public class EffPlayEffect extends Effect {
static {
Skript.registerEffect(EffPlayEffect.class,
- "[:force] (play|show|draw) [%number% [of]] %particles/player effects% (on|%directions%) %entities/locations% [(to %players%|in [a] (radius|range) of %number%)]",
- "(play|show|draw) [%number% [of]] %particles/player effects/entity effects% (on|at) %entities%)");
+ "[:force] (play|show|draw) [%number% [of]] %particles/game effects% (on|%directions%) %entities/locations% [(to %players%|in [a] (radius|range) of %number%)]",
+ "(play|show|draw) [%number% [of]] %particles/game effects/entity effects% (on|at) %entities%)");
}
}
diff --git a/src/main/java/org/skriptlang/skript/bukkit/particles/elements/expressions/ExprGameEffect.java b/src/main/java/org/skriptlang/skript/bukkit/particles/elements/expressions/ExprGameEffect.java
new file mode 100644
index 00000000000..51680e32c2e
--- /dev/null
+++ b/src/main/java/org/skriptlang/skript/bukkit/particles/elements/expressions/ExprGameEffect.java
@@ -0,0 +1,29 @@
+package org.skriptlang.skript.bukkit.particles.elements.expressions;
+
+import ch.njol.skript.Skript;
+import ch.njol.skript.lang.ExpressionType;
+import ch.njol.skript.lang.util.SimpleExpression;
+import org.skriptlang.skript.bukkit.particles.GameEffect;
+
+public class ExprGameEffect extends SimpleExpression {
+ static {
+ // TODO: register the rest via the type parser
+ // make sure to not parse the ones with data, so this class can handle it
+ Skript.registerExpression(ExprGameEffect.class, GameEffect.class, ExpressionType.COMBINED,
+ "[record] song (of|using) %item%",
+ "[dispenser] [black|:white] smoke effect [(in|with|using) direction] %direction/vector%",
+ "[foot]step sound [effect] (on|of|using) %item/blockdata%",
+ "[:instant] [splash] potion break effect (with|of|using) [colour] %color%", // paper changes this type
+ "composter fill[ing] (succe[ss|ed]|fail:fail[ure]) sound [effect]",
+ "villager plant grow[th] effect [(with|using) %number% particles]",
+ "[fake] bone meal effect [(with|using) %number% particles]",
+ // post copper update (1.19?)
+ "(electric|lightning[ rod]|copper) spark effect [(in|using) the (1:x|2:y|3:z) axis]",
+ // paper only
+ "sculk (charge|spread) effect [(with|using) data %number%]",
+ "[finish] brush[ing] effect (with|using) %item/blockdata%",
+ // 1.20.3
+ "trial spawner detect[ing|s] [%number%] player[s] effect"
+ );
+ }
+}
diff --git a/src/main/java/org/skriptlang/skript/bukkit/particles/elements/expressions/ExprPlayerEffect.java b/src/main/java/org/skriptlang/skript/bukkit/particles/elements/expressions/ExprPlayerEffect.java
deleted file mode 100644
index 6cea0959a1f..00000000000
--- a/src/main/java/org/skriptlang/skript/bukkit/particles/elements/expressions/ExprPlayerEffect.java
+++ /dev/null
@@ -1,6 +0,0 @@
-package org.skriptlang.skript.bukkit.particles.elements.expressions;
-
-public class ExprPlayerEffect {
- // TODO: This class is for Bukkit's Effect enum, or at least the options that require data.
- // Syntax unsure, since it still need to be dynamic like particles
-}
From 217ad6021df17afb288b850005764003e0a6b83a Mon Sep 17 00:00:00 2001
From: sovde <10354869+sovdeeth@users.noreply.github.com>
Date: Mon, 10 Jun 2024 18:49:08 +0200
Subject: [PATCH 03/16] begin implementing GameEffects
---
src/main/java/ch/njol/util/VectorMath.java | 21 ++++
.../skript/bukkit/particles/GameEffect.java | 53 ++++++++-
.../bukkit/particles/elements/Types.java | 83 +++++++++++++
.../elements/expressions/ExprGameEffect.java | 110 +++++++++++++++---
src/main/resources/lang/default.lang | 87 ++++++++++++++
5 files changed, 335 insertions(+), 19 deletions(-)
create mode 100644 src/main/java/org/skriptlang/skript/bukkit/particles/elements/Types.java
diff --git a/src/main/java/ch/njol/util/VectorMath.java b/src/main/java/ch/njol/util/VectorMath.java
index fca9785dc6a..e38027c95d7 100644
--- a/src/main/java/ch/njol/util/VectorMath.java
+++ b/src/main/java/ch/njol/util/VectorMath.java
@@ -18,6 +18,7 @@
*/
package ch.njol.util;
+import org.bukkit.block.BlockFace;
import org.bukkit.util.Vector;
/**
@@ -191,6 +192,26 @@ public static float wrapAngleDeg(float angle) {
}
}
+ /**
+ * Calculates the nearest {@link BlockFace} to an arbitrary unit {@link Vector}.
+ *
+ * @param vector a normalized vector
+ * @return the block face most closely aligned to the input vector
+ */
+ public static BlockFace toNearestBlockFace(Vector vector) {
+ double maxDot = -1;
+ double dot;
+ BlockFace nearest = BlockFace.NORTH;
+ for (BlockFace face : BlockFace.values()){
+ dot = face.getDirection().dot(vector);
+ if (dot > maxDot) {
+ maxDot = dot;
+ nearest = face;
+ }
+ }
+ return nearest;
+ }
+
/**
* Copies vector components of {@code vector2} into {@code vector1}.
*/
diff --git a/src/main/java/org/skriptlang/skript/bukkit/particles/GameEffect.java b/src/main/java/org/skriptlang/skript/bukkit/particles/GameEffect.java
index a6e440dabe7..143247dd977 100644
--- a/src/main/java/org/skriptlang/skript/bukkit/particles/GameEffect.java
+++ b/src/main/java/org/skriptlang/skript/bukkit/particles/GameEffect.java
@@ -1,16 +1,23 @@
package org.skriptlang.skript.bukkit.particles;
+import ch.njol.skript.util.EnumUtils;
import org.bukkit.Effect;
import org.jetbrains.annotations.Nullable;
+import java.util.Arrays;
+import java.util.Locale;
+
/**
* A class to hold metadata about {@link org.bukkit.Effect}s before playing.
*/
public class GameEffect {
+
+ public static final EnumUtils ENUM_UTILS = new EnumUtils<>(Effect.class, "game effect");
+
/**
* The {@link Effect} that this object represents
*/
- private Effect effect;
+ private final Effect effect;
/**
* The optional extra data that some {@link Effect}s require.
@@ -18,9 +25,49 @@ public class GameEffect {
@Nullable
private Object data;
- GameEffect(Effect effect) {
+ public GameEffect(Effect effect) {
this.effect = effect;
}
- // TODO: add getters, setters, maybe builder class? Add spawn method.
+ public static GameEffect parse(String input) {
+ Effect effect = ENUM_UTILS.parse(input.toLowerCase(Locale.ENGLISH));
+ if (effect == null || effect.getData() != null) {
+ return null;
+ }
+ return new GameEffect(effect);
+ }
+
+ public Effect getEffect() {
+ return effect;
+ }
+
+ @Nullable
+ public Object getData() {
+ return data;
+ }
+
+ public boolean setData(Object data) {
+ if (effect.getData() != null && effect.getData().isInstance(data)) {
+ this.data = data;
+ return true;
+ }
+ return false;
+ }
+
+ public String toString(int flags) {
+ if (effect.getData() != null)
+ return ENUM_UTILS.toString(getEffect(), flags);
+ return toString();
+ }
+
+ static final String[] namesWithoutData = (String[]) Arrays.stream(Effect.values())
+ .filter(effect -> effect.getData() == null)
+ .map(Enum::name)
+ .toArray();
+ public static String[] getAllNamesWithoutData(){
+ return namesWithoutData.clone();
+ }
+
+
+// TODO: add getters, setters, maybe builder class? Add spawn method.
}
diff --git a/src/main/java/org/skriptlang/skript/bukkit/particles/elements/Types.java b/src/main/java/org/skriptlang/skript/bukkit/particles/elements/Types.java
new file mode 100644
index 00000000000..ad2aed9e6f2
--- /dev/null
+++ b/src/main/java/org/skriptlang/skript/bukkit/particles/elements/Types.java
@@ -0,0 +1,83 @@
+package org.skriptlang.skript.bukkit.particles.elements;
+
+import ch.njol.skript.classes.ClassInfo;
+import ch.njol.skript.classes.Parser;
+import ch.njol.skript.classes.Serializer;
+import ch.njol.skript.expressions.base.EventValueExpression;
+import ch.njol.skript.lang.ParseContext;
+import ch.njol.skript.registrations.Classes;
+import ch.njol.yggdrasil.Fields;
+import org.bukkit.Effect;
+import org.eclipse.jdt.annotation.Nullable;
+import org.skriptlang.skript.bukkit.particles.GameEffect;
+
+import java.io.NotSerializableException;
+import java.io.StreamCorruptedException;
+
+public class Types {
+ static {
+
+ Classes.registerClass(new ClassInfo<>(GameEffect.class, "gameeffect")
+ .user("game ?effects?")
+ .since("INSERT VERSION")
+ .description("Various game effects that can be played for players, like record disc songs, splash potions breaking, or fake bone meal effects.")
+ .name("Game Effect")
+ .usage(GameEffect.getAllNamesWithoutData())
+ .serializer(new Serializer<>() {
+ @Override
+ public Fields serialize(GameEffect effect) {
+ Fields fields = new Fields();
+ fields.putPrimitive("name", effect.getEffect().name());
+ fields.putObject("data", effect.getData());
+ return fields;
+ }
+
+ @Override
+ public void deserialize(GameEffect effect, Fields fields) throws StreamCorruptedException, NotSerializableException {
+ assert false;
+ }
+
+ @Override
+ protected GameEffect deserialize(Fields fields) throws StreamCorruptedException, NotSerializableException {
+ String name = fields.getAndRemovePrimitive("name", String.class);
+ GameEffect effect;
+ try {
+ effect = new GameEffect(Effect.valueOf(name));
+ } catch (IllegalArgumentException e) {
+ return null;
+ }
+ effect.setData(fields.getObject("data"));
+ return effect;
+ }
+
+ @Override
+ public boolean mustSyncDeserialization() {
+ return false;
+ }
+
+ @Override
+ protected boolean canBeInstantiated() {
+ return false;
+ }
+ })
+ .defaultExpression(new EventValueExpression<>(GameEffect.class))
+ .parser(new Parser<>() {
+ @Override
+ @Nullable
+ public GameEffect parse(String input, ParseContext context) {
+ return GameEffect.parse(input);
+ }
+
+ @Override
+ public String toString(GameEffect effect, int flags) {
+ return effect.toString(flags);
+ }
+
+ @Override
+ public String toVariableNameString(GameEffect o) {
+ return o.getEffect().name();
+ }
+ }));
+
+ }
+}
diff --git a/src/main/java/org/skriptlang/skript/bukkit/particles/elements/expressions/ExprGameEffect.java b/src/main/java/org/skriptlang/skript/bukkit/particles/elements/expressions/ExprGameEffect.java
index 51680e32c2e..1a81225b7f3 100644
--- a/src/main/java/org/skriptlang/skript/bukkit/particles/elements/expressions/ExprGameEffect.java
+++ b/src/main/java/org/skriptlang/skript/bukkit/particles/elements/expressions/ExprGameEffect.java
@@ -1,29 +1,107 @@
package org.skriptlang.skript.bukkit.particles.elements.expressions;
import ch.njol.skript.Skript;
+import ch.njol.skript.lang.Expression;
import ch.njol.skript.lang.ExpressionType;
+import ch.njol.skript.lang.SkriptParser;
import ch.njol.skript.lang.util.SimpleExpression;
+import ch.njol.skript.util.Direction;
+import ch.njol.skript.util.Patterns;
+import ch.njol.util.Kleenean;
+import ch.njol.util.VectorMath;
+import org.bukkit.Effect;
+import org.bukkit.block.BlockFace;
+import org.bukkit.event.Event;
+import org.bukkit.util.Vector;
+import org.jetbrains.annotations.Nullable;
import org.skriptlang.skript.bukkit.particles.GameEffect;
public class ExprGameEffect extends SimpleExpression {
+
+ private static final Patterns PATTERNS = new Patterns<>(new Object[][]{
+ {"[record] song (of|using) %item%", Effect.RECORD_PLAY}, // shows the action bar too!
+ {"[dispenser] [black|1:white] smoke effect [(in|with|using) direction] %direction/vector%", Effect.SMOKE},
+ {"[foot]step sound [effect] (on|of|using) %item/blockdata%", Effect.STEP_SOUND},
+ {"[:instant] [splash] potion break effect (with|of|using) [colour] %color%", Effect.POTION_BREAK}, // paper changes this type
+ {"composter fill[ing] (succe[ss|ed]|1:fail[ure]) sound [effect]", Effect.COMPOSTER_FILL_ATTEMPT},
+ {"villager plant grow[th] effect [(with|using) %number% particles]", Effect.VILLAGER_PLANT_GROW},
+ {"[fake] bone meal effect [(with|using) %number% particles]", Effect.BONE_MEAL_USE},
+ // post copper update (1.19?)
+ {"(electric|lightning[ rod]|copper) spark effect [(in|using) the (1:x|2:y|3:z) axis]", Effect.ELECTRIC_SPARK},
+ // paper only
+ {"sculk (charge|spread) effect [(with|using) data %number%]", Effect.PARTICLES_SCULK_CHARGE}, // data explanation here https://discord.com/channels/135877399391764480/836220422223036467/1211040434852208660
+ {"[finish] brush[ing] effect (with|using) %item/blockdata%", Effect.PARTICLES_AND_SOUND_BRUSH_BLOCK_COMPLETE},
+ // 1.20.3
+ {"trial spawner detect[ing|s] [%number%] player[s] effect", Effect.TRIAL_SPAWNER_DETECT_PLAYER}
+ });
+
static {
// TODO: register the rest via the type parser
// make sure to not parse the ones with data, so this class can handle it
- Skript.registerExpression(ExprGameEffect.class, GameEffect.class, ExpressionType.COMBINED,
- "[record] song (of|using) %item%",
- "[dispenser] [black|:white] smoke effect [(in|with|using) direction] %direction/vector%",
- "[foot]step sound [effect] (on|of|using) %item/blockdata%",
- "[:instant] [splash] potion break effect (with|of|using) [colour] %color%", // paper changes this type
- "composter fill[ing] (succe[ss|ed]|fail:fail[ure]) sound [effect]",
- "villager plant grow[th] effect [(with|using) %number% particles]",
- "[fake] bone meal effect [(with|using) %number% particles]",
- // post copper update (1.19?)
- "(electric|lightning[ rod]|copper) spark effect [(in|using) the (1:x|2:y|3:z) axis]",
- // paper only
- "sculk (charge|spread) effect [(with|using) data %number%]",
- "[finish] brush[ing] effect (with|using) %item/blockdata%",
- // 1.20.3
- "trial spawner detect[ing|s] [%number%] player[s] effect"
- );
+ Skript.registerExpression(ExprGameEffect.class, GameEffect.class, ExpressionType.COMBINED, PATTERNS.getPatterns());
+ }
+
+ private GameEffect gameEffect;
+ private Expression> data;
+ private int variant;
+
+
+ @Override
+ public boolean init(Expression>[] expressions, int matchedPattern, Kleenean isDelayed, SkriptParser.ParseResult parseResult) {
+ gameEffect = new GameEffect(PATTERNS.getInfo(matchedPattern));
+ variant = parseResult.mark;
+ if (expressions.length > 0)
+ data = expressions[0];
+ return true;
+ }
+
+ @Override
+ protected GameEffect @Nullable [] get(Event event) {
+ switch (gameEffect.getEffect()) {
+ case SMOKE:
+ return handleSmokeEffects(gameEffect, variant, data.getSingle(event));
+ default:
+ return setData(gameEffect, data.getSingle(event));
+ }
+ }
+
+ private GameEffect @Nullable [] handleSmokeEffects(GameEffect gameEffect, int variant, Object data) {
+ BlockFace blockFace;
+ if (data instanceof Vector) {
+ Vector vector = ((Vector) data);
+ if (vector.isZero())
+ return new GameEffect[0];
+ blockFace = VectorMath.toNearestBlockFace(vector.normalize());
+ } else if (data instanceof Direction) {
+ Vector vector = ((Direction) data).getDirection();
+ // what about relative directions
+ }
+ if (variant == 1)
+ gameEffect = new GameEffect(Effect.SHOOT_WHITE_SMOKE);
+ return setData(gameEffect, data);
+ }
+
+ private GameEffect @Nullable [] setData(GameEffect gameEffect, Object data){
+ if (data == null)
+ return new GameEffect[0]; // invalid data, must return nothing.
+ boolean success = gameEffect.setData(data);
+ if (!success)
+ return new GameEffect[0]; // invalid data
+ return new GameEffect[]{gameEffect};
+ }
+
+ @Override
+ public boolean isSingle() {
+ return false;
+ }
+
+ @Override
+ public Class extends GameEffect> getReturnType() {
+ return null;
+ }
+
+ @Override
+ public String toString(@Nullable Event event, boolean debug) {
+ return null;
}
}
diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang
index efd0651babf..8275baeed5a 100644
--- a/src/main/resources/lang/default.lang
+++ b/src/main/resources/lang/default.lang
@@ -1980,6 +1980,93 @@ transform reasons:
unknown: unknown
infection: infection, villager infection
+# -- Game Effects --
+game effects:
+ click1:
+ name: dispenser click sound
+ pattern:
+ enums: click1
+ click2: menu click sound, gui click sound
+ bow_fire: fire bow sound, shoot bow sound
+ extinguish: extinguish fire sound, extinguish sound
+ ghast_shriek: ghast shriek sound
+ ghast_shoot: ghast shoot sound
+ blaze_shoot: blaze shoot sound
+ zombie_chew_wooden_door: zombie chewing on wooden door sound, zombie attacking wooden door sound
+ zombie_chew_iron_door: zombie chewing on iron door sound, zombie attacking iron door sound
+ zombie_destroy_door: zombie destroy door sound, zombie destroying door sound
+ ender_signal: ender eye break, eye of ender break, ender eye breaking, eye of ender breaking
+ mobspawner_flames: mob spawner flames, spawner flames
+ brewing_stand_brew: brewing sound, brewing stand sound
+ chorus_flower_grow: chorus flower growing sound, chorus plant growing sound
+ chorus_flower_death: chorus flower death sound
+ portal_travel: nether portal travel sound
+ endereye_launch: ender eye launch sound, eye of ender launch sound
+ firework_shoot: dispenser shoot sound
+ dragon_breath: dragon breath, dragon's breath
+ anvil_break: break anvil sound
+ anvil_use: use anvil sound
+ anvil_land: anvil land sound
+ enderdragon_shoot: ender dragon shoot sound, dragon shoot sound
+ enderdragon_growl: ender dragon growl sound, dragon growl sound
+ wither_break_block: wither breaking block sound
+ wither_shoot: wither shoot sound
+ zombie_infect: zombie infection sound
+ zombie_converted_villager: zombie converting villager sound
+ zombie_converted_to_drowned: zombie conversion to drowned sound
+ zombie_converts_to_drowned: zombie conversion to drowned sound
+ husk_converted_to_zombie: husk conversion to zombie sound
+ husk_converts_to_zombie: husk conversion to zombie sound
+ skeleton_converted_to_stray: skeleton conversion to stray sound
+ bat_takeoff: bat take off sound, bat take flight sound
+ end_gateway_spawn: end gateway spawn, new end gateway
+ phantom_bite: phantom bite sound
+ phantom_bites: phantom bites sound
+ grindstone_use: use grindstone sound
+ grindstone_used: used grindstone sound
+ book_page_turn: book page turn sound, turn page sound
+ book_page_turned: book page turned sound, turned page sound
+ smithing_table_use: use smithing table sound
+ pointed_dripstone_drip_lava_into_cauldron: lava dripping into cauldron sound
+ pointed_dripstone_drip_water_into_cauldron: water dripping into cauldron sound
+ dripping_dripstone: water dripping sound, lava dripping sound
+ lava_interact: lava interaction
+ lava_converts_block: lava converts block
+ redstone_torch_burnout: redstone touch burnout
+ redstone_torch_burns_out: redstone touch burns out
+ end_portal_frame_fill: fill end portal frame
+ ender_eye_placed: ender eye placed
+ ender_dragon_destroy_block: ender dragon breaking block, dragon breaking block
+ ender_dragon_destroys_block: ender dragon destroys block, dragon destroys block
+ sponge_dry: sponge dry, sponge dry out
+ copper_wax_on: apply wax, wax on
+ copper_wax_off: remove wax, wax off
+ oxidised_copper_scrape: scrape oxidised copper, scrape copper
+ wither_spawned: spawn wither sound
+ ender_dragon_death: ender dragon death sound, dragon death sound
+ end_portal_created_in_overworld: complete end portal sound, create end portal sound
+ composter_composts: composter compost
+
+
+
+
+
+
+
+
+ # these effect are deprecated in 1.19, they do nothing.
+ # their respective Sounds should be used instead.
+ door_toggle: toggle door sound
+ iron_door_toggle: toggle iron door sound
+ trapdoor_toggle: toggle trapdoor sound
+ iron_trapdoor_toggle: toggle iron trapdoor sound
+ fence_gate_toggle: toggle fence gate sound
+ door_close: close door sound
+ iron_door_close: close iron door sound
+ trapdoor_close: close trapdoor sound
+ iron_trapdoor_close: close iron trapdoor sound
+ fence_gate_close: close fence gate sound
+
# -- Boolean --
boolean:
true:
From 60415e5d1171d7c905abc98193864af52dc2060e Mon Sep 17 00:00:00 2001
From: sovdee <10354869+sovdeeth@users.noreply.github.com>
Date: Sat, 19 Apr 2025 14:01:21 -0400
Subject: [PATCH 04/16] Mostly finish GameEffects
---
src/main/java/ch/njol/skript/Skript.java | 2 +
.../java/ch/njol/skript/util/Direction.java | 20 ++
src/main/java/ch/njol/util/VectorMath.java | 21 --
.../skript/bukkit/particles/GameEffect.java | 35 ++-
.../bukkit/particles/ParticleEffect.java | 14 ++
.../Types.java => ParticleModule.java} | 14 +-
.../elements/effects/EffPlayEffect.java | 106 +++++++-
.../elements/expressions/ExprGameEffect.java | 226 ++++++++++++++----
.../expressions/ExprParticleEffect.java | 29 +++
src/main/resources/lang/default.lang | 128 +++++-----
10 files changed, 446 insertions(+), 149 deletions(-)
rename src/main/java/org/skriptlang/skript/bukkit/particles/{elements/Types.java => ParticleModule.java} (88%)
diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java
index 27a75d44e4d..501c9f420ae 100644
--- a/src/main/java/ch/njol/skript/Skript.java
+++ b/src/main/java/ch/njol/skript/Skript.java
@@ -79,6 +79,7 @@
import org.skriptlang.skript.bukkit.input.InputModule;
import org.skriptlang.skript.bukkit.log.runtime.BukkitRuntimeErrorConsumer;
import org.skriptlang.skript.bukkit.loottables.LootTableModule;
+import org.skriptlang.skript.bukkit.particles.ParticleModule;
import org.skriptlang.skript.bukkit.registration.BukkitRegistryKeys;
import org.skriptlang.skript.bukkit.registration.BukkitSyntaxInfos;
import org.skriptlang.skript.bukkit.tags.TagModule;
@@ -560,6 +561,7 @@ public void onEnable() {
TagModule.load();
FurnaceModule.load();
LootTableModule.load();
+ ParticleModule.load();
} catch (final Exception e) {
exception(e, "Could not load required .class files: " + e.getLocalizedMessage());
setEnabled(false);
diff --git a/src/main/java/ch/njol/skript/util/Direction.java b/src/main/java/ch/njol/skript/util/Direction.java
index d2f8e8fdf8c..178ea6d8a3d 100644
--- a/src/main/java/ch/njol/skript/util/Direction.java
+++ b/src/main/java/ch/njol/skript/util/Direction.java
@@ -264,6 +264,26 @@ public static Location[] getRelatives(final Location[] locations, final Directio
}
return r;
}
+
+ /**
+ * Calculates the nearest {@link BlockFace} to an arbitrary unit {@link Vector}.
+ *
+ * @param vector a normalized vector
+ * @return the block face most closely aligned to the input vector
+ */
+ public static BlockFace toNearestBlockFace(Vector vector) {
+ double maxDot = -1;
+ double dot;
+ BlockFace nearest = BlockFace.NORTH;
+ for (BlockFace face : BlockFace.values()){
+ dot = face.getDirection().dot(vector);
+ if (dot > maxDot) {
+ maxDot = dot;
+ nearest = face;
+ }
+ }
+ return nearest;
+ }
@Override
public String toString() {
diff --git a/src/main/java/ch/njol/util/VectorMath.java b/src/main/java/ch/njol/util/VectorMath.java
index 61f42b19183..9c937989a97 100644
--- a/src/main/java/ch/njol/util/VectorMath.java
+++ b/src/main/java/ch/njol/util/VectorMath.java
@@ -4,7 +4,6 @@
import ch.njol.skript.expressions.ExprVectorFromYawAndPitch;
import ch.njol.skript.expressions.ExprVectorSpherical;
import ch.njol.skript.expressions.ExprYawPitch;
-import org.bukkit.block.BlockFace;
import org.bukkit.util.Vector;
@Deprecated(forRemoval = true)
@@ -107,26 +106,6 @@ public static float wrapAngleDeg(float angle) {
return ExprVectorFromYawAndPitch.wrapAngleDeg(angle);
}
- /**
- * Calculates the nearest {@link BlockFace} to an arbitrary unit {@link Vector}.
- *
- * @param vector a normalized vector
- * @return the block face most closely aligned to the input vector
- */
- public static BlockFace toNearestBlockFace(Vector vector) {
- double maxDot = -1;
- double dot;
- BlockFace nearest = BlockFace.NORTH;
- for (BlockFace face : BlockFace.values()){
- dot = face.getDirection().dot(vector);
- if (dot > maxDot) {
- maxDot = dot;
- nearest = face;
- }
- }
- return nearest;
- }
-
/**
* Copies vector components of {@code vector2} into {@code vector1}.
*/
diff --git a/src/main/java/org/skriptlang/skript/bukkit/particles/GameEffect.java b/src/main/java/org/skriptlang/skript/bukkit/particles/GameEffect.java
index 143247dd977..6bfbf0ce789 100644
--- a/src/main/java/org/skriptlang/skript/bukkit/particles/GameEffect.java
+++ b/src/main/java/org/skriptlang/skript/bukkit/particles/GameEffect.java
@@ -2,6 +2,10 @@
import ch.njol.skript.util.EnumUtils;
import org.bukkit.Effect;
+import org.bukkit.Location;
+import org.bukkit.World;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
@@ -12,6 +16,7 @@
*/
public class GameEffect {
+ // TODO: fix missing key error for effects that require data
public static final EnumUtils ENUM_UTILS = new EnumUtils<>(Effect.class, "game effect");
/**
@@ -54,20 +59,46 @@ public boolean setData(Object data) {
return false;
}
+ /**
+ * Plays the effect at the given location. The given location must have a world.
+ * @param location the location to play the effect at
+ * @param radius the radius to play the effect in, or null to use the default radius
+ */
+ public void draw(@NotNull Location location, @Nullable Number radius) {
+ World world = location.getWorld();
+ if (world == null)
+ return;
+ if (radius == null) {
+ location.getWorld().playEffect(location, effect, data);
+ } else {
+ location.getWorld().playEffect(location, effect, data, radius.intValue());
+ }
+ }
+
+ /**
+ * Plays the effect for the given player.
+ * @param location the location to play the effect at
+ * @param player the player to play the effect for
+ */
+ public void drawForPlayer(Location location, @NotNull Player player) {
+ player.playEffect(location, effect, data);
+ }
+
public String toString(int flags) {
if (effect.getData() != null)
return ENUM_UTILS.toString(getEffect(), flags);
return toString();
}
- static final String[] namesWithoutData = (String[]) Arrays.stream(Effect.values())
+ static final String[] namesWithoutData = Arrays.stream(Effect.values())
.filter(effect -> effect.getData() == null)
.map(Enum::name)
- .toArray();
+ .toArray(String[]::new);
public static String[] getAllNamesWithoutData(){
return namesWithoutData.clone();
}
+
// TODO: add getters, setters, maybe builder class? Add spawn method.
}
diff --git a/src/main/java/org/skriptlang/skript/bukkit/particles/ParticleEffect.java b/src/main/java/org/skriptlang/skript/bukkit/particles/ParticleEffect.java
index 820f7b85357..a92dfbd96df 100644
--- a/src/main/java/org/skriptlang/skript/bukkit/particles/ParticleEffect.java
+++ b/src/main/java/org/skriptlang/skript/bukkit/particles/ParticleEffect.java
@@ -1,7 +1,10 @@
package org.skriptlang.skript.bukkit.particles;
+import org.bukkit.Location;
import org.bukkit.Particle;
+import org.bukkit.World;
+import org.bukkit.entity.Player;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.Nullable;
@@ -56,6 +59,17 @@ public class ParticleEffect {
this.offset = new Vector(0,0,0);
}
+ public void draw(Location location, boolean force) {
+ World world = location.getWorld();
+ if (world == null)
+ return;
+ world.spawnParticle(particle, location, count, offset.getX(), offset.getY(), offset.getZ(), extra, data, force);
+ }
+
+ public void drawForPlayer(Location location, Player player, boolean force) {
+ player.spawnParticle(particle, location, count, offset.getX(), offset.getY(), offset.getZ(), extra, data, force);
+ }
+
// TODO: Add parent interface for ParticleEffect, PlayerEffect, EntityEffect? Would make spawning easier, maybe.
// TODO: add getters, setters, maybe builder class? Add spawn method.
}
diff --git a/src/main/java/org/skriptlang/skript/bukkit/particles/elements/Types.java b/src/main/java/org/skriptlang/skript/bukkit/particles/ParticleModule.java
similarity index 88%
rename from src/main/java/org/skriptlang/skript/bukkit/particles/elements/Types.java
rename to src/main/java/org/skriptlang/skript/bukkit/particles/ParticleModule.java
index ad2aed9e6f2..54af1d93227 100644
--- a/src/main/java/org/skriptlang/skript/bukkit/particles/elements/Types.java
+++ b/src/main/java/org/skriptlang/skript/bukkit/particles/ParticleModule.java
@@ -1,5 +1,6 @@
-package org.skriptlang.skript.bukkit.particles.elements;
+package org.skriptlang.skript.bukkit.particles;
+import ch.njol.skript.Skript;
import ch.njol.skript.classes.ClassInfo;
import ch.njol.skript.classes.Parser;
import ch.njol.skript.classes.Serializer;
@@ -8,15 +9,14 @@
import ch.njol.skript.registrations.Classes;
import ch.njol.yggdrasil.Fields;
import org.bukkit.Effect;
-import org.eclipse.jdt.annotation.Nullable;
-import org.skriptlang.skript.bukkit.particles.GameEffect;
+import java.io.IOException;
import java.io.NotSerializableException;
import java.io.StreamCorruptedException;
-public class Types {
- static {
+public class ParticleModule {
+ public static void load () throws IOException {
Classes.registerClass(new ClassInfo<>(GameEffect.class, "gameeffect")
.user("game ?effects?")
.since("INSERT VERSION")
@@ -63,7 +63,6 @@ protected boolean canBeInstantiated() {
.defaultExpression(new EventValueExpression<>(GameEffect.class))
.parser(new Parser<>() {
@Override
- @Nullable
public GameEffect parse(String input, ParseContext context) {
return GameEffect.parse(input);
}
@@ -79,5 +78,8 @@ public String toVariableNameString(GameEffect o) {
}
}));
+ Skript.getAddonInstance().loadClasses("org.skriptlang.skript.bukkit.particles", "elements");
+
}
+
}
diff --git a/src/main/java/org/skriptlang/skript/bukkit/particles/elements/effects/EffPlayEffect.java b/src/main/java/org/skriptlang/skript/bukkit/particles/elements/effects/EffPlayEffect.java
index 1f521da8c99..74810b0e3b3 100644
--- a/src/main/java/org/skriptlang/skript/bukkit/particles/elements/effects/EffPlayEffect.java
+++ b/src/main/java/org/skriptlang/skript/bukkit/particles/elements/effects/EffPlayEffect.java
@@ -2,12 +2,114 @@
import ch.njol.skript.Skript;
import ch.njol.skript.lang.Effect;
+import ch.njol.skript.lang.Expression;
+import ch.njol.skript.lang.SkriptParser.ParseResult;
+import ch.njol.skript.util.Direction;
+import ch.njol.util.Kleenean;
+import org.bukkit.EntityEffect;
+import org.bukkit.Location;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.Player;
+import org.bukkit.event.Event;
+import org.jetbrains.annotations.Nullable;
+import org.skriptlang.skript.bukkit.particles.GameEffect;
+import org.skriptlang.skript.bukkit.particles.ParticleEffect;
// TODO: better terminology than "effects", as it's getting confusing.
public class EffPlayEffect extends Effect {
static {
Skript.registerEffect(EffPlayEffect.class,
- "[:force] (play|show|draw) [%number% [of]] %particles/game effects% (on|%directions%) %entities/locations% [(to %players%|in [a] (radius|range) of %number%)]",
- "(play|show|draw) [%number% [of]] %particles/game effects/entity effects% (on|at) %entities%)");
+ "[:force] (play|show|draw) %gameeffects% %directions% %locations%",
+ "[:force] (play|show|draw) %gameeffects% %directions% %locations% (for|to) %-players%",
+ "(play|show|draw) %gameeffects% %directions% %locations% (in|with) [a] [view] (radius|range) of %-number%)");
+ // "(play|show|draw) %entityeffects% on %entities%)"
+ }
+
+ private Expression> toDraw;
+ private @Nullable Expression locations;
+ private @Nullable Expression toPlayers;
+ private @Nullable Expression radius;
+ private boolean force;
+
+ // for entity effects
+ private @Nullable Expression entities;
+
+
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public boolean init(Expression>[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) {
+ this.force = parseResult.hasTag("force");
+ this.toDraw = expressions[0];
+ switch (matchedPattern) {
+ case 0 -> this.locations = Direction.combine((Expression extends Direction>) expressions[1], (Expression) expressions[2]);
+ case 1 -> {
+ this.locations = Direction.combine((Expression extends Direction>) expressions[1], (Expression) expressions[2]);
+ this.toPlayers = (Expression) expressions[3];
+ }
+ case 2 -> {
+ this.locations = Direction.combine((Expression extends Direction>) expressions[1], (Expression) expressions[2]);
+ this.radius = (Expression) expressions[3];
+ }
+ case 3 -> this.entities = (Expression) expressions[1];
+ }
+ return true;
+ }
+
+ @Override
+ protected void execute(Event event) {
+ // entity effect
+ if (this.entities != null) {
+ Entity[] entities = this.entities.getArray(event);
+ EntityEffect[] effects = (EntityEffect[]) toDraw.getArray(event);
+ for (Entity entity : entities) {
+ for (EntityEffect effect : effects) {
+ entity.playEffect(effect);
+ }
+ }
+ return;
+ }
+
+ // game effects / particles
+ assert this.locations != null;
+ Number radius = this.radius != null ? this.radius.getSingle(event) : null;
+ Location[] locations = this.locations.getArray(event);
+ Object[] toDraw = this.toDraw.getArray(event);
+ Player[] players = toPlayers != null ? toPlayers.getArray(event) : null;
+
+ for (Object draw : toDraw) {
+ // Game effects
+ if (draw instanceof GameEffect gameEffect) {
+ // in radius
+ if (players == null) {
+ for (Location location : locations)
+ gameEffect.draw(location, radius);
+ // for players
+ } else {
+ for (Player player : players) {
+ for (Location location : locations)
+ gameEffect.drawForPlayer(location, player);
+ }
+ }
+ // Particles
+ } else if (draw instanceof ParticleEffect particleEffect) {
+ // to everyone
+ if (players == null) {
+ for (Location location : locations)
+ particleEffect.draw(location, force);
+ // for players
+ } else {
+ for (Player player : players) {
+ for (Location location : locations)
+ particleEffect.drawForPlayer(location, player, force);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public String toString(@Nullable Event event, boolean debug) {
+ return "";
}
}
diff --git a/src/main/java/org/skriptlang/skript/bukkit/particles/elements/expressions/ExprGameEffect.java b/src/main/java/org/skriptlang/skript/bukkit/particles/elements/expressions/ExprGameEffect.java
index 1a81225b7f3..0c617886044 100644
--- a/src/main/java/org/skriptlang/skript/bukkit/particles/elements/expressions/ExprGameEffect.java
+++ b/src/main/java/org/skriptlang/skript/bukkit/particles/elements/expressions/ExprGameEffect.java
@@ -1,84 +1,131 @@
package org.skriptlang.skript.bukkit.particles.elements.expressions;
import ch.njol.skript.Skript;
+import ch.njol.skript.aliases.ItemType;
import ch.njol.skript.lang.Expression;
import ch.njol.skript.lang.ExpressionType;
-import ch.njol.skript.lang.SkriptParser;
+import ch.njol.skript.lang.SkriptParser.ParseResult;
import ch.njol.skript.lang.util.SimpleExpression;
import ch.njol.skript.util.Direction;
import ch.njol.skript.util.Patterns;
import ch.njol.util.Kleenean;
-import ch.njol.util.VectorMath;
+import org.bukkit.Axis;
+import org.bukkit.Color;
import org.bukkit.Effect;
+import org.bukkit.Material;
import org.bukkit.block.BlockFace;
import org.bukkit.event.Event;
-import org.bukkit.util.Vector;
+import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.skriptlang.skript.bukkit.particles.GameEffect;
+import java.util.ArrayList;
+import java.util.List;
+
public class ExprGameEffect extends SimpleExpression {
- private static final Patterns PATTERNS = new Patterns<>(new Object[][]{
- {"[record] song (of|using) %item%", Effect.RECORD_PLAY}, // shows the action bar too!
- {"[dispenser] [black|1:white] smoke effect [(in|with|using) direction] %direction/vector%", Effect.SMOKE},
- {"[foot]step sound [effect] (on|of|using) %item/blockdata%", Effect.STEP_SOUND},
- {"[:instant] [splash] potion break effect (with|of|using) [colour] %color%", Effect.POTION_BREAK}, // paper changes this type
- {"composter fill[ing] (succe[ss|ed]|1:fail[ure]) sound [effect]", Effect.COMPOSTER_FILL_ATTEMPT},
- {"villager plant grow[th] effect [(with|using) %number% particles]", Effect.VILLAGER_PLANT_GROW},
- {"[fake] bone meal effect [(with|using) %number% particles]", Effect.BONE_MEAL_USE},
- // post copper update (1.19?)
- {"(electric|lightning[ rod]|copper) spark effect [(in|using) the (1:x|2:y|3:z) axis]", Effect.ELECTRIC_SPARK},
- // paper only
- {"sculk (charge|spread) effect [(with|using) data %number%]", Effect.PARTICLES_SCULK_CHARGE}, // data explanation here https://discord.com/channels/135877399391764480/836220422223036467/1211040434852208660
- {"[finish] brush[ing] effect (with|using) %item/blockdata%", Effect.PARTICLES_AND_SOUND_BRUSH_BLOCK_COMPLETE},
- // 1.20.3
- {"trial spawner detect[ing|s] [%number%] player[s] effect", Effect.TRIAL_SPAWNER_DETECT_PLAYER}
- });
+ private static final Patterns PATTERNS;
+
+ private static final List REGISTERED_PATTERNS = new ArrayList<>();
+
+ private static void registerEffect(Effect effect, String pattern) {
+ registerEffect(effect, pattern, (event, expressions, parseResult) -> expressions[0].getSingle(event));
+ }
+
+ private static void registerEffect(Effect effect, String pattern, GetData> getData) {
+ REGISTERED_PATTERNS.add(new EffectPattern(effect, pattern, getData));
+ }
static {
- // TODO: register the rest via the type parser
- // make sure to not parse the ones with data, so this class can handle it
+ registerEffect(Effect.RECORD_PLAY, "[record] song (of|using) %itemtype%", GetData::getMaterialData);
+ registerEffect(Effect.SMOKE, "[dispenser] black smoke effect [(in|with|using) direction] %direction%",
+ GetData::getBlockFaceData);
+ registerEffect(Effect.SHOOT_WHITE_SMOKE, "[dispenser] white smoke effect [(in|with|using) direction] %direction%",
+ GetData::getBlockFaceData);
+ registerEffect(Effect.STEP_SOUND, "[foot]step sound [effect] (on|of|using) %itemtype/blockdata%"); // handle version changes
+ registerEffect(Effect.POTION_BREAK, "[splash] potion break effect (with|of|using) [colour] %color%",
+ GetData::getColorData); // paper changes this type from potion data to color
+ registerEffect(Effect.INSTANT_POTION_BREAK, "instant [splash] potion break effect (with|of|using) [colour] %color%",
+ GetData::getColorData);
+ registerEffect(Effect.COMPOSTER_FILL_ATTEMPT, "[composter] fill[ing] (succe[ss|ed]|1:fail[ure]) sound [effect]",
+ (event, expressions, parseResult) -> parseResult.mark == 0);
+
+ if (!Skript.isRunningMinecraft(1, 20, 5)) {
+ //noinspection removal
+ registerEffect(Effect.VILLAGER_PLANT_GROW, "villager plant grow[th] effect [(with|using) %-number% particles]",
+ GetData::defaultTo10Particles);
+ }
+
+ registerEffect(Effect.BONE_MEAL_USE, "[fake] bone meal effect [(with|using) %-number% particles]",
+ GetData::defaultTo10Particles);
+ registerEffect(Effect.ELECTRIC_SPARK, "(electric|lightning[ rod]|copper) spark effect [(in|using|along) the (1:x|2:y|3:z) axis]",
+ (event, expressions, parseResult) -> (parseResult.mark == 0 ? null : Axis.values()[parseResult.mark - 1]));
+
+ // All modern ones are Paper only
+ if (Skript.fieldExists(Effect.class, "PARTICLES_SCULK_CHARGE")) {
+ if (Skript.isRunningMinecraft(1, 20, 1)) {
+ registerEffect(Effect.PARTICLES_SCULK_CHARGE, "sculk (charge|spread) effect [(with|using) data %number%]"); // data explanation here https://discord.com/channels/135877399391764480/836220422223036467/1211040434852208660
+ registerEffect(Effect.PARTICLES_AND_SOUND_BRUSH_BLOCK_COMPLETE, "[finish] brush[ing] effect (with|using) %itemtype/blockdata%");
+ }
+ if (Skript.isRunningMinecraft(1, 20, 4)) {
+ registerEffect(Effect.TRIAL_SPAWNER_DETECT_PLAYER, "trial spawner detect[ing|s] [%-number%] player[s] effect",
+ GetData::defaultTo1Player);
+ registerEffect(Effect.TRIAL_SPAWNER_SPAWN, "[:ominous] trial spawner spawn[ing] effect",
+ GetData::isOminous);
+ registerEffect(Effect.TRIAL_SPAWNER_SPAWN_MOB_AT, "[:ominous] trial spawner spawn[ing] mob effect with sound",
+ GetData::isOminous);
+ }
+ if (Skript.isRunningMinecraft(1, 20, 5)) {
+ registerEffect(Effect.BEE_GROWTH, "bee growth effect [(with|using) %-number% particles]",
+ GetData::defaultTo10Particles);
+ registerEffect(Effect.VAULT_ACTIVATE, "[:ominous] [trial] vault activate effect",
+ GetData::isOminous);
+ registerEffect(Effect.VAULT_DEACTIVATE, "[:ominous] [trial] vault deactivate effect",
+ GetData::isOminous);
+ registerEffect(Effect.TRIAL_SPAWNER_DETECT_PLAYER_OMINOUS, "ominous trial spawner detect[ing|s] [%-number%] player[s] effect",
+ GetData::defaultTo1Player);
+ registerEffect(Effect.TRIAL_SPAWNER_BECOME_OMINOUS, "trial spawner become[ing] [:not] ominous effect",
+ (event, expressions, parseResult) -> !parseResult.hasTag("not"));
+ registerEffect(Effect.TRIAL_SPAWNER_SPAWN_ITEM, "[:ominous] trial spawner spawn[ing] item effect",
+ GetData::isOminous);
+ registerEffect(Effect.TURTLE_EGG_PLACEMENT, "place turtle egg effect [(with|using) %-number% particles]",
+ GetData::defaultTo10Particles);
+ registerEffect(Effect.SMASH_ATTACK, "[mace] smash attack effect [(with|using) %-number% particles]",
+ GetData::defaultTo10Particles);
+ }
+ }
+
+ // create Patterns object
+ Object[][] patterns = new Object[REGISTERED_PATTERNS.size()][2];
+ int i = 0;
+ for (EffectPattern effectPattern : REGISTERED_PATTERNS) {
+ patterns[i][0] = effectPattern.pattern;
+ patterns[i][1] = effectPattern;
+ i++;
+ }
+ PATTERNS = new Patterns<>(patterns);
+
Skript.registerExpression(ExprGameEffect.class, GameEffect.class, ExpressionType.COMBINED, PATTERNS.getPatterns());
}
private GameEffect gameEffect;
- private Expression> data;
- private int variant;
+ private GetData> getData;
+ private Expression>[] expressions;
+ private ParseResult parseResult;
@Override
- public boolean init(Expression>[] expressions, int matchedPattern, Kleenean isDelayed, SkriptParser.ParseResult parseResult) {
- gameEffect = new GameEffect(PATTERNS.getInfo(matchedPattern));
- variant = parseResult.mark;
- if (expressions.length > 0)
- data = expressions[0];
+ public boolean init(Expression>[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) {
+ gameEffect = new GameEffect(PATTERNS.getInfo(matchedPattern).effect());
+ getData = PATTERNS.getInfo(matchedPattern).getData();
+ this.expressions = expressions;
+ this.parseResult = parseResult;
return true;
}
@Override
protected GameEffect @Nullable [] get(Event event) {
- switch (gameEffect.getEffect()) {
- case SMOKE:
- return handleSmokeEffects(gameEffect, variant, data.getSingle(event));
- default:
- return setData(gameEffect, data.getSingle(event));
- }
- }
-
- private GameEffect @Nullable [] handleSmokeEffects(GameEffect gameEffect, int variant, Object data) {
- BlockFace blockFace;
- if (data instanceof Vector) {
- Vector vector = ((Vector) data);
- if (vector.isZero())
- return new GameEffect[0];
- blockFace = VectorMath.toNearestBlockFace(vector.normalize());
- } else if (data instanceof Direction) {
- Vector vector = ((Direction) data).getDirection();
- // what about relative directions
- }
- if (variant == 1)
- gameEffect = new GameEffect(Effect.SHOOT_WHITE_SMOKE);
- return setData(gameEffect, data);
+ return setData(gameEffect, getData.getData(event, expressions, parseResult));
}
private GameEffect @Nullable [] setData(GameEffect gameEffect, Object data){
@@ -92,16 +139,89 @@ public boolean init(Expression>[] expressions, int matchedPattern, Kleenean is
@Override
public boolean isSingle() {
- return false;
+ return true;
}
@Override
public Class extends GameEffect> getReturnType() {
- return null;
+ return GameEffect.class;
}
@Override
public String toString(@Nullable Event event, boolean debug) {
- return null;
+ // TODO: handle properly
+ return "game effect " + gameEffect.getEffect().name();
+ }
+
+ /**
+ * A helper class to store the effect and its pattern.
+ *
+ * @param effect The effect
+ * @param pattern The pattern
+ * @param getData The function to get the data from the event
+ */
+ private record EffectPattern(Effect effect, String pattern, GetData> getData) {}
+
+ /**
+ * A functional interface to get the data from the event.
+ *
+ * @param The type of the data
+ */
+ @FunctionalInterface
+ interface GetData {
+ /**
+ * Get the data from the event.
+ *
+ * @param event The event to evaluate with
+ * @param expressions Any expressions that are used in the pattern
+ * @param parseResult The parse result from parsing
+ * @return The data to use for the effect
+ */
+ T getData(Event event, Expression>[] expressions, ParseResult parseResult);
+
+ //
+ // Helper functions for common data types
+ //
+
+ private static @Nullable Material getMaterialData(Event event, Expression> @NotNull [] expressions, ParseResult parseResult) {
+ Object input = expressions[0].getSingle(event);
+ if (!(input instanceof ItemType itemType))
+ return null;
+ return itemType.getMaterial();
+ }
+
+ private static @Nullable BlockFace getBlockFaceData(Event event, Expression> @NotNull [] expressions, ParseResult parseResult) {
+ Object input = expressions[0].getSingle(event);
+ if (!(input instanceof Direction direction))
+ return null;
+ return Direction.toNearestBlockFace(direction.getDirection());
+ }
+
+ private static @Nullable Color getColorData(Event event, Expression> @NotNull [] expressions, ParseResult parseResult) {
+ Object input = expressions[0].getSingle(event);
+ if (!(input instanceof ch.njol.skript.util.Color color))
+ return null;
+ return color.asBukkitColor();
+ }
+
+ private static boolean isOminous(Event event, Expression>[] expressions, @NotNull ParseResult parseResult) {
+ return parseResult.hasTag("ominous");
+ }
+
+ private static int defaultTo10Particles(Event event, Expression> @NotNull [] expressions, ParseResult parseResult) {
+ Object input = expressions[0].getSingle(event);
+ if (!(input instanceof Number number))
+ return 10;
+ return number.intValue();
+ }
+
+ private static int defaultTo1Player(Event event, Expression> @NotNull [] expressions, ParseResult parseResult) {
+ Object input = expressions[0].getSingle(event);
+ if (!(input instanceof Number number))
+ return 1;
+ return number.intValue();
+ }
+
}
+
}
diff --git a/src/main/java/org/skriptlang/skript/bukkit/particles/elements/expressions/ExprParticleEffect.java b/src/main/java/org/skriptlang/skript/bukkit/particles/elements/expressions/ExprParticleEffect.java
index 78d1d8e32b4..67c8780084a 100644
--- a/src/main/java/org/skriptlang/skript/bukkit/particles/elements/expressions/ExprParticleEffect.java
+++ b/src/main/java/org/skriptlang/skript/bukkit/particles/elements/expressions/ExprParticleEffect.java
@@ -1,9 +1,38 @@
package org.skriptlang.skript.bukkit.particles.elements.expressions;
+import ch.njol.skript.lang.Expression;
+import ch.njol.skript.lang.SkriptParser;
import ch.njol.skript.lang.util.SimpleExpression;
+import ch.njol.util.Kleenean;
+import org.bukkit.event.Event;
+import org.jetbrains.annotations.Nullable;
import org.skriptlang.skript.bukkit.particles.ParticleEffect;
public class ExprParticleEffect extends SimpleExpression {
+ @Override
+ protected ParticleEffect @Nullable [] get(Event event) {
+ return new ParticleEffect[0];
+ }
+
+ @Override
+ public boolean isSingle() {
+ return false;
+ }
+
+ @Override
+ public Class extends ParticleEffect> getReturnType() {
+ return null;
+ }
+
+ @Override
+ public String toString(@Nullable Event event, boolean debug) {
+ return "";
+ }
+
+ @Override
+ public boolean init(Expression>[] expressions, int matchedPattern, Kleenean isDelayed, SkriptParser.ParseResult parseResult) {
+ return false;
+ }
// TODO:
// Syntax:
// count + (name + "particle" + data) + offset + extra
diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang
index 97ce21589f4..2f993e704b2 100644
--- a/src/main/resources/lang/default.lang
+++ b/src/main/resources/lang/default.lang
@@ -2412,71 +2412,69 @@ transform reasons:
infection: infection, villager infection
# -- Game Effects --
-game effects:
- click1:
- name: dispenser click sound
- pattern:
- enums: click1
- click2: menu click sound, gui click sound
- bow_fire: fire bow sound, shoot bow sound
- extinguish: extinguish fire sound, extinguish sound
- ghast_shriek: ghast shriek sound
- ghast_shoot: ghast shoot sound
- blaze_shoot: blaze shoot sound
- zombie_chew_wooden_door: zombie chewing on wooden door sound, zombie attacking wooden door sound
- zombie_chew_iron_door: zombie chewing on iron door sound, zombie attacking iron door sound
- zombie_destroy_door: zombie destroy door sound, zombie destroying door sound
- ender_signal: ender eye break, eye of ender break, ender eye breaking, eye of ender breaking
- mobspawner_flames: mob spawner flames, spawner flames
- brewing_stand_brew: brewing sound, brewing stand sound
- chorus_flower_grow: chorus flower growing sound, chorus plant growing sound
- chorus_flower_death: chorus flower death sound
- portal_travel: nether portal travel sound
- endereye_launch: ender eye launch sound, eye of ender launch sound
- firework_shoot: dispenser shoot sound
- dragon_breath: dragon breath, dragon's breath
- anvil_break: break anvil sound
- anvil_use: use anvil sound
- anvil_land: anvil land sound
- enderdragon_shoot: ender dragon shoot sound, dragon shoot sound
- enderdragon_growl: ender dragon growl sound, dragon growl sound
- wither_break_block: wither breaking block sound
- wither_shoot: wither shoot sound
- zombie_infect: zombie infection sound
- zombie_converted_villager: zombie converting villager sound
- zombie_converted_to_drowned: zombie conversion to drowned sound
- zombie_converts_to_drowned: zombie conversion to drowned sound
- husk_converted_to_zombie: husk conversion to zombie sound
- husk_converts_to_zombie: husk conversion to zombie sound
- skeleton_converted_to_stray: skeleton conversion to stray sound
- bat_takeoff: bat take off sound, bat take flight sound
- end_gateway_spawn: end gateway spawn, new end gateway
- phantom_bite: phantom bite sound
- phantom_bites: phantom bites sound
- grindstone_use: use grindstone sound
- grindstone_used: used grindstone sound
- book_page_turn: book page turn sound, turn page sound
- book_page_turned: book page turned sound, turned page sound
- smithing_table_use: use smithing table sound
- pointed_dripstone_drip_lava_into_cauldron: lava dripping into cauldron sound
- pointed_dripstone_drip_water_into_cauldron: water dripping into cauldron sound
- dripping_dripstone: water dripping sound, lava dripping sound
- lava_interact: lava interaction
- lava_converts_block: lava converts block
- redstone_torch_burnout: redstone touch burnout
- redstone_torch_burns_out: redstone touch burns out
- end_portal_frame_fill: fill end portal frame
- ender_eye_placed: ender eye placed
- ender_dragon_destroy_block: ender dragon breaking block, dragon breaking block
- ender_dragon_destroys_block: ender dragon destroys block, dragon destroys block
- sponge_dry: sponge dry, sponge dry out
- copper_wax_on: apply wax, wax on
- copper_wax_off: remove wax, wax off
- oxidised_copper_scrape: scrape oxidised copper, scrape copper
- wither_spawned: spawn wither sound
- ender_dragon_death: ender dragon death sound, dragon death sound
- end_portal_created_in_overworld: complete end portal sound, create end portal sound
- composter_composts: composter compost
+# all patterns should include the word 'effect'
+game effect:
+ click1: dispenser click sound effect
+ click2: menu click sound effect, gui click sound effect
+ bow_fire: fire bow sound effect, shoot bow sound effect
+ extinguish: extinguish fire sound effect, extinguish sound effect
+ ghast_shriek: ghast shriek sound effect
+ ghast_shoot: ghast shoot sound effect
+ blaze_shoot: blaze shoot sound effect
+ zombie_chew_wooden_door: zombie chewing on wooden door sound effect, zombie attacking wooden door sound effect
+ zombie_chew_iron_door: zombie chewing on iron door sound effect, zombie attacking iron door sound effect
+ zombie_destroy_door: zombie destroy door sound effect, zombie destroying door sound effect
+ ender_signal: ender eye break effect, eye of ender break effect
+ mobspawner_flames: mob spawner flames effect, spawner flames effect
+ brewing_stand_brew: brewing sound effect
+ chorus_flower_grow: chorus flower growing sound effect
+ chorus_flower_death: chorus flower death sound effect
+ portal_travel: nether portal travel sound effect
+ endereye_launch: ender eye launch sound effect, eye of ender launch sound effect
+ firework_shoot: dispenser shoot sound effect
+ dragon_breath: dragon breath effect, dragon's breath effect
+ anvil_break: break anvil sound effect
+ anvil_use: use anvil sound effect
+ anvil_land: anvil land sound effect
+ enderdragon_shoot: ender dragon shoot sound effect, dragon shoot sound effect
+ enderdragon_growl: ender dragon growl sound effect, dragon growl sound effect
+ wither_break_block: wither breaking block sound effect
+ wither_shoot: wither shoot sound effect
+ zombie_infect: zombie infection sound effect
+ zombie_converted_villager: zombie converting villager sound effect
+ zombie_converted_to_drowned: zombie converting to drowned sound effect
+ zombie_converts_to_drowned: zombie converting to drowned sound effect
+ husk_converted_to_zombie: husk converting to zombie sound effect
+ husk_converts_to_zombie: husk converting to zombie sound effect
+ skeleton_converted_to_stray: skeleton converting to stray sound effect
+ bat_takeoff: bat take off sound effect
+ end_gateway_spawn: end gateway spawn effect
+ phantom_bite: phantom bite sound effect
+ phantom_bites: phantom bites sound effect
+ grindstone_use: use grindstone sound effect
+ grindstone_used: used grindstone sound effect
+ book_page_turn: turn page sound effect
+ book_page_turned: turned page sound effect
+ smithing_table_use: use smithing table sound effect
+ pointed_dripstone_drip_lava_into_cauldron: lava dripping into cauldron sound effect
+ pointed_dripstone_drip_water_into_cauldron: water dripping into cauldron sound effect
+ dripping_dripstone: water dripping sound effect, lava dripping sound effect
+ lava_interact: lava interaction effect
+ lava_converts_block: lava converts block effect
+ redstone_torch_burnout: redstone torch burnout effect
+ redstone_torch_burns_out: redstone torch burns out effect
+ end_portal_frame_fill: fill end portal frame effect
+ ender_eye_placed: ender eye placed effect, eye of ender placed effect
+ ender_dragon_destroy_block: ender dragon breaking block effect, dragon breaking block effect
+ ender_dragon_destroys_block: ender dragon destroys block effect, dragon destroys block effect
+ sponge_dry: sponge dry out effect
+ copper_wax_on: apply wax effect
+ copper_wax_off: remove wax effect
+ oxidised_copper_scrape: scrape oxidised copper effect
+ wither_spawned: spawn wither sound effect
+ ender_dragon_death: ender dragon death sound effect, dragon death sound effect
+ end_portal_created_in_overworld: complete end portal sound effect, create end portal sound effect
+ composter_composts: composter compost effect
# these effect are deprecated in 1.19, they do nothing.
# their respective Sounds should be used instead.
From f20f50408bb17bf040c82f1bc8f712b659dc61ca Mon Sep 17 00:00:00 2001
From: sovdee <10354869+sovdeeth@users.noreply.github.com>
Date: Sun, 20 Apr 2025 20:25:25 -0400
Subject: [PATCH 05/16] Finish game effects
---
.../skript/bukkit/particles/GameEffect.java | 10 +++-
src/main/resources/lang/default.lang | 60 +++++++++++++++----
2 files changed, 56 insertions(+), 14 deletions(-)
diff --git a/src/main/java/org/skriptlang/skript/bukkit/particles/GameEffect.java b/src/main/java/org/skriptlang/skript/bukkit/particles/GameEffect.java
index 6bfbf0ce789..7aec4912ac2 100644
--- a/src/main/java/org/skriptlang/skript/bukkit/particles/GameEffect.java
+++ b/src/main/java/org/skriptlang/skript/bukkit/particles/GameEffect.java
@@ -1,5 +1,7 @@
package org.skriptlang.skript.bukkit.particles;
+import ch.njol.skript.Skript;
+import ch.njol.skript.registrations.Classes;
import ch.njol.skript.util.EnumUtils;
import org.bukkit.Effect;
import org.bukkit.Location;
@@ -16,8 +18,7 @@
*/
public class GameEffect {
- // TODO: fix missing key error for effects that require data
- public static final EnumUtils ENUM_UTILS = new EnumUtils<>(Effect.class, "game effect");
+ public static final EnumUtils ENUM_UTILS = new EnumUtils<>(Effect.class, "game effect"); // exclude effects that require data
/**
* The {@link Effect} that this object represents
@@ -36,7 +37,10 @@ public GameEffect(Effect effect) {
public static GameEffect parse(String input) {
Effect effect = ENUM_UTILS.parse(input.toLowerCase(Locale.ENGLISH));
- if (effect == null || effect.getData() != null) {
+ if (effect == null)
+ return null;
+ if (effect.getData() != null) {
+ Skript.error("The effect " + Classes.toString(effect) + " requires data and cannot be parsed directly. Use the Game Effect expression instead.");
return null;
}
return new GameEffect(effect);
diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang
index 2f993e704b2..1650e1f4547 100644
--- a/src/main/resources/lang/default.lang
+++ b/src/main/resources/lang/default.lang
@@ -2476,18 +2476,56 @@ game effect:
end_portal_created_in_overworld: complete end portal sound effect, create end portal sound effect
composter_composts: composter compost effect
- # these effect are deprecated in 1.19, they do nothing.
+ # these effects are deprecated in 1.19, they do nothing.
# their respective Sounds should be used instead.
- door_toggle: toggle door sound
- iron_door_toggle: toggle iron door sound
- trapdoor_toggle: toggle trapdoor sound
- iron_trapdoor_toggle: toggle iron trapdoor sound
- fence_gate_toggle: toggle fence gate sound
- door_close: close door sound
- iron_door_close: close iron door sound
- trapdoor_close: close trapdoor sound
- iron_trapdoor_close: close iron trapdoor sound
- fence_gate_close: close fence gate sound
+ door_toggle: toggle door sound effect
+ iron_door_toggle: toggle iron door sound effect
+ trapdoor_toggle: toggle trapdoor sound effect
+ iron_trapdoor_toggle: toggle iron trapdoor sound effect
+ fence_gate_toggle: toggle fence gate sound effect
+ door_close: close door sound effect
+ iron_door_close: close iron door sound effect
+ trapdoor_close: close trapdoor sound effect
+ iron_trapdoor_close: close iron trapdoor sound effect
+ fence_gate_close: close fence gate sound effect
+
+ # to categorize
+ pointed_dripstone_land: pointed dripstone land effect
+ sound_stop_jukebox_song: stop jukebox song sound effect
+ crafter_craft: crafter craft sound effect
+ crafter_fail: crafter fail sound effect
+ particles_egg_crack: egg crack effect
+ gust_dust: gust dust effect
+ trial_spawner_eject_item: trial spawner eject item effect
+ vault_eject_item: vault eject item effect
+ spawn_cobweb: spawn cobweb effect
+ sound_with_charge_shot: charge shot sound effect
+
+ # these effects require data and can't be parsed directly, but exist here in case skript needs a proper name.
+ record_play: play record effect
+ smoke: dispenser black smoke effect
+ step_sound: footstep sound effect
+ potion_break: splash potion break effect
+ instant_potion_break: instant splash potion break effect
+ villager_plant_grow: villager grow plant effect
+ composter_fill_attempt: composting fill effect
+ bone_meal_use: bone meal use effect
+ electric_spark: electric spark effect
+ shoot_white_smoke: dispenser white smoke effect
+ bee_growth: bee grow plant effect
+ turtle_egg_placement: place turtle egg effect
+ smash_attack: mace smash attack effect
+ particles_sculk_charge: sculk charge effect
+ particles_sculk_shriek: sculk shriek effect
+ particles_and_sound_brush_block_complete: brush block effect
+ trial_spawner_spawn: trial spawner spawn effect
+ trial_spawner_spawn_mob_at: trial spawner spawn mob effect
+ trial_spawner_detect_player: trial spawner detect player effect
+ vault_activate: vault activate effect
+ vault_deactivate: vault deactivate effect
+ trial_spawner_detect_player_ominous: ominous trial spawner detect player effect
+ trial_spawner_become_ominous: trial spawner become ominous effect
+ trial_spawner_spawn_item: trial spawner spawn item effect
# -- Teleport Flags --
teleport flags:
From 6cbe2916aa7c7f5336141c7795741a08dd0ef4f0 Mon Sep 17 00:00:00 2001
From: sovdee <10354869+sovdeeth@users.noreply.github.com>
Date: Sun, 20 Apr 2025 20:26:35 -0400
Subject: [PATCH 06/16] forgot types.gameeffect
---
src/main/resources/lang/default.lang | 23 +++++++++++------------
1 file changed, 11 insertions(+), 12 deletions(-)
diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang
index 1650e1f4547..6183361126d 100644
--- a/src/main/resources/lang/default.lang
+++ b/src/main/resources/lang/default.lang
@@ -2475,6 +2475,16 @@ game effect:
ender_dragon_death: ender dragon death sound effect, dragon death sound effect
end_portal_created_in_overworld: complete end portal sound effect, create end portal sound effect
composter_composts: composter compost effect
+ pointed_dripstone_land: pointed dripstone land effect
+ sound_stop_jukebox_song: stop jukebox song sound effect
+ crafter_craft: crafter craft sound effect
+ crafter_fail: crafter fail sound effect
+ particles_egg_crack: egg crack effect
+ gust_dust: gust dust effect
+ trial_spawner_eject_item: trial spawner eject item effect
+ vault_eject_item: vault eject item effect
+ spawn_cobweb: spawn cobweb effect
+ sound_with_charge_shot: charge shot sound effect
# these effects are deprecated in 1.19, they do nothing.
# their respective Sounds should be used instead.
@@ -2489,18 +2499,6 @@ game effect:
iron_trapdoor_close: close iron trapdoor sound effect
fence_gate_close: close fence gate sound effect
- # to categorize
- pointed_dripstone_land: pointed dripstone land effect
- sound_stop_jukebox_song: stop jukebox song sound effect
- crafter_craft: crafter craft sound effect
- crafter_fail: crafter fail sound effect
- particles_egg_crack: egg crack effect
- gust_dust: gust dust effect
- trial_spawner_eject_item: trial spawner eject item effect
- vault_eject_item: vault eject item effect
- spawn_cobweb: spawn cobweb effect
- sound_with_charge_shot: charge shot sound effect
-
# these effects require data and can't be parsed directly, but exist here in case skript needs a proper name.
record_play: play record effect
smoke: dispenser black smoke effect
@@ -2805,6 +2803,7 @@ types:
vehicle: vehicle¦s @a
fishingstate: fishing state¦s @a
equipmentslot: equipment slot¦s @an
+ gameeffect: game effect¦s @a
# Skript
From 104992397082116ac330915f727ad64d30868dc7 Mon Sep 17 00:00:00 2001
From: sovdee <10354869+sovdeeth@users.noreply.github.com>
Date: Sun, 20 Apr 2025 21:03:01 -0400
Subject: [PATCH 07/16] Initial EntityEffect Efforts
now i have to go through every one, check what it really does, and update its name accordingly
fun
---
.../skript/bukkit/particles/GameEffect.java | 2 +
.../bukkit/particles/ParticleModule.java | 20 ++++-
.../elements/effects/EffPlayEffect.java | 30 +++++--
src/main/resources/lang/default.lang | 81 ++++++++++++++++++-
4 files changed, 122 insertions(+), 11 deletions(-)
diff --git a/src/main/java/org/skriptlang/skript/bukkit/particles/GameEffect.java b/src/main/java/org/skriptlang/skript/bukkit/particles/GameEffect.java
index 7aec4912ac2..c5d5843b5d7 100644
--- a/src/main/java/org/skriptlang/skript/bukkit/particles/GameEffect.java
+++ b/src/main/java/org/skriptlang/skript/bukkit/particles/GameEffect.java
@@ -69,6 +69,8 @@ public boolean setData(Object data) {
* @param radius the radius to play the effect in, or null to use the default radius
*/
public void draw(@NotNull Location location, @Nullable Number radius) {
+ if (effect.getData() != null && data == null)
+ return;
World world = location.getWorld();
if (world == null)
return;
diff --git a/src/main/java/org/skriptlang/skript/bukkit/particles/ParticleModule.java b/src/main/java/org/skriptlang/skript/bukkit/particles/ParticleModule.java
index 54af1d93227..6331f17d4c2 100644
--- a/src/main/java/org/skriptlang/skript/bukkit/particles/ParticleModule.java
+++ b/src/main/java/org/skriptlang/skript/bukkit/particles/ParticleModule.java
@@ -2,6 +2,7 @@
import ch.njol.skript.Skript;
import ch.njol.skript.classes.ClassInfo;
+import ch.njol.skript.classes.EnumClassInfo;
import ch.njol.skript.classes.Parser;
import ch.njol.skript.classes.Serializer;
import ch.njol.skript.expressions.base.EventValueExpression;
@@ -9,10 +10,11 @@
import ch.njol.skript.registrations.Classes;
import ch.njol.yggdrasil.Fields;
import org.bukkit.Effect;
+import org.bukkit.EntityEffect;
import java.io.IOException;
-import java.io.NotSerializableException;
import java.io.StreamCorruptedException;
+import java.util.Arrays;
public class ParticleModule {
@@ -23,6 +25,12 @@ public static void load () throws IOException {
.description("Various game effects that can be played for players, like record disc songs, splash potions breaking, or fake bone meal effects.")
.name("Game Effect")
.usage(GameEffect.getAllNamesWithoutData())
+ .supplier(() -> {
+ Effect[] effects = Effect.values();
+ return Arrays.stream(effects).map(GameEffect::new)
+ .filter(effect -> effect.getData() == null)
+ .iterator();
+ })
.serializer(new Serializer<>() {
@Override
public Fields serialize(GameEffect effect) {
@@ -33,12 +41,12 @@ public Fields serialize(GameEffect effect) {
}
@Override
- public void deserialize(GameEffect effect, Fields fields) throws StreamCorruptedException, NotSerializableException {
+ public void deserialize(GameEffect effect, Fields fields) {
assert false;
}
@Override
- protected GameEffect deserialize(Fields fields) throws StreamCorruptedException, NotSerializableException {
+ protected GameEffect deserialize(Fields fields) throws StreamCorruptedException {
String name = fields.getAndRemovePrimitive("name", String.class);
GameEffect effect;
try {
@@ -78,6 +86,12 @@ public String toVariableNameString(GameEffect o) {
}
}));
+ Classes.registerClass(new EnumClassInfo<>(EntityEffect.class, "entityeffect", "entity effect")
+ .user("entity ?effects?")
+ .name("Entity Effect")
+ .description("Various entity effects that can be played for entities, like wolf howling, or villager happy.")
+ .since("INSERT VERSION"));
+
Skript.getAddonInstance().loadClasses("org.skriptlang.skript.bukkit.particles", "elements");
}
diff --git a/src/main/java/org/skriptlang/skript/bukkit/particles/elements/effects/EffPlayEffect.java b/src/main/java/org/skriptlang/skript/bukkit/particles/elements/effects/EffPlayEffect.java
index 74810b0e3b3..4e9987b1d79 100644
--- a/src/main/java/org/skriptlang/skript/bukkit/particles/elements/effects/EffPlayEffect.java
+++ b/src/main/java/org/skriptlang/skript/bukkit/particles/elements/effects/EffPlayEffect.java
@@ -1,9 +1,11 @@
package org.skriptlang.skript.bukkit.particles.elements.effects;
import ch.njol.skript.Skript;
+import ch.njol.skript.config.Node;
import ch.njol.skript.lang.Effect;
import ch.njol.skript.lang.Expression;
import ch.njol.skript.lang.SkriptParser.ParseResult;
+import ch.njol.skript.registrations.Classes;
import ch.njol.skript.util.Direction;
import ch.njol.util.Kleenean;
import org.bukkit.EntityEffect;
@@ -14,15 +16,16 @@
import org.jetbrains.annotations.Nullable;
import org.skriptlang.skript.bukkit.particles.GameEffect;
import org.skriptlang.skript.bukkit.particles.ParticleEffect;
+import org.skriptlang.skript.log.runtime.SyntaxRuntimeErrorProducer;
// TODO: better terminology than "effects", as it's getting confusing.
-public class EffPlayEffect extends Effect {
+public class EffPlayEffect extends Effect implements SyntaxRuntimeErrorProducer {
static {
Skript.registerEffect(EffPlayEffect.class,
"[:force] (play|show|draw) %gameeffects% %directions% %locations%",
"[:force] (play|show|draw) %gameeffects% %directions% %locations% (for|to) %-players%",
- "(play|show|draw) %gameeffects% %directions% %locations% (in|with) [a] [view] (radius|range) of %-number%)");
- // "(play|show|draw) %entityeffects% on %entities%)"
+ "(play|show|draw) %gameeffects% %directions% %locations% (in|with) [a] [view] (radius|range) of %-number%)",
+ "(play|show|draw) %entityeffects% on %entities%");
}
private Expression> toDraw;
@@ -34,7 +37,7 @@ public class EffPlayEffect extends Effect {
// for entity effects
private @Nullable Expression entities;
-
+ private Node node;
@Override
@SuppressWarnings("unchecked")
@@ -53,6 +56,7 @@ public boolean init(Expression>[] expressions, int matchedPattern, Kleenean is
}
case 3 -> this.entities = (Expression) expressions[1];
}
+ this.node = getParser().getNode();
return true;
}
@@ -62,9 +66,16 @@ protected void execute(Event event) {
if (this.entities != null) {
Entity[] entities = this.entities.getArray(event);
EntityEffect[] effects = (EntityEffect[]) toDraw.getArray(event);
- for (Entity entity : entities) {
- for (EntityEffect effect : effects) {
- entity.playEffect(effect);
+ for (EntityEffect effect : effects) {
+ boolean played = false;
+ for (Entity entity : entities) {
+ if (effect.isApplicableTo(entity)) {
+ entity.playEffect(effect);
+ played = true;
+ }
+ }
+ if (entities.length > 0 && !played) {
+ warning("Effect " + Classes.toString(effect) + " is not applicable to any of the given entities: (" + Classes.toString(entities, this.entities.getAnd()) + ")");
}
}
return;
@@ -112,4 +123,9 @@ protected void execute(Event event) {
public String toString(@Nullable Event event, boolean debug) {
return "";
}
+
+ @Override
+ public Node getNode() {
+ return node;
+ }
}
diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang
index 6183361126d..1eb1e160d58 100644
--- a/src/main/resources/lang/default.lang
+++ b/src/main/resources/lang/default.lang
@@ -2525,6 +2525,85 @@ game effect:
trial_spawner_become_ominous: trial spawner become ominous effect
trial_spawner_spawn_item: trial spawner spawn item effect
+# -- Entity Effects --
+# all patterns should include the word 'effect' or 'animation'
+entity effect:
+ arrow_particles: tipped arrow particles effect
+ rabbit_jump: rabbit jump effect
+ reset_spawner_minecart_delay: reset spawner minecart delay effect
+ hurt: hurt animation
+ death: death animation
+ egg_break: egg break animation
+ snowball_break: snowball break animation
+ projectile_crack: projectile crack animation
+ entity_death: entity death animation
+ fang_attack: fang attack animation
+ hoglin_attack: hoglin attack animation
+ iron_golen_attack: iron golem attack animation
+ ravager_attack: ravager attack animation
+ warden_attack: warden attack animation
+ zoglin_attack: zoglin attack animation
+ entity_attack: entity attack animation
+ wolf_smoke: wolf smoke effect
+ wolf_hearts: wolf hearts effect
+ taming_failed: taming failed effect
+ taming_succeeded: taming succeeded effect
+ wolf_shake: wolf shake animation
+ sheep_eat: sheep eat animation
+ sheep_eat_grass: sheep eat grass animation
+ tnt_minecart_ignite: ignite tnt minecart animation
+ iron_golem_rose: iron golem rose animation
+ villager_heart: villager heart effect
+ villager_angry: villager angry effect
+ villager_happy: villager happy effect
+ witch_magic: witch magic effect
+ zombie_transform: zombie transform animation
+ firework_explode: firework explode animation
+ love_hearts: love hearts effect
+ squid_rotate: squid rotate animation
+ entity_poof: entity poof effect
+ guardian_target: guardian target effect
+ shield_block: shield block animation
+ shield_break: shield break animation
+ armor_stand_hit: armor stand hit animation
+ thorns_hurt: thorns hurt animation
+ iron_golem_sheath: iron golem sheath animation
+ totem_resurrect: totem resurrect animation
+ protected_from_death: protected from death animation
+ hurt_drown: drown damage effect
+ hurt_explosion: explosion damage effect
+ dolphin_fed: fed dolphin animation
+ ravager_stunned: stunned ravager animation
+ cat_tame_fail: cat tame fail animation
+ cat_tame_success: cat tame success animation
+ trusting_failed: trusting failed
+ trusting_succeeded: trusting succeeded
+ villager_splash: villager splash effect
+ player_bad_omen_raid: player bad omen raid effect
+ hurt_berry_bush: hurt berry bush effect
+ fox_chew: fox chew animation
+ teleport_ender: ender teleport animation
+ break_equipment_main_hand: break equipment main hand effect
+ break_equipment_off_hand: break equipment off hand effect
+ break_equipment_helmet: break equipment helmet effect
+ break_equipment_chestplate: break equipment chestplate effect
+ break_equipment_leggings: break equipment leggings effect
+ break_equipment_boots: break equipment boots effect
+ honey_block_slide_particles: honey block slide particles
+ honey_block_fall_particles: honey block fall particles
+ swap_hand_items: swap hand items effect
+ wolf_shake_stop: wolf shake stop animation
+ goat_lower_head: goat lower head animation
+ goat_raise_head: goat raise head animation
+ spawn_death_smoke: spawn death smoke effect
+ warden_tendril_shake: warden tendril shake animation
+ warden_sonic_attack: warden sonic attack animation
+ sniffer_dig: sniffer dig animation
+ armadillo_peek: armadillo peek animation
+ body_break: break body animation
+ break_equipment_body: break equipment body effect
+ shake: shake animation
+
# -- Teleport Flags --
teleport flags:
retain_open_inventory: opened inventory, open inventory, inventory
@@ -2804,7 +2883,7 @@ types:
fishingstate: fishing state¦s @a
equipmentslot: equipment slot¦s @an
gameeffect: game effect¦s @a
-
+ entityeffect: entity effect¦s @a
# Skript
weathertype: weather type¦s @a
From 74834455d83870b9c96c23c3993e3f358a66f80f Mon Sep 17 00:00:00 2001
From: sovdee <10354869+sovdeeth@users.noreply.github.com>
Date: Mon, 9 Jun 2025 00:23:53 -0700
Subject: [PATCH 08/16] verify + name entityeffects
---
.../njol/skript/entity/SimpleEntityData.java | 1 +
.../elements/effects/EffPlayEffect.java | 42 +++++--
src/main/resources/lang/default.lang | 114 ++++++++++--------
3 files changed, 95 insertions(+), 62 deletions(-)
diff --git a/src/main/java/ch/njol/skript/entity/SimpleEntityData.java b/src/main/java/ch/njol/skript/entity/SimpleEntityData.java
index cb9fe2348e8..6bd92f03f46 100644
--- a/src/main/java/ch/njol/skript/entity/SimpleEntityData.java
+++ b/src/main/java/ch/njol/skript/entity/SimpleEntityData.java
@@ -235,6 +235,7 @@ private static void addSuperEntity(String codeName, Class extends Entity> enti
addSuperEntity("mob", Mob.class);
addSuperEntity("creature", Creature.class);
addSuperEntity("animal", Animals.class);
+ addSuperEntity("tameable", Tameable.class);
addSuperEntity("fish", Fish.class);
addSuperEntity("golem", Golem.class);
addSuperEntity("projectile", Projectile.class);
diff --git a/src/main/java/org/skriptlang/skript/bukkit/particles/elements/effects/EffPlayEffect.java b/src/main/java/org/skriptlang/skript/bukkit/particles/elements/effects/EffPlayEffect.java
index 4e9987b1d79..c89ef81956b 100644
--- a/src/main/java/org/skriptlang/skript/bukkit/particles/elements/effects/EffPlayEffect.java
+++ b/src/main/java/org/skriptlang/skript/bukkit/particles/elements/effects/EffPlayEffect.java
@@ -2,6 +2,7 @@
import ch.njol.skript.Skript;
import ch.njol.skript.config.Node;
+import ch.njol.skript.entity.EntityData;
import ch.njol.skript.lang.Effect;
import ch.njol.skript.lang.Expression;
import ch.njol.skript.lang.SkriptParser.ParseResult;
@@ -13,6 +14,7 @@
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.Event;
+import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.skriptlang.skript.bukkit.particles.GameEffect;
import org.skriptlang.skript.bukkit.particles.ParticleEffect;
@@ -66,18 +68,7 @@ protected void execute(Event event) {
if (this.entities != null) {
Entity[] entities = this.entities.getArray(event);
EntityEffect[] effects = (EntityEffect[]) toDraw.getArray(event);
- for (EntityEffect effect : effects) {
- boolean played = false;
- for (Entity entity : entities) {
- if (effect.isApplicableTo(entity)) {
- entity.playEffect(effect);
- played = true;
- }
- }
- if (entities.length > 0 && !played) {
- warning("Effect " + Classes.toString(effect) + " is not applicable to any of the given entities: (" + Classes.toString(entities, this.entities.getAnd()) + ")");
- }
- }
+ drawEntityEffects(effects, entities);
return;
}
@@ -119,6 +110,33 @@ protected void execute(Event event) {
}
}
+ /**
+ * Helper method to draw entity effects on entities. Provides a runtime warning if no provided entities are applicable
+ * @param effects the effects to draw
+ * @param entities the entities to draw the effects on
+ */
+ private void drawEntityEffects(EntityEffect @NotNull [] effects, Entity @NotNull [] entities) {
+ for (EntityEffect effect : effects) {
+ boolean played = false;
+ for (Entity entity : entities) {
+ if (effect.isApplicableTo(entity)) {
+ entity.playEffect(effect);
+ played = true;
+ }
+ }
+ if (entities.length > 0 && !played) {
+ // todo: cache?
+ String[] applicableClasses = effect.getApplicableClasses().stream()
+ .map(EntityData::toString)
+ .distinct().toArray(String[]::new);
+ assert this.entities != null;
+ warning("The '" + Classes.toString(effect) + "' is not applicable to any of the given entities " +
+ "(" + Classes.toString(entities, this.entities.getAnd()) + "), " +
+ "only to " + Classes.toString(applicableClasses, false) + ".");
+ }
+ }
+ }
+
@Override
public String toString(@Nullable Event event, boolean debug) {
return "";
diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang
index 1eb1e160d58..3c6355dd47c 100644
--- a/src/main/resources/lang/default.lang
+++ b/src/main/resources/lang/default.lang
@@ -2527,82 +2527,96 @@ game effect:
# -- Entity Effects --
# all patterns should include the word 'effect' or 'animation'
+# 'effect' should be used for particles, 'sound effect' for sounds, 'animation' for model animations
entity effect:
arrow_particles: tipped arrow particles effect
- rabbit_jump: rabbit jump effect
+ rabbit_jump: rabbit jump animation
reset_spawner_minecart_delay: reset spawner minecart delay effect
hurt: hurt animation
+
+ # death was superseded by entity_death 1.12.2
+ entity_death: death animation
death: death animation
- egg_break: egg break animation
- snowball_break: snowball break animation
- projectile_crack: projectile crack animation
- entity_death: entity death animation
- fang_attack: fang attack animation
+
+ egg_break: egg break effect
+ snowball_break: snowball break effect
+ projectile_crack: projectile crack effect
+ fang_attack: evoker fang attack animation
hoglin_attack: hoglin attack animation
iron_golen_attack: iron golem attack animation
ravager_attack: ravager attack animation
warden_attack: warden attack animation
zoglin_attack: zoglin attack animation
entity_attack: entity attack animation
- wolf_smoke: wolf smoke effect
- wolf_hearts: wolf hearts effect
+
+ # wolf effects superseded by taming effects 1.21
+ wolf_smoke: taming failed effect
+ wolf_hearts: taming succeeded effect
taming_failed: taming failed effect
taming_succeeded: taming succeeded effect
+
+ trusting_failed: ocelot distrust effect
+ trusting_succeeded: ocelot trust effect
wolf_shake: wolf shake animation
- sheep_eat: sheep eat animation
- sheep_eat_grass: sheep eat grass animation
+ wolf_shake_stop: stop wolf shake animation
tnt_minecart_ignite: ignite tnt minecart animation
iron_golem_rose: iron golem rose animation
+ iron_golem_sheath: iron golem sheath animation
villager_heart: villager heart effect
villager_angry: villager angry effect
villager_happy: villager happy effect
witch_magic: witch magic effect
- zombie_transform: zombie transform animation
+ zombie_transform: zombie transform sound effect
firework_explode: firework explode animation
love_hearts: love hearts effect
- squid_rotate: squid rotate animation
+ squid_rotate: squid rotation reset
entity_poof: entity poof effect
- guardian_target: guardian target effect
- shield_block: shield block animation
- shield_break: shield break animation
- armor_stand_hit: armor stand hit animation
- thorns_hurt: thorns hurt animation
- iron_golem_sheath: iron golem sheath animation
- totem_resurrect: totem resurrect animation
- protected_from_death: protected from death animation
- hurt_drown: drown damage effect
- hurt_explosion: explosion damage effect
- dolphin_fed: fed dolphin animation
- ravager_stunned: stunned ravager animation
- cat_tame_fail: cat tame fail animation
- cat_tame_success: cat tame success animation
- trusting_failed: trusting failed
- trusting_succeeded: trusting succeeded
- villager_splash: villager splash effect
- player_bad_omen_raid: player bad omen raid effect
- hurt_berry_bush: hurt berry bush effect
- fox_chew: fox chew animation
- teleport_ender: ender teleport animation
- break_equipment_main_hand: break equipment main hand effect
- break_equipment_off_hand: break equipment off hand effect
- break_equipment_helmet: break equipment helmet effect
- break_equipment_chestplate: break equipment chestplate effect
- break_equipment_leggings: break equipment leggings effect
- break_equipment_boots: break equipment boots effect
- honey_block_slide_particles: honey block slide particles
- honey_block_fall_particles: honey block fall particles
- swap_hand_items: swap hand items effect
- wolf_shake_stop: wolf shake stop animation
- goat_lower_head: goat lower head animation
- goat_raise_head: goat raise head animation
- spawn_death_smoke: spawn death smoke effect
+ shield_block: shield block sound effect
+ shield_break: shield break sound effect
+ armor_stand_hit: armor stand hit effect
+ dolphin_fed: fed dolphin effect
+ ravager_stunned: stunned ravager effect
+ villager_splash: villager sweat effect
+ player_bad_omen_raid: player bad omen raid effect # pre 1.20.5
+ fox_chew: fox chew effect
+ teleport_ender: ender teleport effect
+ break_equipment_main_hand: break main hand effect
+ break_equipment_off_hand: break off hand effect
+ break_equipment_helmet: break helmet effect
+ break_equipment_chestplate: break chestplate effect
+ break_equipment_leggings: break leggings effect
+ break_equipment_boots: break boots effect
+ body_break: break body armor effect, break body armour effect
+ honey_block_slide_particles: honey block slide effect
+ honey_block_fall_particles: honey block fall effect
+ swap_hand_items: swap hands effect # this actually makes players swap hands
+ goat_lower_head: goat lowering head animation
+ goat_raise_head: goat raising head animation
+ spawn_death_smoke: spawn in smoke effect
warden_tendril_shake: warden tendril shake animation
warden_sonic_attack: warden sonic attack animation
- sniffer_dig: sniffer dig animation
+ sniffer_dig: sniffer dig sound effect # only if the sniffer is already in search/dig state
armadillo_peek: armadillo peek animation
- body_break: break body animation
- break_equipment_body: break equipment body effect
- shake: shake animation
+ shake: creaking shake animation
+
+ # sheep eat superseded by sheep eat grass 1.12.2
+ sheep_eat: sheep eating grass animation
+ sheep_eat_grass: sheep eating grass animation
+
+ # identical
+ totem_resurrect: protected from death effect
+ protected_from_death: protected from death effect
+
+ # non-functional
+ guardian_target: guardian target sound effect
+ thorns_hurt: thorns hurt effect
+ hurt_drown: drown damage effect
+ hurt_explosion: explosion damage effect
+ hurt_berry_bush: hurt berry bush effect
+ cat_tame_fail: cat tame fail effect
+ cat_tame_success: cat tame success effect
+
+
# -- Teleport Flags --
teleport flags:
From 293acfd032655c1a700876126f2003a987280ffd Mon Sep 17 00:00:00 2001
From: sovdee <10354869+sovdeeth@users.noreply.github.com>
Date: Sun, 23 Nov 2025 18:37:39 -0800
Subject: [PATCH 09/16] implement particle effects first pass
---
.../ch/njol/skript/classes/EnumParser.java | 2 +-
.../skript/classes/data/SkriptClasses.java | 58 ++--
.../njol/skript/effects/EffVisualEffect.java | 6 +-
.../bukkit/particles/ParticleEffect.java | 135 +++++++-
.../bukkit/particles/ParticleModule.java | 110 +++++++
.../elements/effects/EffPlayEffect.java | 4 +-
.../expressions/ExprParticleWithData.java | 293 ++++++++++++++++++
src/main/resources/lang/default.lang | 130 +++++++-
8 files changed, 695 insertions(+), 43 deletions(-)
create mode 100644 src/main/java/org/skriptlang/skript/bukkit/particles/elements/expressions/ExprParticleWithData.java
diff --git a/src/main/java/ch/njol/skript/classes/EnumParser.java b/src/main/java/ch/njol/skript/classes/EnumParser.java
index 69d503a130f..e286afe1cc0 100644
--- a/src/main/java/ch/njol/skript/classes/EnumParser.java
+++ b/src/main/java/ch/njol/skript/classes/EnumParser.java
@@ -20,7 +20,7 @@ public class EnumParser> extends PatternedParser implements
private final Class enumClass;
private final String languageNode;
private String[] names;
- private final Map parseMap = new HashMap<>();
+ protected final Map parseMap = new HashMap<>();
private String[] patterns;
/**
diff --git a/src/main/java/ch/njol/skript/classes/data/SkriptClasses.java b/src/main/java/ch/njol/skript/classes/data/SkriptClasses.java
index 87df08749ad..578d0a374a4 100644
--- a/src/main/java/ch/njol/skript/classes/data/SkriptClasses.java
+++ b/src/main/java/ch/njol/skript/classes/data/SkriptClasses.java
@@ -18,8 +18,6 @@
import ch.njol.skript.localization.RegexMessage;
import ch.njol.skript.registrations.Classes;
import ch.njol.skript.util.*;
-import ch.njol.skript.util.visual.VisualEffect;
-import ch.njol.skript.util.visual.VisualEffects;
import ch.njol.yggdrasil.Fields;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -475,34 +473,34 @@ public String toVariableNameString(final Experience xp) {
})
.serializer(new YggdrasilSerializer<>()));
- Classes.registerClass(new ClassInfo<>(VisualEffect.class, "visualeffect")
- .name("Visual Effect")
- .description("A visible effect, e.g. particles.")
- .examples("show wolf hearts on the clicked wolf",
- "play mob spawner flames at the targeted block to the player")
- .usage(VisualEffects.getAllNames())
- .since("2.1")
- .user("(visual|particle) effects?")
- .after("itemtype")
- .parser(new Parser() {
- @Override
- @Nullable
- public VisualEffect parse(String s, ParseContext context) {
- return VisualEffects.parse(s);
- }
-
- @Override
- public String toString(VisualEffect e, int flags) {
- return e.toString(flags);
- }
-
- @Override
- public String toVariableNameString(VisualEffect e) {
- return e.toString();
- }
-
- })
- .serializer(new YggdrasilSerializer<>()));
+// Classes.registerClass(new ClassInfo<>(VisualEffect.class, "visualeffect")
+// .name("Visual Effect")
+// .description("A visible effect, e.g. particles.")
+// .examples("show wolf hearts on the clicked wolf",
+// "play mob spawner flames at the targeted block to the player")
+// .usage(VisualEffects.getAllNames())
+// .since("2.1")
+// .user("(visual|particle) effects?")
+// .after("itemtype")
+// .parser(new Parser() {
+// @Override
+// @Nullable
+// public VisualEffect parse(String s, ParseContext context) {
+// return VisualEffects.parse(s);
+// }
+//
+// @Override
+// public String toString(VisualEffect e, int flags) {
+// return e.toString(flags);
+// }
+//
+// @Override
+// public String toVariableNameString(VisualEffect e) {
+// return e.toString();
+// }
+//
+// })
+// .serializer(new YggdrasilSerializer<>()));
Classes.registerClass(new ClassInfo<>(GameruleValue.class, "gamerulevalue")
.user("gamerule values?")
diff --git a/src/main/java/ch/njol/skript/effects/EffVisualEffect.java b/src/main/java/ch/njol/skript/effects/EffVisualEffect.java
index bd6acbfdef7..f62b0fb3365 100644
--- a/src/main/java/ch/njol/skript/effects/EffVisualEffect.java
+++ b/src/main/java/ch/njol/skript/effects/EffVisualEffect.java
@@ -28,9 +28,9 @@
public class EffVisualEffect extends Effect {
static {
- Skript.registerEffect(EffVisualEffect.class,
- "(play|show) %visualeffects% (on|%directions%) %entities/locations% [(to %-players%|in (radius|range) of %-number%)]",
- "(play|show) %number% %visualeffects% (on|%directions%) %entities/locations% [(to %-players%|in (radius|range) of %-number%)]");
+// Skript.registerEffect(EffVisualEffect.class,
+// "(play|show) %visualeffects% (on|%directions%) %entities/locations% [(to %-players%|in (radius|range) of %-number%)]",
+// "(play|show) %number% %visualeffects% (on|%directions%) %entities/locations% [(to %-players%|in (radius|range) of %-number%)]");
}
@SuppressWarnings("NotNullFieldNotInitialized")
diff --git a/src/main/java/org/skriptlang/skript/bukkit/particles/ParticleEffect.java b/src/main/java/org/skriptlang/skript/bukkit/particles/ParticleEffect.java
index a92dfbd96df..62e704944d7 100644
--- a/src/main/java/org/skriptlang/skript/bukkit/particles/ParticleEffect.java
+++ b/src/main/java/org/skriptlang/skript/bukkit/particles/ParticleEffect.java
@@ -1,17 +1,49 @@
package org.skriptlang.skript.bukkit.particles;
+import ch.njol.skript.Skript;
+import ch.njol.skript.classes.EnumParser;
+import ch.njol.skript.lang.Debuggable;
+import ch.njol.skript.lang.ParseContext;
+import ch.njol.skript.registrations.Classes;
+import ch.njol.skript.util.StringMode;
import org.bukkit.Location;
import org.bukkit.Particle;
import org.bukkit.World;
import org.bukkit.entity.Player;
+import org.bukkit.event.Event;
import org.bukkit.util.Vector;
+import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import java.util.Locale;
+import java.util.Map;
+
/**
* A class to hold particle metadata prior to spawning
*/
-public class ParticleEffect {
+public class ParticleEffect implements Debuggable {
+
+ private static final ParticleParser ENUM_PARSER = new ParticleParser();
+
+ public static ParticleEffect parse(String input, ParseContext context) {
+ Particle particle = ENUM_PARSER.parse(input.toLowerCase(Locale.ENGLISH), context);
+ if (particle == null)
+ return null;
+ if (particle.getDataType() != Void.class) {
+ Skript.error("The " + Classes.toString(particle) + " requires data and cannot be parsed directly. Use the Particle With Data expression instead.");
+ return null;
+ }
+ return new ParticleEffect(particle);
+ }
+
+ public static String toString(Particle particle, int flags) {
+ return ENUM_PARSER.toString(particle, flags);
+ }
+
+ public static String[] getAllNamesWithoutData() {
+ return ENUM_PARSER.getPatternsWithoutData();
+ }
/**
* The base {@link Particle} to use. This determines the properties and what data this {@link ParticleEffect} can accept.
@@ -40,26 +72,34 @@ public class ParticleEffect {
* This, by default, determines the speed at which a particle moves. It must be positive.
*
* When {@link ParticleEffect#count} is 0, this instead acts as a multiplier to the velocity provided by {@link ParticleEffect#offset},
- * or if {@link ParticleEffect#particle} is {@link Particle#SPELL_MOB_AMBIENT} or {@link Particle#SPELL_MOB}, then
+ * or if {@link ParticleEffect#particle} is {@link Particle#ENTITY_EFFECT}, then
* this acts as an exponent to the RGB value provided by {@link ParticleEffect#offset}.
*/
private float extra;
/**
- * This field contains extra data that some particles require. For example, {@link Particle#REDSTONE} requires {@link org.bukkit.Particle.DustOptions}
+ * This determines whether the particle should be visible to players at long range.
+ */
+ private boolean force;
+
+ /**
+ * This field contains extra data that some particles require. For example, {@link Particle#DUST} requires {@link org.bukkit.Particle.DustOptions}
* to determine its size and colour.
*/
@Nullable
private Object data;
- ParticleEffect(Particle particle) {
+ public ParticleEffect(Particle particle) {
this.particle = particle;
this.count = 1;
this.extra = 0;
this.offset = new Vector(0,0,0);
+ this.force = false;
}
public void draw(Location location, boolean force) {
+ if (this.particle.getDataType() != Void.class && !this.particle.getDataType().isInstance(data))
+ return; // data is not compatible with the particle type
World world = location.getWorld();
if (world == null)
return;
@@ -67,9 +107,92 @@ public void draw(Location location, boolean force) {
}
public void drawForPlayer(Location location, Player player, boolean force) {
+ if (this.particle.getDataType() != Void.class && !this.particle.getDataType().isInstance(data))
+ return; // data is not compatible with the particle type
player.spawnParticle(particle, location, count, offset.getX(), offset.getY(), offset.getZ(), extra, data, force);
}
- // TODO: Add parent interface for ParticleEffect, PlayerEffect, EntityEffect? Would make spawning easier, maybe.
- // TODO: add getters, setters, maybe builder class? Add spawn method.
+ public Particle getParticle() {
+ return particle;
+ }
+
+ public ParticleEffect setParticle(Particle particle) {
+ this.particle = particle;
+ return this;
+ }
+
+ public int getCount() {
+ return count;
+ }
+
+ public ParticleEffect setCount(int count) {
+ this.count = count;
+ return this;
+ }
+
+ public Vector getOffset() {
+ return offset;
+ }
+
+ public ParticleEffect setOffset(Vector offset) {
+ this.offset = offset;
+ return this;
+ }
+
+ public float getExtra() {
+ return extra;
+ }
+
+ public ParticleEffect setExtra(float extra) {
+ this.extra = extra;
+ return this;
+ }
+
+ public @Nullable Object getData() {
+ return data;
+ }
+
+ public ParticleEffect setData(@Nullable Object data) {
+ if (data != null && !this.particle.getDataType().isInstance(data))
+ throw new IllegalArgumentException("Data type " + data.getClass().getName() + " is not compatible with particle type " + this.particle.name() + " (expected " + this.particle.getDataType().getName() + ")");
+ this.data = data;
+ return this;
+ }
+
+ public boolean isForce() {
+ return force;
+ }
+
+ public ParticleEffect setForce(boolean force) {
+ this.force = force;
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return toString(null, false);
+ }
+
+ @Override
+ public String toString(@Nullable Event event, boolean debug) {
+ return ENUM_PARSER.toString(particle, 0) + (data != null ? " with data" + Classes.toString(data, debug ? StringMode.DEBUG : StringMode.MESSAGE) : "");
+ }
+
+ private static class ParticleParser extends EnumParser {
+
+ public ParticleParser() {
+ super(Particle.class, "particle");
+ }
+
+ public String @NotNull [] getPatternsWithoutData() {
+ return parseMap.entrySet().stream()
+ .filter(entry -> {
+ Particle particle = entry.getValue();
+ return particle.getDataType() == Void.class;
+ })
+ .map(Map.Entry::getKey)
+ .toArray(String[]::new);
+ }
+
+ }
}
diff --git a/src/main/java/org/skriptlang/skript/bukkit/particles/ParticleModule.java b/src/main/java/org/skriptlang/skript/bukkit/particles/ParticleModule.java
index 6331f17d4c2..fa14d96c2c1 100644
--- a/src/main/java/org/skriptlang/skript/bukkit/particles/ParticleModule.java
+++ b/src/main/java/org/skriptlang/skript/bukkit/particles/ParticleModule.java
@@ -11,6 +11,8 @@
import ch.njol.yggdrasil.Fields;
import org.bukkit.Effect;
import org.bukkit.EntityEffect;
+import org.bukkit.Particle;
+import org.bukkit.util.Vector;
import java.io.IOException;
import java.io.StreamCorruptedException;
@@ -19,6 +21,8 @@
public class ParticleModule {
public static void load () throws IOException {
+
+ // gane effects
Classes.registerClass(new ClassInfo<>(GameEffect.class, "gameeffect")
.user("game ?effects?")
.since("INSERT VERSION")
@@ -86,12 +90,118 @@ public String toVariableNameString(GameEffect o) {
}
}));
+
+ // entity effects
Classes.registerClass(new EnumClassInfo<>(EntityEffect.class, "entityeffect", "entity effect")
.user("entity ?effects?")
.name("Entity Effect")
.description("Various entity effects that can be played for entities, like wolf howling, or villager happy.")
.since("INSERT VERSION"));
+
+
+ // particles
+ Classes.registerClass(new ClassInfo<>(ParticleEffect.class, "particle")
+ .user("particles?")
+ .since("INSERT VERSION")
+ .description("Various particles.")
+ .name("Particle")
+ .usage(ParticleEffect.getAllNamesWithoutData())
+ .supplier(() -> {
+ Particle[] particles = Particle.values();
+ return Arrays.stream(particles).map(ParticleEffect::new).iterator();
+ })
+ .serializer(new Serializer<>() {
+ @Override
+ public Fields serialize(ParticleEffect effect) {
+ Fields fields = new Fields();
+ fields.putPrimitive("name", effect.getParticle().name());
+ fields.putPrimitive("count", effect.getCount());
+ fields.putObject("offset", effect.getOffset());
+ fields.putPrimitive("extra", effect.getExtra());
+ fields.putObject("data", effect.getData());
+ fields.putPrimitive("force", effect.isForce());
+ return fields;
+ }
+
+ @Override
+ public void deserialize(ParticleEffect effect, Fields fields) {
+ assert false;
+ }
+
+ @Override
+ protected ParticleEffect deserialize(Fields fields) throws StreamCorruptedException {
+ String name = fields.getAndRemovePrimitive("name", String.class);
+ ParticleEffect effect;
+ try {
+ effect = new ParticleEffect(Particle.valueOf(name));
+ } catch (IllegalArgumentException e) {
+ return null;
+ }
+ effect.setCount(fields.getAndRemovePrimitive("count", Integer.class));
+ effect.setOffset(fields.getAndRemoveObject("offset", Vector.class));
+ effect.setExtra(fields.getAndRemovePrimitive("extra", Float.class));
+ effect.setData(fields.getAndRemoveObject("data", effect.getParticle().getDataType()));
+ effect.setForce(fields.getAndRemovePrimitive("force", Boolean.class));
+ return effect;
+ }
+
+ @Override
+ public boolean mustSyncDeserialization() {
+ return false;
+ }
+
+ @Override
+ protected boolean canBeInstantiated() {
+ return false;
+ }
+ })
+ .defaultExpression(new EventValueExpression<>(ParticleEffect.class))
+ .parser(new Parser<>() {
+ @Override
+ public ParticleEffect parse(String input, ParseContext context) {
+ return ParticleEffect.parse(input, context);
+ }
+
+ @Override
+ public String toString(ParticleEffect effect, int flags) {
+ return effect.toString();
+ }
+
+ @Override
+ public String toVariableNameString(ParticleEffect effect) {
+ return effect.getParticle().name();
+ }
+ }));
+
+ // bukkit particles, so Classes.toString(Particle) works
+ // Should not be used directly
+ Classes.registerClass(new ClassInfo<>(Particle.class, "bukkitparticle")
+ .user("bukkit ?particles?")
+ .since("INSERT VERSION")
+ .parser(new Parser<>() {
+ @Override
+ public Particle parse(String input, ParseContext context) {
+ throw new IllegalStateException();
+ }
+
+ @Override
+ public boolean canParse(ParseContext context) {
+ return false;
+ }
+
+ @Override
+ public String toString(Particle particle, int flags) {
+ return ParticleEffect.toString(particle, flags);
+ }
+
+ @Override
+ public String toVariableNameString(Particle particle) {
+ return toString(particle, 0);
+ }
+ }));
+
+ // load elements!
Skript.getAddonInstance().loadClasses("org.skriptlang.skript.bukkit.particles", "elements");
}
diff --git a/src/main/java/org/skriptlang/skript/bukkit/particles/elements/effects/EffPlayEffect.java b/src/main/java/org/skriptlang/skript/bukkit/particles/elements/effects/EffPlayEffect.java
index c89ef81956b..ba35003fb5e 100644
--- a/src/main/java/org/skriptlang/skript/bukkit/particles/elements/effects/EffPlayEffect.java
+++ b/src/main/java/org/skriptlang/skript/bukkit/particles/elements/effects/EffPlayEffect.java
@@ -24,8 +24,8 @@
public class EffPlayEffect extends Effect implements SyntaxRuntimeErrorProducer {
static {
Skript.registerEffect(EffPlayEffect.class,
- "[:force] (play|show|draw) %gameeffects% %directions% %locations%",
- "[:force] (play|show|draw) %gameeffects% %directions% %locations% (for|to) %-players%",
+ "[:force] (play|show|draw) %gameeffects/particles% %directions% %locations%",
+ "[:force] (play|show|draw) %gameeffects/particles% %directions% %locations% (for|to) %-players%",
"(play|show|draw) %gameeffects% %directions% %locations% (in|with) [a] [view] (radius|range) of %-number%)",
"(play|show|draw) %entityeffects% on %entities%");
}
diff --git a/src/main/java/org/skriptlang/skript/bukkit/particles/elements/expressions/ExprParticleWithData.java b/src/main/java/org/skriptlang/skript/bukkit/particles/elements/expressions/ExprParticleWithData.java
new file mode 100644
index 00000000000..87670464310
--- /dev/null
+++ b/src/main/java/org/skriptlang/skript/bukkit/particles/elements/expressions/ExprParticleWithData.java
@@ -0,0 +1,293 @@
+package org.skriptlang.skript.bukkit.particles.elements.expressions;
+
+import ch.njol.skript.Skript;
+import ch.njol.skript.aliases.ItemType;
+import ch.njol.skript.lang.Expression;
+import ch.njol.skript.lang.ExpressionType;
+import ch.njol.skript.lang.SkriptParser.ParseResult;
+import ch.njol.skript.lang.util.SimpleExpression;
+import ch.njol.skript.util.Color;
+import ch.njol.skript.util.Patterns;
+import ch.njol.skript.util.Timespan;
+import ch.njol.util.Kleenean;
+import org.bukkit.*;
+import org.bukkit.block.data.BlockData;
+import org.bukkit.entity.Entity;
+import org.bukkit.event.Event;
+import org.bukkit.inventory.ItemStack;
+import org.jetbrains.annotations.Nullable;
+import org.skriptlang.skript.bukkit.particles.ParticleEffect;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+public class ExprParticleWithData extends SimpleExpression {
+
+ private static final Patterns PATTERNS;
+
+ private static final List REGISTERED_PATTERNS = new ArrayList<>();
+
+ private static void registerParticle(Particle particle, String pattern, T defaultData) {
+ registerParticle(particle, pattern, (event, expressions, parseResult) -> {
+ if (expressions[0] == null)
+ return defaultData; // default data if none is provided
+ //noinspection unchecked
+ T data = (T) expressions[0].getSingle(event);
+ if (data == null)
+ return defaultData; // default data if none is provided
+ return data;
+ });
+ }
+
+ private static void registerParticle(Particle particle, String pattern, GetData> getData) {
+ REGISTERED_PATTERNS.add(new ParticlePattern(particle, pattern, getData));
+ }
+
+ static {
+ registerParticle(Particle.EFFECT, "[a[n]] %color% effect particle[s] (of|with) power %number%",
+ //
+ (event, expressions, parseResult) -> {
+ Color color = (Color) expressions[0].getSingle(event);
+ if (color == null)
+ return org.bukkit.Color.WHITE; // default color if none is provided
+ Number power = (Number) expressions[1].getSingle(event);
+ if (power == null)
+ power = 1.0; // default power if none is provided
+ return new Particle.Spell(color.asBukkitColor(), power.floatValue());
+ }
+ //
+ );
+
+ registerParticle(Particle.ENTITY_EFFECT, "[a[n]] %color% (potion|entity) effect particle[s]", org.bukkit.Color.WHITE);
+ registerParticle(Particle.FLASH, "[a[n]] %color% flash particle[s]", org.bukkit.Color.WHITE);
+ registerParticle(Particle.TINTED_LEAVES, "[a[n]] %color% tinted leaves particle[s]", org.bukkit.Color.WHITE);
+
+ registerParticle(Particle.DUST, "[a[n]] %color% dust particle[s] [of size %number%]",
+ //
+ (event, expressions, parseResult) -> {
+ org.bukkit.Color bukkitColor;
+ Color color = (Color) expressions[0].getSingle(event);
+ if (color == null) {
+ bukkitColor = org.bukkit.Color.WHITE; // default color if none is provided
+ } else {
+ bukkitColor = color.asBukkitColor();
+ }
+
+ Number size = (Number) expressions[1].getSingle(event);
+ if (size == null || size.doubleValue() <= 0) {
+ size = 1.0; // default size if none is provided or invalid
+ }
+
+ return new Particle.DustOptions(bukkitColor, size.floatValue());
+ } //
+ );
+
+ // dust color transition particle
+ registerParticle(Particle.DUST_COLOR_TRANSITION, "[a[n]] %color% dust particle[s] [of size %number%] that transitions to %color%",
+ //
+ (event, expressions, parseResult) -> {
+ org.bukkit.Color bukkitColor;
+ Color color = (Color) expressions[0].getSingle(event);
+ if (color == null) {
+ bukkitColor = org.bukkit.Color.WHITE; // default color if none is provided
+ } else {
+ bukkitColor = color.asBukkitColor();
+ }
+
+ Number size = (Number) expressions[1].getSingle(event);
+ if (size == null || size.doubleValue() <= 0) {
+ size = 1.0; // default size if none is provided or invalid
+ }
+
+ Color toColor = (Color) expressions[2].getSingle(event);
+ org.bukkit.Color bukkitToColor;
+ if (toColor == null) {
+ bukkitToColor = org.bukkit.Color.WHITE; // default transition color if none is provided
+ } else {
+ bukkitToColor = toColor.asBukkitColor();
+ }
+
+ return new Particle.DustTransition(bukkitColor, bukkitToColor, size.floatValue());
+ } //
+ );
+
+ registerParticle(Particle.ITEM, "[an] %itemtype% item particle[s]",
+ //
+ (event, expressions, parseResult) -> {
+ ItemType itemType = (ItemType) expressions[0].getSingle(event);
+ if (itemType == null)
+ return new ItemStack(Material.AIR); // default item if none is provided
+ return itemType.getRandom();
+ } //
+ );
+
+ GetData blockdataData = (event, expressions, parseResult) -> {
+ //
+ Object object = expressions[0].getSingle(event);
+ if (object instanceof ItemType itemType) {
+ ItemStack random = itemType.getRandom();
+ return Bukkit.createBlockData(random != null ? random.getType() : itemType.getMaterial());
+ } else if (object instanceof BlockData blockData) {
+ return blockData;
+ }
+ return Bukkit.createBlockData(Material.AIR); // default block if none is provided
+ //
+ };
+ registerParticle(Particle.BLOCK, "[a[n]] %itemtype/blockdata% block particle[s]", blockdataData);
+ registerParticle(Particle.BLOCK_CRUMBLE, "[a[n]] %itemtype/blockdata% [block] crumble particle[s]", blockdataData);
+ registerParticle(Particle.BLOCK_MARKER, "[a[n]] %itemtype/blockdata% [block] marker particle[s]", blockdataData);
+ registerParticle(Particle.DUST_PILLAR, "[a[n]] %itemtype/blockdata% dust pillar particle[s]", blockdataData);
+ registerParticle(Particle.FALLING_DUST, "[a] falling %itemtype/blockdata% dust particle[s]", blockdataData);
+
+ registerParticle(Particle.DRAGON_BREATH, "[a] dragon breath particle[s] [of power %-number%]", 0.5f);
+
+ registerParticle(Particle.SCULK_CHARGE, "[a] sculk charge particle[s] [with angle %-number%]",
+ //
+ (event, expressions, parseResult) -> {
+ if (expressions[0] == null)
+ return 0; // default angle if none is provided
+ Number angle = (Number) expressions[0].getSingle(event);
+ if (angle == null)
+ return 0; // default angle if none is provided
+ return (float) Math.toRadians(angle.floatValue());
+ } //
+ );
+
+ registerParticle(Particle.TRAIL, "[a[n]] %color% trail particle leading to %location% [(for|with [a] duration of) %-timespan%]",
+ //
+ (event, expressions, parseResult) -> {
+ org.bukkit.Color bukkitColor;
+ Color color = (Color) expressions[0].getSingle(event);
+ if (color == null) {
+ bukkitColor = org.bukkit.Color.WHITE; // default color if none is provided
+ } else {
+ bukkitColor = color.asBukkitColor();
+ }
+
+ Location targetLocation = (Location) expressions[1].getSingle(event);
+ if (targetLocation == null)
+ return null;
+
+ Number durationTicks;
+ if (expressions[2] == null) {
+ durationTicks = 20; // default duration of 1 second if none is provided
+ } else {
+ Number duration = (Number) expressions[2].getSingle(event);
+ // default duration of 1 second if none is provided
+ durationTicks = Objects.requireNonNullElse(duration, 20);
+ }
+
+ return new Particle.Trail(targetLocation, bukkitColor, durationTicks.intValue());
+ } //
+ );
+
+ registerParticle(Particle.VIBRATION, "[a] vibration particle moving to %entity/location% over %timespan%",
+ //
+ (event, expressions, parseResult) -> {
+ Object target = expressions[0].getSingle(event);
+ Vibration.Destination destination;
+ if (target instanceof Location location) {
+ destination = new Vibration.Destination.BlockDestination(location);
+ } else if (target instanceof Entity entity) {
+ destination = new Vibration.Destination.EntityDestination(entity);
+ } else {
+ return null;
+ }
+
+ int duration;
+ Timespan timespan = (Timespan) expressions[1].getSingle(event);
+ if (timespan == null) {
+ duration = 20; // default duration of 1 second if none is provided
+ } else {
+ duration = (int) timespan.getAs(Timespan.TimePeriod.TICK);
+ }
+ return new Vibration(destination, duration);
+ } //
+ );
+
+ // create Patterns object
+ Object[][] patterns = new Object[REGISTERED_PATTERNS.size()][2];
+ int i = 0;
+ for (ParticlePattern particlePattern : REGISTERED_PATTERNS) {
+ patterns[i][0] = particlePattern.pattern;
+ patterns[i][1] = particlePattern;
+ i++;
+ }
+ PATTERNS = new Patterns<>(patterns);
+
+ Skript.registerExpression(ExprParticleWithData.class, ParticleEffect.class, ExpressionType.COMBINED, PATTERNS.getPatterns());
+ }
+
+ private ParseResult parseResult;
+ private Expression>[] expressions;
+ private Particle particle;
+ private GetData> getData;
+
+ @Override
+ public boolean init(Expression>[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) {
+ this.parseResult = parseResult;
+ this.expressions = expressions;
+ ParticlePattern particlePattern = PATTERNS.getInfo(matchedPattern);
+ if (particlePattern == null)
+ return false;
+ this.particle = particlePattern.particle;
+ this.getData = particlePattern.getData;
+ return true;
+ }
+
+ @Override
+ protected ParticleEffect @Nullable [] get(Event event) {
+ Object data = getData.getData(event, expressions, parseResult);
+ if (data == null) {
+ error("Could not obtain required data for particle " + particle.name());
+ return null;
+ }
+ ParticleEffect effect = new ParticleEffect(particle);
+ effect.setData(data);
+ return new ParticleEffect[] {effect};
+ }
+
+ @Override
+ public boolean isSingle() {
+ return true;
+ }
+
+ @Override
+ public Class extends ParticleEffect> getReturnType() {
+ return ParticleEffect.class;
+ }
+
+ @Override
+ public String toString(@Nullable Event event, boolean debug) {
+ return "particle with data";
+ }
+
+ /**
+ * A helper class to store a particle and its pattern.
+ *
+ * @param particle The particle
+ * @param pattern The pattern
+ * @param getData The function to get the data from the event
+ */
+ private record ParticlePattern(Particle particle, String pattern, GetData> getData) {}
+
+ /**
+ * A functional interface to get the data from the event.
+ *
+ * @param The type of the data
+ */
+ @FunctionalInterface
+ interface GetData {
+ /**
+ * Get the data from the event.
+ *
+ * @param event The event to evaluate with
+ * @param expressions Any expressions that are used in the pattern
+ * @param parseResult The parse result from parsing
+ * @return The data to use for the effect, or null if the required data could not be obtained
+ */
+ @Nullable T getData(Event event, Expression>[] expressions, ParseResult parseResult);
+ }
+
+}
diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang
index 7265ed48735..60c8710d7af 100644
--- a/src/main/resources/lang/default.lang
+++ b/src/main/resources/lang/default.lang
@@ -1616,6 +1616,9 @@ entities:
animal:
name: animal¦s
pattern: animal[plural:s]
+ tameable:
+ name: tameable creature¦s
+ pattern: tameable creature[1¦s]
fish:
name: fish¦es
pattern: fish[plural:es]
@@ -2890,6 +2893,7 @@ entity effect:
hoglin_attack: hoglin attack animation
iron_golen_attack: iron golem attack animation
ravager_attack: ravager attack animation
+ ravager_roared: ravager roared animation
warden_attack: warden attack animation
zoglin_attack: zoglin attack animation
entity_attack: entity attack animation
@@ -2931,7 +2935,9 @@ entity effect:
break_equipment_chestplate: break chestplate effect
break_equipment_leggings: break leggings effect
break_equipment_boots: break boots effect
+ break_equipment_saddle: break saddle effect
body_break: break body armor effect, break body armour effect
+ break_equipment_body: break body armor effect, break body armour effect
honey_block_slide_particles: honey block slide effect
honey_block_fall_particles: honey block fall effect
swap_hand_items: swap hands effect # this actually makes players swap hands
@@ -2943,6 +2949,10 @@ entity effect:
sniffer_dig: sniffer dig sound effect # only if the sniffer is already in search/dig state
armadillo_peek: armadillo peek animation
shake: creaking shake animation
+
+
+ hurt_drown: drown damage effect
+ drown_particles: drowning damage effect
# sheep eat superseded by sheep eat grass 1.12.2
sheep_eat: sheep eating grass animation
@@ -2955,12 +2965,129 @@ entity effect:
# non-functional
guardian_target: guardian target sound effect
thorns_hurt: thorns hurt effect
- hurt_drown: drown damage effect
hurt_explosion: explosion damage effect
hurt_berry_bush: hurt berry bush effect
cat_tame_fail: cat tame fail effect
cat_tame_success: cat tame success effect
+# -- Particle Effects --
+# all patterns should include the word 'particle'
+particle:
+ angry_villager: angry villager particle
+ ash: ash particle
+ block: block particle
+ block_crumble: block crumble particle
+ block_marker: block marker particle
+ bubble: bubble particle
+ bubble_column_up: bubble column particle
+ bubble_pop: bubble pop particle
+ campfire_cosy_smoke: campfire cosy smoke particle
+ campfire_signal_smoke: campfire signal smoke particle
+ cherry_leaves: cherry leaves particle
+ cloud: cloud particle
+ composter: composter particle
+ copper_fire_flame: copper flame particle
+ crimson_spore: crimson spore particle
+ crit: crit particle
+ current_down: downward current particle
+ damage_indicator: damage indicator particle
+ dolphin: dolphin particle
+ dragon_breath: dragon breath particle
+ dripping_dripstone_lava: dripstone dripping lava particle
+ dripping_dripstone_water: dripstone dripping water particle
+ dripping_honey: dripping honey particle
+ dripping_lava: dripping lava particle
+ dripping_obsidian_tear: dripping obsidian tear particle
+ dripping_water: dripping water particle
+ dust: dust particle
+ dust_color_transition: dust color transition particle
+ dust_pillar: dust pillar particle
+ dust_plume: dust plume particle
+ effect: potion effect particle
+ egg_crack: egg crack particle
+ elder_guardian: elder guardian particle
+ electric_spark: electric spark particle
+ enchant: enchanting particle
+ enchanted_hit: enchanted hit particle
+ end_rod: end rod particle
+ entity_effect: entity effect particle
+ explosion: explosion particle
+ explosion_emitter: explosion emitter particle
+ falling_dripstone_lava: falling dripstone lava particle
+ falling_dripstone_water: falling dripstone water particle
+ falling_dust: falling dust particle
+ falling_honey: falling honey particle
+ falling_lava: falling lava particle
+ falling_nectar: falling nectar particle
+ falling_obsidian_tear: falling obsidian tear particle
+ falling_spore_blossom: falling spore blossom particle
+ falling_water: falling water particle
+ firefly: firefly particle
+ firework: firework particle
+ fishing: fishing particle
+ flame: flame particle
+ flash: flash particle
+ glow: glow particle
+ glow_squid_ink: glow squid ink particle
+ gust: gust particle
+ gust_emitter_large: large gust emitter particle
+ gust_emitter_small: small gust emitter particle
+ happy_villager: happy villager particle
+ heart: heart particle
+ infested: infested particle
+ instant_effect: instant effect particle
+ item: item particle
+ item_cobweb: cobweb item particle
+ item_slime: slime item particle
+ item_snowball: snowball item particle
+ landing_honey: landing honey particle
+ landing_lava: landing lava particle
+ landing_obsidian_tear: landing obsidian tear particle
+ large_smoke: large smoke particle
+ lava: lava particle
+ mycelium: mycelium particle
+ nautilus: nautilus particle
+ note: note particle
+ ominous_spawning: ominous spawning particle
+ pale_oak_leaves: pale oak leaves particle
+ poof: poof particle
+ portal: portal particle
+ raid_omen: raid omen particle
+ rain: rain particle
+ reverse_portal: reverse portal particle
+ scrape: scrape particle
+ sculk_charge: sculk charge particle
+ sculk_charge_pop: sculk charge pop particle
+ sculk_soul: sculk soul particle
+ shriek: shriek particle
+ small_flame: small flame particle
+ small_gust: small gust particle
+ smoke: smoke particle
+ sneeze: sneeze particle
+ snowflake: snowflake particle
+ sonic_boom: sonic boom particle
+ soul: soul particle
+ soul_fire_flame: soul fire flame particle
+ spit: spit particle
+ splash: splash particle
+ spore_blossom_air: spore blossom air particle
+ squid_ink: squid ink particle
+ sweep_attack: sweep attack particle
+ tinted_leaves: tinted leaves particle
+ totem_of_undying: totem of undying particle
+ trail: trail particle
+ trial_omen: trial omen particle
+ trial_spawner_detection: trial spawner detection particle
+ trial_spawner_detection_ominous: ominous trial spawner detection particle
+ underwater: underwater particle
+ vault_connection: vault connection particle
+ vibration: vibration particle
+ warped_spore: warped spore particle
+ wax_off: wax off particle
+ wax_on: wax on particle
+ white_ash: white ash particle
+ white_smoke: white smoke particle
+ witch: witch particle
# -- Teleport Flags --
@@ -3342,6 +3469,7 @@ types:
equippablecomponent: equippable component¦s @an
gameeffect: game effect¦s @a
entityeffect: entity effect¦s @a
+ particle: particle effect¦s @a
# Skript
weathertype: weather type¦s @a
From 251523509ed22861487f94f430410d2fa53a62d9 Mon Sep 17 00:00:00 2001
From: sovdee <10354869+sovdeeth@users.noreply.github.com>
Date: Sat, 29 Nov 2025 21:15:15 -0800
Subject: [PATCH 10/16] rework particles
use particle builder as a base class
separate child impls for properties like directional
add expressions for offset/speed/scale/distribution/velocitty/count
velocity/scale and distribution automatically change count as supported
add serialization for particle datas (required a helper class in yggsdrasil)
better particle info registration for particles with data
---
.../njol/skript/expressions/ExprVelocity.java | 112 ++--
.../ch/njol/skript/expressions/ExprXOf.java | 29 +-
.../njol/yggdrasil/SimpleClassSerializer.java | 71 +++
.../bukkit/particles/ParticleEffect.java | 198 -------
.../bukkit/particles/ParticleModule.java | 518 +++++++++++++++---
.../bukkit/particles/ParticleUtils.java | 156 ++++++
.../elements/effects/EffPlayEffect.java | 34 +-
.../expressions/ExprParticleCount.java | 68 +++
.../expressions/ExprParticleDistribution.java | 64 +++
.../expressions/ExprParticleEffect.java | 50 --
.../expressions/ExprParticleOffset.java | 70 +++
.../expressions/ExprParticleScale.java | 63 +++
.../expressions/ExprParticleSpeed.java | 64 +++
.../expressions/ExprParticleWithData.java | 253 +--------
.../particleeffects/ConvergingEffect.java | 17 +
.../particleeffects/DirectionalEffect.java | 33 ++
.../particleeffects/ParticleEffect.java | 298 ++++++++++
.../particleeffects/ParticleInfo.java | 49 ++
.../particleeffects/ScalableEffect.java | 67 +++
.../regressions/3284-itemcrack particle.sk | 3 -
.../regressions/4769-fire-visualeffect.sk | 18 -
.../tests/syntaxes/effects/EffPlayEffect.sk | 52 ++
.../tests/syntaxes/effects/EffVisualEffect.sk | 108 ----
23 files changed, 1642 insertions(+), 753 deletions(-)
create mode 100644 src/main/java/ch/njol/yggdrasil/SimpleClassSerializer.java
delete mode 100644 src/main/java/org/skriptlang/skript/bukkit/particles/ParticleEffect.java
create mode 100644 src/main/java/org/skriptlang/skript/bukkit/particles/ParticleUtils.java
create mode 100644 src/main/java/org/skriptlang/skript/bukkit/particles/elements/expressions/ExprParticleCount.java
create mode 100644 src/main/java/org/skriptlang/skript/bukkit/particles/elements/expressions/ExprParticleDistribution.java
delete mode 100644 src/main/java/org/skriptlang/skript/bukkit/particles/elements/expressions/ExprParticleEffect.java
create mode 100644 src/main/java/org/skriptlang/skript/bukkit/particles/elements/expressions/ExprParticleOffset.java
create mode 100644 src/main/java/org/skriptlang/skript/bukkit/particles/elements/expressions/ExprParticleScale.java
create mode 100644 src/main/java/org/skriptlang/skript/bukkit/particles/elements/expressions/ExprParticleSpeed.java
create mode 100644 src/main/java/org/skriptlang/skript/bukkit/particles/particleeffects/ConvergingEffect.java
create mode 100644 src/main/java/org/skriptlang/skript/bukkit/particles/particleeffects/DirectionalEffect.java
create mode 100644 src/main/java/org/skriptlang/skript/bukkit/particles/particleeffects/ParticleEffect.java
create mode 100644 src/main/java/org/skriptlang/skript/bukkit/particles/particleeffects/ParticleInfo.java
create mode 100644 src/main/java/org/skriptlang/skript/bukkit/particles/particleeffects/ScalableEffect.java
delete mode 100644 src/test/skript/tests/regressions/3284-itemcrack particle.sk
delete mode 100644 src/test/skript/tests/regressions/4769-fire-visualeffect.sk
create mode 100644 src/test/skript/tests/syntaxes/effects/EffPlayEffect.sk
delete mode 100644 src/test/skript/tests/syntaxes/effects/EffVisualEffect.sk
diff --git a/src/main/java/ch/njol/skript/expressions/ExprVelocity.java b/src/main/java/ch/njol/skript/expressions/ExprVelocity.java
index 3cea900ab22..4dc6c70ad71 100644
--- a/src/main/java/ch/njol/skript/expressions/ExprVelocity.java
+++ b/src/main/java/ch/njol/skript/expressions/ExprVelocity.java
@@ -1,68 +1,100 @@
package ch.njol.skript.expressions;
-import org.bukkit.entity.Entity;
-import org.bukkit.event.Event;
-import org.bukkit.util.Vector;
-import org.jetbrains.annotations.Nullable;
-
import ch.njol.skript.classes.Changer.ChangeMode;
import ch.njol.skript.doc.Description;
-import ch.njol.skript.doc.Examples;
+import ch.njol.skript.doc.Example;
import ch.njol.skript.doc.Name;
import ch.njol.skript.doc.Since;
import ch.njol.skript.expressions.base.SimplePropertyExpression;
import ch.njol.util.coll.CollectionUtils;
+import org.bukkit.entity.Entity;
+import org.bukkit.event.Event;
+import org.bukkit.util.Vector;
+import org.jetbrains.annotations.Nullable;
+import org.joml.Vector3d;
+import org.skriptlang.skript.bukkit.particles.particleeffects.DirectionalEffect;
-/**
- * @author Sashie
- */
-@Name("Vectors - Velocity")
-@Description("Gets or changes velocity of an entity.")
-@Examples({"set player's velocity to {_v}"})
+// TODO: replace with type property expression
+@Name("Velocity")
+@Description({
+ "Gets or changes velocity of an entity or particle.",
+ "Setting the velocity of a particle will remove its random dispersion and force it to be a single particle."
+})
+@Example("set player's velocity to {_v}")
+@Example("set the velocity of {_particle} to vector(0, 1, 0)")
+@Example("""
+ if the vector length of the player's velocity is greater than 5:
+ send "You're moving fast!" to the player
+ """)
@Since("2.2-dev31")
-public class ExprVelocity extends SimplePropertyExpression {
+public class ExprVelocity extends SimplePropertyExpression