Skip to content

Commit

Permalink
Particle Compatibility Improvements (SkriptLang#6716)
Browse files Browse the repository at this point in the history
* first pass at updating lang entries

* Rewrite Particle lang entries

A lot of particles probably don't work though

* Add a test

* Fix block marker particles

* Add support for multiple expressions in a particle pattern

* first pass at new data suppliers

* Fix a few issues related to new suppliers

* Data supplier touchups

* Fix broken particles

---------

Co-authored-by: Moderocky <admin@moderocky.com>
  • Loading branch information
2 people authored and sovdeeth committed May 30, 2024
1 parent 800eaba commit 4f878c3
Show file tree
Hide file tree
Showing 5 changed files with 760 additions and 377 deletions.
28 changes: 22 additions & 6 deletions src/main/java/ch/njol/skript/util/visual/VisualEffect.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,13 @@
import ch.njol.skript.lang.Expression;
import ch.njol.skript.lang.SkriptParser.ParseResult;
import ch.njol.skript.lang.SyntaxElement;
import ch.njol.skript.lang.util.ContextlessEvent;
import ch.njol.util.Kleenean;
import ch.njol.yggdrasil.YggdrasilSerializable;
import org.bukkit.Bukkit;
import org.bukkit.Effect;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Particle;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
Expand All @@ -36,8 +39,6 @@

public class VisualEffect implements SyntaxElement, YggdrasilSerializable {

private static final boolean HAS_REDSTONE_DATA = Skript.classExists("org.bukkit.Particle$DustOptions");

private VisualEffectType type;

@Nullable
Expand All @@ -46,14 +47,29 @@ public class VisualEffect implements SyntaxElement, YggdrasilSerializable {
private float dX, dY, dZ = 0f;

public VisualEffect() {}

@SuppressWarnings({"null", "ConstantConditions"})
@Override
public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) {
type = VisualEffects.get(matchedPattern);

if (exprs.length > 4 && exprs[0] != null) {
data = exprs[0].getSingle(null);
if (exprs.length > 4) {
int exprCount = exprs.length - 4; // some effects might have multiple expressions
ContextlessEvent event = ContextlessEvent.get();
if (exprCount == 1) {
data = exprs[0] != null ? exprs[0].getSingle(event) : null;
} else { // provide an array of expression values
Object[] dataArray = new Object[exprCount];
for (int i = 0; i < exprCount; i++)
dataArray[i] = exprs[i] != null ? exprs[i].getSingle(event) : null;
data = dataArray;
}
}

if (parseResult.hasTag("barrierbm")) { // barrier compatibility
data = Bukkit.createBlockData(Material.BARRIER);
} else if (parseResult.hasTag("lightbm")) { // light compatibility
data = Bukkit.createBlockData(Material.LIGHT);
}

if ((parseResult.mark & 1) != 0) {
Expand Down Expand Up @@ -100,7 +116,7 @@ public void play(@Nullable Player[] ps, Location l, @Nullable Entity e, int coun
}

// Some particles use offset as RGB color codes
if (type.isColorable() && (!HAS_REDSTONE_DATA || particle != (VisualEffects.OLD_REDSTONE_PARTICLE != null ? VisualEffects.OLD_REDSTONE_PARTICLE : Particle.DUST)) && data instanceof ParticleOption) {
if (type.isColorable() && data instanceof ParticleOption) {
ParticleOption option = ((ParticleOption) data);
dX = option.getRed();
dY = option.getGreen();
Expand Down
200 changes: 119 additions & 81 deletions src/main/java/ch/njol/skript/util/visual/VisualEffects.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
package ch.njol.skript.util.visual;

import ch.njol.skript.Skript;
import ch.njol.skript.aliases.Aliases;
import ch.njol.skript.aliases.ItemType;
import ch.njol.skript.bukkitutil.ItemUtils;
import ch.njol.skript.lang.SkriptParser;
import ch.njol.skript.lang.SyntaxElementInfo;
import ch.njol.skript.localization.Language;
Expand All @@ -29,13 +29,14 @@
import ch.njol.skript.util.ColorRGB;
import ch.njol.skript.util.Direction;
import ch.njol.skript.util.SkriptColor;
import ch.njol.skript.util.Timespan;
import ch.njol.skript.variables.Variables;
import ch.njol.util.StringUtils;
import ch.njol.util.coll.iterator.SingleItemIterator;
import org.bukkit.*;
import org.bukkit.block.BlockFace;
import org.bukkit.entity.Entity;
import org.bukkit.inventory.ItemStack;
import org.bukkit.material.MaterialData;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import org.eclipse.jdt.annotation.Nullable;
Expand All @@ -53,7 +54,6 @@
public class VisualEffects {

private static final boolean NEW_EFFECT_DATA = Skript.classExists("org.bukkit.block.data.BlockData");
private static final boolean HAS_REDSTONE_DATA = Skript.classExists("org.bukkit.Particle$DustOptions");

private static final Map<String, Consumer<VisualEffectType>> effectTypeModifiers = new HashMap<>();
private static SyntaxElementInfo<VisualEffect> elementInfo;
Expand Down Expand Up @@ -120,37 +120,15 @@ private static void registerDataSupplier(String id, BiFunction<Object, Location,
effectTypeModifiers.put(id, consumer);
}

@Nullable
static final Particle OLD_REDSTONE_PARTICLE;
@Nullable
static final Particle OLD_ITEM_CRACK_PARTICLE;

static {
Particle oldRedstoneParticle = null;
Particle oldItemCrackParticle = null;
if (Skript.fieldExists(Particle.class, "REDSTONE")) { // initialize legacy particle support
try {
oldRedstoneParticle = Particle.valueOf("REDSTONE");
oldItemCrackParticle = Particle.valueOf("ITEM_CRACK");
} catch (IllegalArgumentException e) {
oldRedstoneParticle = null;
oldItemCrackParticle = null;
Skript.exception(e, "Failed to initialize legacy particle support. Some particles may not work as expected.");
}
}
OLD_REDSTONE_PARTICLE = oldRedstoneParticle;
OLD_ITEM_CRACK_PARTICLE = oldItemCrackParticle;
}
// only applies to some older versions where ITEM_CRACK exists
private static final boolean IS_ITEM_CRACK_MATERIAL =
Skript.fieldExists(Particle.class, "ITEM_CRACK")
&& Particle.valueOf("ITEM_CRACK").getDataType() == Material.class;

static {
Language.addListener(() -> {
if (visualEffectTypes != null) // Already registered
return;
// Colorables
registerColorable("Particle.SPELL_MOB");
registerColorable("Particle.SPELL_MOB_AMBIENT");
registerColorable("Particle.REDSTONE");
registerColorable("Particle.NOTE");

// Data suppliers
registerDataSupplier("Effect.POTION_BREAK", (raw, location) ->
Expand All @@ -161,72 +139,132 @@ private static void registerDataSupplier(String id, BiFunction<Object, Location,
return Direction.getFacing(((Direction) raw).getDirection(location), false);
});

Color defaultColor = SkriptColor.LIGHT_RED;
registerDataSupplier("Particle.SPELL_MOB", (raw, location) -> {
Color color = raw == null ? defaultColor : (Color) raw;
return new ParticleOption(color, 1);
// Useful: https://minecraft.wiki/w/Particle_format

/*
* Particles with BlockData DataType
*/
final BiFunction<Object, Location, Object> blockDataSupplier = (raw, location) -> {
if (raw instanceof Object[]) { // workaround for modern pattern since it contains a choice
Object[] data = (Object[]) raw;
raw = data[0] != null ? data[0] : data[1];
}
if (raw == null)
return Bukkit.createBlockData(Material.AIR);
if (raw instanceof ItemType)
return Bukkit.createBlockData(((ItemType) raw).getRandom().getType());
return raw;
};
registerDataSupplier("Particle.BLOCK", blockDataSupplier);
registerDataSupplier("Particle.BLOCK_CRACK", blockDataSupplier);
registerDataSupplier("Particle.BLOCK_DUST", blockDataSupplier);

registerDataSupplier("Particle.BLOCK_MARKER", blockDataSupplier);

registerDataSupplier("Particle.DUST_PILLAR", blockDataSupplier);

registerDataSupplier("Particle.FALLING_DUST", blockDataSupplier);

/*
* Particles with DustOptions DataType
*/
final Color defaultColor = SkriptColor.LIGHT_RED;
final BiFunction<Object, Location, Object> dustOptionsSupplier = (raw, location) -> {
Object[] data = (Object[]) raw;
Color color = data[0] != null ? (Color) data[0] : defaultColor;
float size = data[1] != null ? (Float) data[1] : 1;
return new Particle.DustOptions(color.asBukkitColor(), size);
};
registerDataSupplier("Particle.DUST", dustOptionsSupplier);
registerDataSupplier("Particle.REDSTONE", dustOptionsSupplier);

/*
* Particles with Color DataType
*/
registerDataSupplier("Particle.ENTITY_EFFECT", (raw, location) -> {
if (raw == null)
return defaultColor.asBukkitColor();
return ((Color) raw).asBukkitColor();
});
registerDataSupplier("Particle.SPELL_MOB_AMBIENT", (raw, location) -> {
Color color = raw == null ? defaultColor : (Color) raw;
final BiFunction<Object, Location, Object> oldColorSupplier = (raw, location) -> {
Color color = raw != null ? (Color) raw : defaultColor;
return new ParticleOption(color, 1);
};
registerColorable("Particle.SPELL_MOB");
registerDataSupplier("Particle.SPELL_MOB", oldColorSupplier);
registerColorable("Particle.SPELL_MOB_AMBIENT");
registerDataSupplier("Particle.SPELL_MOB_AMBIENT", oldColorSupplier);

final BiFunction<Object, Location, Object> itemStackSupplier = (raw, location) -> {
ItemStack itemStack = null;
if (raw instanceof ItemType)
itemStack = ((ItemType) raw).getRandom();
if (itemStack == null || ItemUtils.isAir(itemStack.getType())) // item crack air is not allowed
itemStack = new ItemStack(Material.IRON_SWORD);
if (IS_ITEM_CRACK_MATERIAL)
return itemStack.getType();
return itemStack;
};
registerDataSupplier("Particle.ITEM", itemStackSupplier);
registerDataSupplier("Particle.ITEM_CRACK", itemStackSupplier);

/*
* Particles with other DataTypes
*/
registerDataSupplier("Particle.DUST_COLOR_TRANSITION", (raw, location) -> {
Object[] data = (Object[]) raw;
Color fromColor = data[0] != null ? (Color) data[0] : defaultColor;
Color toColor = data[1] != null ? (Color) data[1] : defaultColor;
float size = data[2] != null ? (Float) data[2] : 1;
return new Particle.DustTransition(fromColor.asBukkitColor(), toColor.asBukkitColor(), size);
});
registerDataSupplier("Particle.REDSTONE", (raw, location) -> {
Color color = raw == null ? defaultColor : (Color) raw;
ParticleOption particleOption = new ParticleOption(color, 1);

if (HAS_REDSTONE_DATA && (OLD_REDSTONE_PARTICLE == null || OLD_REDSTONE_PARTICLE.getDataType() == Particle.DustOptions.class)) {
return new Particle.DustOptions(particleOption.getBukkitColor(), particleOption.size);
} else {
return particleOption;
}
});

// uses color differently
registerColorable("Particle.NOTE");
// TODO test how this works
registerDataSupplier("Particle.NOTE", (raw, location) -> {
int colorValue = (int) (((Number) raw).floatValue() * 255);
ColorRGB color = new ColorRGB(colorValue, 0, 0);
return new ParticleOption(color, 1);
});
registerDataSupplier("Particle.ITEM_CRACK", (raw, location) -> {
ItemStack itemStack = Aliases.javaItemType("iron sword").getRandom();
if (raw instanceof ItemType) {
ItemStack rand = ((ItemType) raw).getRandom();
if (rand != null)
itemStack = rand;
} else if (raw != null) {
return raw;
}

assert itemStack != null;
if (OLD_ITEM_CRACK_PARTICLE == null || OLD_ITEM_CRACK_PARTICLE.getDataType() == Material.class)
return itemStack.getType();
return itemStack;
// Float DataType, represents "the angle the particle displays at in radians"
registerDataSupplier("Particle.SCULK_CHARGE", (raw, location) -> raw != null ? raw : 0);

// Integer DataType, represents "the delay in ticks"
registerDataSupplier("Particle.SHRIEK", (raw, location) -> {
int delay = 0;
if (raw instanceof Timespan)
delay = (int) Math.min(Math.max(((Timespan) raw).getTicks(), 0), Integer.MAX_VALUE);
return delay;
});

BiFunction<Object, Location, Object> crackDustBiFunction = (raw, location) -> {
if (raw == null) {
return Material.STONE.getData();
} else if (raw instanceof ItemType) {
ItemStack rand = ((ItemType) raw).getRandom();
if (NEW_EFFECT_DATA) {
return Bukkit.createBlockData(rand != null ? rand.getType() : Material.STONE);
} else {
if (rand == null)
return Material.STONE.getData();

@SuppressWarnings("deprecation")
MaterialData type = rand.getData();
assert type != null;
return type;
}
} else {
return raw;
}
};
registerDataSupplier("Particle.BLOCK_CRACK", crackDustBiFunction);
registerDataSupplier("Particle.BLOCK_DUST", crackDustBiFunction);
registerDataSupplier("Particle.FALLING_DUST", crackDustBiFunction);
registerDataSupplier("Particle.VIBRATION", (raw, location) -> VibrationUtils.buildVibration((Object[]) raw, location));

generateTypes();
});
}

// exists to avoid NoClassDefFoundError from Vibration
private static final class VibrationUtils {
private static Vibration buildVibration(Object[] data, Location location) {
int arrivalTime = -1;
if (data[1] != null)
arrivalTime = (int) Math.min(Math.max(((Timespan) data[1]).getTicks(), 0), Integer.MAX_VALUE);
if (data[0] instanceof Entity) {
Entity entity = (Entity) data[0];
if (arrivalTime == -1)
arrivalTime = (int) (location.distance(entity.getLocation()) / 20);
//noinspection removal - new constructor only exists on newer versions
return new Vibration(location, new Vibration.Destination.EntityDestination(entity), arrivalTime);
}
// assume it's a location
Location destination = data[0] != null ? (Location) data[0] : location;
if (arrivalTime == -1)
arrivalTime = (int) (location.distance(destination) / 20);
//noinspection removal - new constructor only exists on newer versions
return new Vibration(location, new Vibration.Destination.BlockDestination(destination), arrivalTime);
}
}

}
Loading

0 comments on commit 4f878c3

Please sign in to comment.