diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index 616c20b3e98..8250ee17f75 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -100,6 +100,7 @@ import org.skriptlang.skript.bukkit.loottables.LootTableModule; import org.skriptlang.skript.bukkit.registration.BukkitRegistryKeys; import org.skriptlang.skript.bukkit.registration.BukkitSyntaxInfos; +import org.skriptlang.skript.bukkit.spawners.SpawnerModule; import org.skriptlang.skript.bukkit.tags.TagModule; import org.skriptlang.skript.lang.comparator.Comparator; import org.skriptlang.skript.lang.comparator.Comparators; @@ -585,7 +586,7 @@ public void onEnable() { TagModule.load(); FurnaceModule.load(); LootTableModule.load(); - skript.loadModules(new DamageSourceModule()); + skript.loadModules(new DamageSourceModule(), new SpawnerModule()); } 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/classes/data/SkriptClasses.java b/src/main/java/ch/njol/skript/classes/data/SkriptClasses.java index 22539ac0187..3854fd7ded9 100644 --- a/src/main/java/ch/njol/skript/classes/data/SkriptClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/SkriptClasses.java @@ -13,10 +13,7 @@ import ch.njol.skript.lang.ParseContext; import ch.njol.skript.lang.function.DynamicFunctionReference; import ch.njol.skript.lang.util.SimpleLiteral; -import ch.njol.skript.lang.util.common.AnyAmount; -import ch.njol.skript.lang.util.common.AnyContains; -import ch.njol.skript.lang.util.common.AnyNamed; -import ch.njol.skript.lang.util.common.AnyValued; +import ch.njol.skript.lang.util.common.*; import ch.njol.skript.localization.Noun; import ch.njol.skript.localization.RegexMessage; import ch.njol.skript.registrations.Classes; @@ -964,6 +961,14 @@ public String toVariableNameString(DynamicFunctionReference function) { .examples("{a} contains {b}") .since("2.10") ); + + Classes.registerClass(new AnyInfo<>(AnyWeighted.class, "weighted") + .name("Any Weighted Thing") + .description("Something that has a weight.") + .usage("") + .examples("the weight of {spawner entry}") + .since("INSERT VERSION") + ); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprSpawnerType.java b/src/main/java/ch/njol/skript/expressions/ExprSpawnerType.java deleted file mode 100644 index d9e720ae2e8..00000000000 --- a/src/main/java/ch/njol/skript/expressions/ExprSpawnerType.java +++ /dev/null @@ -1,118 +0,0 @@ -package ch.njol.skript.expressions; - -import ch.njol.skript.Skript; -import ch.njol.skript.bukkitutil.EntityUtils; -import ch.njol.skript.classes.Changer.ChangeMode; -import ch.njol.skript.doc.Description; -import ch.njol.skript.doc.Example; -import ch.njol.skript.doc.Name; -import ch.njol.skript.doc.RequiredPlugins; -import ch.njol.skript.doc.Since; -import ch.njol.skript.entity.EntityData; -import ch.njol.skript.expressions.base.SimplePropertyExpression; -import ch.njol.util.coll.CollectionUtils; -import org.bukkit.block.Block; -import org.bukkit.block.CreatureSpawner; -import org.bukkit.block.TrialSpawner; -import org.bukkit.entity.EntityType; -import org.bukkit.event.Event; -import org.bukkit.spawner.TrialSpawnerConfiguration; -import org.jetbrains.annotations.Nullable; - -@Name("Spawner Type") -@Description(""" - The entity type of a spawner (mob spawner). - Change the entity type, reset it (pig) or clear it (Minecraft 1.20.0+). - """) -@Example(""" - on right click: - if event-block is a spawner: - send "Spawner's type if %spawner type of event-block%" to player - """) -@Example("set the creature type of {_spawner} to a trader llama") -@Example("reset {_spawner}'s entity type # Pig") -@Example("clear the spawner type of {_spawner} # Minecraft 1.20.0+") -@Since("2.4, 2.9.2 (trial spawner), 2.12 (delete)") -@RequiredPlugins("Minecraft 1.20.0+ (delete)") -public class ExprSpawnerType extends SimplePropertyExpression { - - private static final boolean HAS_TRIAL_SPAWNER = Skript.classExists("org.bukkit.block.TrialSpawner"); - private static final boolean RUNNING_1_20_0 = Skript.isRunningMinecraft(1, 20, 0); - - static { - register(ExprSpawnerType.class, EntityData.class, "(spawner|entity|creature) type[s]", "blocks"); - } - - @Nullable - public EntityData convert(Block block) { - if (block.getState() instanceof CreatureSpawner creatureSpawner) { - EntityType type = creatureSpawner.getSpawnedType(); - if (type == null) - return null; - return EntityUtils.toSkriptEntityData(type); - } else if (HAS_TRIAL_SPAWNER && block.getState() instanceof TrialSpawner trialSpawner) { - EntityType type; - if (trialSpawner.isOminous()) { - type = trialSpawner.getOminousConfiguration().getSpawnedType(); - } else { - type = trialSpawner.getNormalConfiguration().getSpawnedType(); - } - if (type == null) - return null; - return EntityUtils.toSkriptEntityData(type); - } - return null; - } - - @Override - public Class @Nullable [] acceptChange(ChangeMode mode) { - if (mode == ChangeMode.SET || mode == ChangeMode.RESET) { - return CollectionUtils.array(EntityData.class); - } else if (mode == ChangeMode.DELETE) { - if (RUNNING_1_20_0) { - return CollectionUtils.array(EntityData.class); - } else { - Skript.error("You can only delete the spawner type of a spawner on Minecraft 1.20.0 or newer"); - } - } - return null; - } - - @Override - public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { - EntityType entityType = null; - if (delta != null) { - //noinspection rawtypes - entityType = EntityUtils.toBukkitEntityType(((EntityData) delta[0])); - } else if (mode == ChangeMode.RESET) { - entityType = EntityType.PIG; - } - - for (Block block : getExpr().getArray(event)) { - if (block.getState() instanceof CreatureSpawner creatureSpawner) { - creatureSpawner.setSpawnedType(entityType); - creatureSpawner.update(); // Actually trigger the spawner's update - } else if (HAS_TRIAL_SPAWNER && block.getState() instanceof TrialSpawner trialSpawner) { - TrialSpawnerConfiguration config; - if (trialSpawner.isOminous()) { - config = trialSpawner.getOminousConfiguration(); - } else { - config = trialSpawner.getNormalConfiguration(); - } - config.setSpawnedType(entityType); - trialSpawner.update(); - } - } - } - - @Override - public Class getReturnType() { - return EntityData.class; - } - - @Override - protected String getPropertyName() { - return "entity type"; - } - -} diff --git a/src/main/java/ch/njol/skript/expressions/ExprWeight.java b/src/main/java/ch/njol/skript/expressions/ExprWeight.java new file mode 100644 index 00000000000..673a6ac64c2 --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprWeight.java @@ -0,0 +1,74 @@ +package ch.njol.skript.expressions; + +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +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.skript.lang.util.common.AnyWeighted; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; + +@Name("Weight") +@Description(""" + Returns the weight of a weighted object. If supported, this weight can be modified. + """) +@Example(""" + broadcast weight of {_spawner entry} + set weight of {_spawner entry} to 5 + add 5 to weight of {_spawner entry} + remove 2 from weight of {_spawner entry} + """) +@Since("INSERT VERSION") +public class ExprWeight extends SimplePropertyExpression { + + static { + register(ExprWeight.class, Number.class, "weight[s]", "weighteds"); + } + + @Override + public @Nullable Number convert(AnyWeighted weighted) { + return weighted.weight(); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + return switch (mode) { + case SET, ADD, REMOVE -> CollectionUtils.array(Number.class); + default -> null; + }; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + assert delta != null; + + Number deltaValue = (Number) delta[0]; + for (AnyWeighted weighted : getExpr().getArray(event)) { + if (!weighted.supportsWeightChange()) + error("This object does not support weight modification."); + + Number newValue = switch (mode) { + case SET -> deltaValue; + case ADD -> weighted.weight().doubleValue() + deltaValue.doubleValue(); + case REMOVE -> weighted.weight().doubleValue() - deltaValue.doubleValue(); + default -> 0; + }; + + weighted.setWeight(newValue); + } + } + + @Override + public Class getReturnType() { + return Number.class; + } + + @Override + protected String getPropertyName() { + return "weight"; + } + +} diff --git a/src/main/java/ch/njol/skript/lang/util/common/AnyWeighted.java b/src/main/java/ch/njol/skript/lang/util/common/AnyWeighted.java new file mode 100644 index 00000000000..dddcf05ed13 --- /dev/null +++ b/src/main/java/ch/njol/skript/lang/util/common/AnyWeighted.java @@ -0,0 +1,42 @@ +package ch.njol.skript.lang.util.common; + +import org.jetbrains.annotations.UnknownNullability; + +/** + * A provider for anything with a weight + * Anything implementing this (or convertible to this) can be used by the {@link ch.njol.skript.expressions.ExprWeight} + * property expression. + * + * @see AnyProvider + */ +@FunctionalInterface +public interface AnyWeighted extends AnyProvider { + + /** + * @return This thing's weight + */ + @UnknownNullability Number weight(); + + /** + * This is called before {@link #setWeight(Number)}. + * If the result is false, setting the weight will never be attempted. + * + * @return Whether this supports being set + */ + default boolean supportsWeightChange() { + return false; + } + + /** + * The behaviour for changing this thing's weight, if possible. + * If not possible, then {@link #supportsWeightChange()} should return false and this + * may throw an error. + * + * @param weight The weight to change + * @throws UnsupportedOperationException If this is impossible + */ + default void setWeight(Number weight) throws UnsupportedOperationException { + throw new UnsupportedOperationException(); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/spawners/SpawnerModule.java b/src/main/java/org/skriptlang/skript/bukkit/spawners/SpawnerModule.java new file mode 100644 index 00000000000..a9ab9e17d5e --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/spawners/SpawnerModule.java @@ -0,0 +1,313 @@ +package org.skriptlang.skript.bukkit.spawners; + +import ch.njol.skript.Skript; +import ch.njol.skript.bukkitutil.EntityUtils; +import ch.njol.skript.classes.ClassInfo; +import ch.njol.skript.classes.Parser; +import ch.njol.skript.classes.Serializer; +import ch.njol.skript.classes.YggdrasilSerializer; +import ch.njol.skript.entity.EntityData; +import ch.njol.skript.expressions.base.EventValueExpression; +import ch.njol.skript.lang.ParseContext; +import ch.njol.skript.lang.SyntaxElement; +import ch.njol.skript.registrations.Classes; +import ch.njol.skript.registrations.EventValues; +import ch.njol.yggdrasil.Fields; +import com.destroystokyo.paper.event.entity.PreSpawnerSpawnEvent; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.block.Block; +import org.bukkit.block.spawner.SpawnRule; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntitySnapshot; +import org.bukkit.event.entity.SpawnerSpawnEvent; +import org.bukkit.event.entity.TrialSpawnerSpawnEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.loot.LootTable; +import org.skriptlang.skript.addon.AddonModule; +import org.skriptlang.skript.addon.SkriptAddon; +import org.skriptlang.skript.bukkit.spawners.util.SkriptSpawnerEntry; +import org.skriptlang.skript.bukkit.spawners.util.events.MobSpawnerDataEvent; +import org.skriptlang.skript.bukkit.spawners.util.events.SpawnRuleEvent; +import org.skriptlang.skript.bukkit.spawners.util.events.SpawnerEntryEvent; +import org.skriptlang.skript.bukkit.spawners.util.events.TrialSpawnerDataEvent; +import org.skriptlang.skript.bukkit.spawners.util.spawnerdata.SkriptMobSpawnerData; +import org.skriptlang.skript.bukkit.spawners.util.spawnerdata.SkriptSpawnerData; +import org.skriptlang.skript.bukkit.spawners.util.spawnerdata.SkriptTrialSpawnerData; +import org.skriptlang.skript.registration.SyntaxRegistry; +import org.skriptlang.skript.util.ClassLoader; + +import java.io.StreamCorruptedException; +import java.lang.reflect.InvocationTargetException; +import java.util.HashMap; +import java.util.Map; +import java.util.StringJoiner; + +@SuppressWarnings("UnstableApiUsage") +public class SpawnerModule implements AddonModule { + + @Override + public void init(SkriptAddon addon) { + Classes.registerClass(new ClassInfo<>(SkriptSpawnerData.class, "spawnerdata") + .user("spawner ?datas?") + .name("Spawner Data") + .description(""" + Represents the common data of a mob spawner or a trial spawner, including spawner entries, minimum \ + and maximum spawn delays, and more. + """) + .defaultExpression(new EventValueExpression<>(SkriptSpawnerData.class)) + .since("INSERT VERSION") + ); + + Classes.registerClass(new ClassInfo<>(SkriptMobSpawnerData.class, "mobspawnerdata") + .user("mob ?spawner ?datas?") + .name("Mob Spawner Data") + .description(""" + Represents the mob spawner data that can be contained in a monster spawner or a spawner minecart. \ + Additional information can be found on \ + the Minecraft wiki page about mob spawners. + """) + .since("INSERT VERSION") + .defaultExpression(new EventValueExpression<>(SkriptMobSpawnerData.class)) + .parser(new Parser<>() { + @Override + public boolean canParse(ParseContext context) { + return false; + } + + @Override + public String toString(SkriptMobSpawnerData data, int flags) { + return "mob spawner data"; + } + + @Override + public String toVariableNameString(SkriptMobSpawnerData data) { + return "mob_spawner_data:" + data.hashCode(); + } + }) + .serializer(new YggdrasilSerializer<>()) + ); + + Classes.registerClass(new ClassInfo<>(SkriptTrialSpawnerData.class, "trialspawnerdata") + .user("trial ?spawner ?datas?") + .name("Trial Spawner Data") + .description(""" + Represents the static data of a trial spawner, including details such as \ + activation range, reward entries, and more. + Additional information can be found on \ + the Minecraft wiki page about trial spawners. + """) + .since("INSERT VERSION") + .defaultExpression(new EventValueExpression<>(SkriptTrialSpawnerData.class)) + .parser(new Parser<>() { + @Override + public boolean canParse(ParseContext context) { + return false; + } + + @Override + public String toString(SkriptTrialSpawnerData data, int flags) { + return "trial spawner data"; + } + + @Override + public String toVariableNameString(SkriptTrialSpawnerData data) { + return "trial_spawner_data:" + data.hashCode(); + } + }) + .serializer(new YggdrasilSerializer<>()) + ); + + Classes.registerClass(new ClassInfo<>(SkriptSpawnerEntry.class, "spawnerentry") + .user("spawner ?entr(y|ies)") + .name("Spawner Entry") + .description(""" + A spawner entry represents what entity can spawn from a spawner, including details such as \ + spawn rules, spawn weight, and equipment. + More information about spawner entries can be found on \ + the Minecraft wiki page about spawners. + """) + .since("INSERT VERSION") + .defaultExpression(new EventValueExpression<>(SkriptSpawnerEntry.class)) + .parser(new Parser<>() { + @Override + public boolean canParse(ParseContext context) { + return false; + } + + @Override + public String toString(SkriptSpawnerEntry entry, int flags) { + return "spawner entry of " + Classes.toString(entry.getEntitySnapshot()); + } + + @Override + public String toVariableNameString(SkriptSpawnerEntry entry) { + return "spawner_entry:" + entry.hashCode(); + } + }) + .serializer(new Serializer<>() { + @Override + public Fields serialize(SkriptSpawnerEntry entry) { + Fields fields = new Fields(); + + fields.putPrimitive("weight", entry.weight()); + fields.putObject("entity_snapshot", entry.getEntitySnapshot()); + fields.putObject("spawn_rule", entry.getSpawnRule()); + fields.putObject("equipment_loot_table", entry.getEquipmentLootTable()); + + int count = 0; + for (var entrySet : entry.getDropChances().entrySet()) { + fields.putObject("equipment_slot_" + count, entrySet.getKey()); + fields.putPrimitive("chance_" + count, entrySet.getValue()); + count++; + } + + return fields; + } + + @Override + public void deserialize(SkriptSpawnerEntry entry, Fields fields) { + assert false; + } + + @Override + protected SkriptSpawnerEntry deserialize(Fields fields) throws StreamCorruptedException { + //noinspection DataFlowIssue + SkriptSpawnerEntry entry = new SkriptSpawnerEntry(fields.getObject("entity_snapshot", EntitySnapshot.class)); + entry.setWeight(fields.getPrimitive("weight", int.class)); + entry.setSpawnRule(fields.getObject("spawn_rule", SpawnRule.class)); + entry.setEquipmentLootTable(fields.getObject("equipment_loot_table", LootTable.class)); + + Map dropChances = new HashMap<>(); + int count = 0; + while (fields.contains("equipment_slot_" + count)) { + EquipmentSlot slot = fields.getObject("equipment_slot_" + count, EquipmentSlot.class); + float chance = fields.getPrimitive("chance_" + count, float.class); + dropChances.put(slot, chance); + count++; + } + + entry.setDropChances(dropChances); + return entry; + } + + @Override + public boolean mustSyncDeserialization() { + return true; + } + + @Override + protected boolean canBeInstantiated() { + return false; + } + }) + ); + + Classes.registerClass(new ClassInfo<>(SpawnRule.class, "spawnrule") + .user("spawn ?rules?") + .name("Spawn Rule") + .description(""" + Spawn rules specify the light levels required for a spawner entry to spawn. \ + More information can be found on \ + the Minecraft wiki page about spawners. + """) + .since("INSERT VERSION") + .defaultExpression(new EventValueExpression<>(SpawnRule.class)) + .parser(new Parser<>() { + @Override + public boolean canParse(ParseContext context) { + return false; + } + + @Override + public String toString(SpawnRule rule, int flags) { + StringJoiner joiner = new StringJoiner(" ", "spawn rule with", ""); + joiner.add("min block light " + rule.getMinBlockLight() + ','); + joiner.add("max block light " + rule.getMaxBlockLight() + ','); + joiner.add("min sky light " + rule.getMinSkyLight() + ", and"); + joiner.add("max sky light " + rule.getMaxSkyLight()); + return joiner.toString(); + } + + @Override + public String toVariableNameString(SpawnRule rule) { + return "spawn rule:" + + rule.getMinBlockLight() + ',' + + rule.getMaxBlockLight() + ',' + + rule.getMinSkyLight() + ',' + + rule.getMaxSkyLight(); + } + }) + .serializer(new Serializer<>() { + @Override + public Fields serialize(SpawnRule rule) { + Fields fields = new Fields(); + fields.putPrimitive("min_block_light", rule.getMinBlockLight()); + fields.putPrimitive("max_block_light", rule.getMaxBlockLight()); + fields.putPrimitive("min_sky_light", rule.getMinSkyLight()); + fields.putPrimitive("max_sky_light", rule.getMaxSkyLight()); + return fields; + } + + @Override + public void deserialize(SpawnRule rule, Fields fields) { + assert false; + } + + @Override + protected SpawnRule deserialize(Fields fields) throws StreamCorruptedException { + int minBlockLight = fields.getPrimitive("min_block_light", int.class); + int maxBlockLight = fields.getPrimitive("max_block_light", int.class); + int minSkyLight = fields.getPrimitive("min_sky_light", int.class); + int maxSkyLight = fields.getPrimitive("max_sky_light", int.class); + return new SpawnRule(minBlockLight, maxBlockLight, minSkyLight, maxSkyLight); + } + + @Override + public boolean mustSyncDeserialization() { + return true; + } + + @Override + protected boolean canBeInstantiated() { + return false; + } + }) + ); + + EventValues.registerEventValue(TrialSpawnerDataEvent.class, SkriptTrialSpawnerData.class, TrialSpawnerDataEvent::getSpawnerData); + EventValues.registerEventValue(MobSpawnerDataEvent.class, SkriptMobSpawnerData.class, MobSpawnerDataEvent::getSpawnerData); + EventValues.registerEventValue(SpawnerEntryEvent.class, SkriptSpawnerEntry.class, SpawnerEntryEvent::getSpawnerEntry); + EventValues.registerEventValue(SpawnRuleEvent.class, SpawnRule.class, SpawnRuleEvent::getSpawnRule); + + EventValues.registerEventValue(SpawnerSpawnEvent.class, Location.class, SpawnerSpawnEvent::getLocation); + EventValues.registerEventValue(SpawnerSpawnEvent.class, Entity.class, SpawnerSpawnEvent::getEntity); + + EventValues.registerEventValue(TrialSpawnerSpawnEvent.class, Block.class, event -> event.getTrialSpawner().getBlock()); + EventValues.registerEventValue(TrialSpawnerSpawnEvent.class, Location.class, TrialSpawnerSpawnEvent::getLocation); + EventValues.registerEventValue(TrialSpawnerSpawnEvent.class, Entity.class, TrialSpawnerSpawnEvent::getEntity); + + EventValues.registerEventValue(PreSpawnerSpawnEvent.class, EntityData.class, + event -> EntityUtils.toSkriptEntityData(event.getType())); + } + + @Override + public void load(SkriptAddon addon) { + ClassLoader.builder() + .basePackage("org.skriptlang.skript.bukkit.spawners.elements") + .deep(true) + .initialize(true) + .forEachClass(clazz -> { + if (SyntaxElement.class.isAssignableFrom(clazz)) { + try { + clazz.getMethod("register", SyntaxRegistry.class).invoke(null, addon.syntaxRegistry()); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + Bukkit.getLogger().severe("Failed to load syntax class: " + e); + } + } + }) + .build() + .loadClasses(Skript.class, Skript.getAddonInstance().getFile()); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/conditions/CondIsActivated.java b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/conditions/CondIsActivated.java new file mode 100644 index 00000000000..56cfe8d901a --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/conditions/CondIsActivated.java @@ -0,0 +1,51 @@ +package org.skriptlang.skript.bukkit.spawners.elements.conditions; + +import ch.njol.skript.conditions.base.PropertyCondition; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import org.bukkit.block.data.type.TrialSpawner; +import org.bukkit.block.data.type.TrialSpawner.State; +import org.skriptlang.skript.bukkit.spawners.util.SpawnerUtils; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@Name("Is Active Spawner") +@Description(""" + Checks whether a spawner is active. An active spawner must have a player in its activation range, the sky and \ + block light spawn levels must match the requirements, and there must be an entity type, snapshot or spawner entry \ + assigned to the spawner. + """) +@Example(""" + if the block at player is an active spawner: + send "The spawner is activated!" to player + """) +@Since("INSERT VERSION") +public class CondIsActivated extends PropertyCondition { + + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.CONDITION, infoBuilder(CondIsActivated.class, PropertyType.BE, + "[an] (activated|active) spawner", "blocks/entities") + .supplier(CondIsActivated::new) + .build() + ); + } + + @Override + public boolean check(Object object) { + if (SpawnerUtils.isMobSpawner(object)) { + return SpawnerUtils.getMobSpawner(object).isActivated(); + } else if (SpawnerUtils.isTrialSpawner(object)) { + TrialSpawner trialSpawner = (TrialSpawner) SpawnerUtils.getTrialSpawner(object).getBlockData(); + return trialSpawner.getTrialSpawnerState() == State.ACTIVE; + } + + return false; + } + + @Override + protected String getPropertyName() { + return "an activated spawner"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/conditions/CondIsMobSpawner.java b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/conditions/CondIsMobSpawner.java new file mode 100644 index 00000000000..80a5a019ddf --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/conditions/CondIsMobSpawner.java @@ -0,0 +1,41 @@ +package org.skriptlang.skript.bukkit.spawners.elements.conditions; + +import ch.njol.skript.conditions.base.PropertyCondition; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import org.skriptlang.skript.bukkit.spawners.util.SpawnerUtils; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@Name("Is Mob Spawner") +@Description(""" + Checks whether a block or entity is a mob spawner (monster spawner or spawner minecart). + """) +@Example(""" + broadcast whether event-block is a mob spawner + """) +@Example(""" + if event-entity is a mob spawner: + broadcast "%event-entity% is a mob spawner!" + """) +public class CondIsMobSpawner extends PropertyCondition { + + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.CONDITION, infoBuilder(CondIsMobSpawner.class, PropertyType.BE, + "[a] mob spawner", "blocks/entities") + .supplier(CondIsMobSpawner::new) + .build() + ); + } + + @Override + public boolean check(Object spawnerObject) { + return SpawnerUtils.isMobSpawner(spawnerObject); + } + + @Override + protected String getPropertyName() { + return "a mob spawner"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/conditions/CondIsOminous.java b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/conditions/CondIsOminous.java new file mode 100644 index 00000000000..599e301ccc5 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/conditions/CondIsOminous.java @@ -0,0 +1,50 @@ +package org.skriptlang.skript.bukkit.spawners.elements.conditions; + +import ch.njol.skript.conditions.base.PropertyCondition; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import org.skriptlang.skript.bukkit.spawners.util.SpawnerUtils; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@Name("Trial Spawner Is Ominous") +@Description(""" + Checks whether a trial spawner is ominous. This can also be used with trial spawner block datas. + """) +@Example(""" + if the block at player is ominous: + send "The trial spawner is ominous!" to player + """) +@Example(""" + set {_data} to block data of block at player + send whether {_data} is ominous to player + """) +@Since("INSERT VERSION") +public class CondIsOminous extends PropertyCondition { + + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.CONDITION, infoBuilder(CondIsOminous.class, PropertyType.BE, + "ominous", "blocks/blockdatas") + .supplier(CondIsOminous::new) + .build() + ); + } + + @Override + public boolean check(Object object) { + if (SpawnerUtils.isTrialSpawner(object)) { + return SpawnerUtils.getTrialSpawner(object).isOminous(); + } else if (object instanceof org.bukkit.block.data.type.TrialSpawner spawner) { + return spawner.isOminous(); + } + + return false; + } + + @Override + protected String getPropertyName() { + return "ominous"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/conditions/CondIsTracking.java b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/conditions/CondIsTracking.java new file mode 100644 index 00000000000..d058e64d212 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/conditions/CondIsTracking.java @@ -0,0 +1,94 @@ +package org.skriptlang.skript.bukkit.spawners.elements.conditions; + +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Condition; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.SyntaxStringBuilder; +import ch.njol.util.Kleenean; +import org.bukkit.block.Block; +import org.bukkit.block.TrialSpawner; +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.spawners.util.SpawnerUtils; +import org.skriptlang.skript.registration.SyntaxInfo; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@Name("Trial Spawner Is Tracking") +@Description(""" + Checks whether a trial spawner is tracking the entity or player. + Tracked players are players that have joined the battle by stepping into the trial spawner's activation range. \ + Meanwhile, tracked entities (non-players) are entities that were spawned by the trial spawner. + """) +@Example(""" + force the event-block to start tracking player + if the event-block is tracking player: + send "indeed! you are being tracked.." + """) +@Since("INSERT VERSION") +public class CondIsTracking extends Condition { + + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.CONDITION, SyntaxInfo.builder(CondIsTracking.class) + .supplier(CondIsTracking::new) + .priority(SyntaxInfo.COMBINED) + .addPatterns( + "%blocks% (is|are) tracking %entities%", + "%blocks% (isn't|is not|aren't|are not) tracking %entities%") + .build() + ); + } + + private Expression spawners; + private Expression entities; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + //noinspection unchecked + spawners = (Expression) exprs[0]; + //noinspection unchecked + entities = (Expression) exprs[1]; + setNegated(matchedPattern == 1); + return true; + } + + @Override + public boolean check(Event event) { + return spawners.check(event, block -> { + if (!SpawnerUtils.isTrialSpawner(block)) + return false; + + TrialSpawner spawner = SpawnerUtils.getTrialSpawner(block); + + return entities.check(event, entity -> { + if (entity instanceof Player player) { + return spawner.isTrackingPlayer(player); + } else { + return spawner.isTrackingEntity(entity); + } + }); + + }, isNegated()); + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + SyntaxStringBuilder builder = new SyntaxStringBuilder(event, debug); + + builder.append(spawners); + if (isNegated()) { + builder.append("aren't"); + } else { + builder.append("are"); + } + builder.append("tracking", entities); + + return builder.toString(); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/effects/EffEjectReward.java b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/effects/EffEjectReward.java new file mode 100644 index 00000000000..388f7b933bb --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/effects/EffEjectReward.java @@ -0,0 +1,68 @@ +package org.skriptlang.skript.bukkit.spawners.elements.effects; + +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Effect; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import org.bukkit.block.Block; +import org.bukkit.block.data.type.TrialSpawner; +import org.bukkit.block.data.type.TrialSpawner.State; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.spawners.util.SpawnerUtils; +import org.skriptlang.skript.registration.SyntaxInfo; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@Name("Eject Trial Spawner Rewards") +@Description(""" + Make a trial spawner eject its rewards. + """) +@Example(""" + spit out the trial rewards from event-block + """) +@Since("INSERT VERSION") +public class EffEjectReward extends Effect { + + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.EFFECT, SyntaxInfo.builder(EffEjectReward.class) + .supplier(EffEjectReward::new) + .priority(SyntaxInfo.COMBINED) + .addPattern("(spit out|eject) [the] trial reward[s] from %blocks%") + .build() + ); + } + + private Expression blocks; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + //noinspection unchecked + blocks = (Expression) exprs[0]; + return true; + } + + @Override + protected void execute(Event event) { + for (Block block : blocks.getArray(event)) { + if (!SpawnerUtils.isTrialSpawner(block)) + continue; + + org.bukkit.block.TrialSpawner state = SpawnerUtils.getTrialSpawner(block); + TrialSpawner data = (TrialSpawner) state.getBlockData(); + + data.setTrialSpawnerState(State.EJECTING_REWARD); + + state.setBlockData(data); + } + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "spit out the trial rewards from " + blocks.toString(event, debug); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/effects/EffMakeOminous.java b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/effects/EffMakeOminous.java new file mode 100644 index 00000000000..e7f2005cb72 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/effects/EffMakeOminous.java @@ -0,0 +1,79 @@ +package org.skriptlang.skript.bukkit.spawners.elements.effects; + +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Effect; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.SyntaxStringBuilder; +import ch.njol.util.Kleenean; +import org.bukkit.block.Block; +import org.bukkit.block.data.type.TrialSpawner; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.registration.SyntaxInfo; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@Name("Trial Spawner State") +@Description("Make a trial spawner or a trial spawner block data ominous or normal.") +@Example(""" + make event-block ominous + make event-block normal + """) +@Example(""" + force event-block to be ominous + force event-block to be regular + """) +@Since("INSERT VERSION") +public class EffMakeOminous extends Effect { + + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.EFFECT, SyntaxInfo.builder(EffMakeOminous.class) + .supplier(EffMakeOminous::new) + .priority(SyntaxInfo.COMBINED) + .addPatterns( + "make %blocks/blockdatas% (:ominous|regular|normal)", + "force %blocks/blockdatas% to be (:ominous|regular|normal)") + .build() + ); + } + + private boolean ominous; + private Expression spawners; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + ominous = parseResult.hasTag("ominous"); + spawners = exprs[0]; + return true; + } + + @Override + protected void execute(Event event) { + for (Object object : spawners.getArray(event)) { + if (object instanceof TrialSpawner spawner) { + spawner.setOminous(ominous); + } else if (object instanceof Block block && block.getState() instanceof org.bukkit.block.TrialSpawner spawner) { + spawner.setOminous(ominous); + spawner.update(true, false); + } + } + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + SyntaxStringBuilder builder = new SyntaxStringBuilder(event, debug); + + builder.append("make", spawners); + if (ominous) { + builder.append("ominous"); + } else { + builder.append("normal"); + } + + return builder.toString(); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/effects/EffSpawnerItem.java b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/effects/EffSpawnerItem.java new file mode 100644 index 00000000000..50e6e87b771 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/effects/EffSpawnerItem.java @@ -0,0 +1,81 @@ +package org.skriptlang.skript.bukkit.spawners.elements.effects; + +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.lang.Effect; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import org.bukkit.block.CreatureSpawner; +import org.bukkit.event.Event; +import org.bukkit.inventory.ItemStack; +import org.bukkit.spawner.Spawner; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.spawners.util.SpawnerUtils; +import org.skriptlang.skript.registration.SyntaxInfo; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@Name("Modify Spawner Item") +@Description(""" + Make a mob spawner spawn items rather than entities. In this case, the mob spawner's spawn count determines \ + how many stacks are spawned, not how many items should be in each stack. + """) +@Example(""" + make event-block spawn 15 diamonds + modify the mob spawner data of event-block: + set the spawn count to 3 + # This will now spawn 3 stacks of 15 diamonds each + """) +@Example(""" + force event-block to spawn 5 diamonds + force event-block to spawn a diamond sword + """) +public class EffSpawnerItem extends Effect { + + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.EFFECT, SyntaxInfo.builder(EffSpawnerItem.class) + .supplier(EffSpawnerItem::new) + .priority(SyntaxInfo.COMBINED) + .addPatterns( + "make %blocks/entities% spawn %itemstack%", + "force %blocks/entities% to spawn %itemstack%") + .build() + ); + } + + private Expression spawners; + private Expression itemStack; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + spawners = exprs[0]; + //noinspection unchecked + itemStack = (Expression) exprs[1]; + return true; + } + + @Override + protected void execute(Event event) { + ItemStack item = itemStack.getSingle(event); + if (item == null) + return; + + for (Object object : spawners.getArray(event)) { + if (!SpawnerUtils.isMobSpawner(object)) + continue; + + Spawner mobSpawner = SpawnerUtils.getMobSpawner(object); + mobSpawner.setSpawnedItem(item); + + if (mobSpawner instanceof CreatureSpawner creatureSpawner) + creatureSpawner.update(true, false); + } + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "force " + spawners.toString(event, debug) + " to spawn " + itemStack.toString(event, debug); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/effects/EffTrialSpawnerTrack.java b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/effects/EffTrialSpawnerTrack.java new file mode 100644 index 00000000000..1a57e471ab5 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/effects/EffTrialSpawnerTrack.java @@ -0,0 +1,106 @@ +package org.skriptlang.skript.bukkit.spawners.elements.effects; + +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Effect; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.SyntaxStringBuilder; +import ch.njol.util.Kleenean; +import org.bukkit.block.Block; +import org.bukkit.block.TrialSpawner; +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.spawners.util.SpawnerUtils; +import org.skriptlang.skript.registration.SyntaxInfo; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@Name("Trial Spawner Tracking") +@Description(""" + Make a trial spawner start or stop tracking an entity. + Tracked players are players that have joined the battle by stepping into the trial spawner's activation range. \ + Meanwhile, tracked entities (non-players) are entities that were spawned by the trial spawner. + Note that the trial spawner may decide to start or stop tracking entities at any given time. + """) +@Example(""" + make event-block start tracking player + force event-block to stop tracking player + """) +@Since("INSERT VERSION") +public class EffTrialSpawnerTrack extends Effect { + + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.EFFECT, SyntaxInfo.builder(EffTrialSpawnerTrack.class) + .supplier(EffTrialSpawnerTrack::new) + .priority(SyntaxInfo.COMBINED) + .addPatterns( + "make %blocks% (:start|stop) tracking %entities%", + "force %blocks% to (:start|stop) tracking %entities%") + .build() + ); + } + + private boolean start; + private Expression blocks; + private Expression entities; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + start = parseResult.hasTag("start"); + //noinspection unchecked + blocks = (Expression) exprs[0]; + //noinspection unchecked + entities = (Expression) exprs[1]; + return true; + } + + @Override + protected void execute(Event event) { + for (Block spawner : blocks.getArray(event)) { + if (!SpawnerUtils.isTrialSpawner(spawner)) + continue; + + TrialSpawner trialSpawner = SpawnerUtils.getTrialSpawner(spawner); + + assert trialSpawner != null; + + for (Entity entity : entities.getArray(event)) { + if (entity instanceof Player player) { + if (start) { + trialSpawner.startTrackingPlayer(player); + } else { + trialSpawner.stopTrackingPlayer(player); + } + } else { + if (start) { + trialSpawner.startTrackingEntity(entity); + } else { + trialSpawner.stopTrackingEntity(entity); + } + } + } + + trialSpawner.update(true, false); + } + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + SyntaxStringBuilder builder = new SyntaxStringBuilder(event, debug); + + builder.append("make", blocks); + if (start) { + builder.append("start"); + } else { + builder.append("stop"); + } + builder.append("tracking", entities); + + return builder.toString(); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/events/EvtPreSpawnerSpawn.java b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/events/EvtPreSpawnerSpawn.java new file mode 100644 index 00000000000..280ffff309e --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/events/EvtPreSpawnerSpawn.java @@ -0,0 +1,66 @@ +package org.skriptlang.skript.bukkit.spawners.elements.events; + +import ch.njol.skript.bukkitutil.EntityUtils; +import ch.njol.skript.entity.EntityData; +import ch.njol.skript.lang.Literal; +import ch.njol.skript.lang.SkriptEvent; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.SyntaxStringBuilder; +import com.destroystokyo.paper.event.entity.PreSpawnerSpawnEvent; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.registration.BukkitRegistryKeys; +import org.skriptlang.skript.bukkit.registration.BukkitSyntaxInfos; +import org.skriptlang.skript.registration.SyntaxInfo; +import org.skriptlang.skript.registration.SyntaxRegistry; + +public class EvtPreSpawnerSpawn extends SkriptEvent { + + public static void register(SyntaxRegistry registry) { + registry.register(BukkitRegistryKeys.EVENT, BukkitSyntaxInfos.Event.builder(EvtPreSpawnerSpawn.class, "Pre Mob Spawner Spawn") + .priority(SyntaxInfo.COMBINED) + .supplier(EvtPreSpawnerSpawn::new) + .addEvent(PreSpawnerSpawnEvent.class) + .addPatterns("pre spawner spawn [of %-entitydatas%]") + .addDescription(""" + This event is called right before a mob spawner spawns an entity. + """) + .addExamples( + "on pre spawner spawn:", + "on pre spawner spawn of a pig:") + .addSince("INSERT VERSION") + .build() + ); + } + + private Literal> entityDatas; + + @Override + public boolean init(Literal[] args, int matchedPattern, ParseResult parseResult) { + //noinspection unchecked + entityDatas = (Literal>) args[0]; + return true; + } + + @Override + public boolean check(Event event) { + if (entityDatas != null && event instanceof PreSpawnerSpawnEvent preEvent) { + EntityData currentData = EntityUtils.toSkriptEntityData(preEvent.getType()); + return entityDatas.stream(event).anyMatch(entityData -> entityData.isSupertypeOf(currentData)); + } + + return true; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + SyntaxStringBuilder builder = new SyntaxStringBuilder(event, debug); + + builder.append("pre spawner spawn"); + if (entityDatas != null) + builder.append("of", entityDatas); + + return builder.toString(); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/events/EvtSpawnerSpawn.java b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/events/EvtSpawnerSpawn.java new file mode 100644 index 00000000000..e4bf6339488 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/events/EvtSpawnerSpawn.java @@ -0,0 +1,84 @@ +package org.skriptlang.skript.bukkit.spawners.elements.events; + +import ch.njol.skript.bukkitutil.EntityUtils; +import ch.njol.skript.entity.EntityData; +import ch.njol.skript.lang.Literal; +import ch.njol.skript.lang.SkriptEvent; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.SyntaxStringBuilder; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.event.Event; +import org.bukkit.event.entity.EntityEvent; +import org.bukkit.event.entity.SpawnerSpawnEvent; +import org.bukkit.event.entity.TrialSpawnerSpawnEvent; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.registration.BukkitRegistryKeys; +import org.skriptlang.skript.bukkit.registration.BukkitSyntaxInfos; +import org.skriptlang.skript.bukkit.spawners.util.SpawnerDataType; +import org.skriptlang.skript.registration.SyntaxInfo; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@SuppressWarnings("UnstableApiUsage") +public class EvtSpawnerSpawn extends SkriptEvent { + + public static void register(SyntaxRegistry registry) { + registry.register(BukkitRegistryKeys.EVENT, BukkitSyntaxInfos.Event.builder(EvtSpawnerSpawn.class, "Spawner Spawn") + .priority(SyntaxInfo.COMBINED) + .supplier(EvtSpawnerSpawn::new) + .addEvents(CollectionUtils.array(SpawnerSpawnEvent.class, TrialSpawnerSpawnEvent.class)) + .addPattern("[:trial|:mob] spawner spawn[ing] [of %-entitydatas%]") + .addDescription(""" + This event is called when a mob spawner or a trial spawner spawns an entity. + """) + .addExamples( + "on trial spawner spawning of a zombie:", + "on mob spawner spawn:", + "on spawner spawn of a skeleton:") + .addSince("INSERT VERSION") + .build() + ); + } + + private SpawnerDataType type; + private Literal> entityDatas; + + @Override + public boolean init(Literal[] args, int matchedPattern, ParseResult parseResult) { + type = SpawnerDataType.fromTags(parseResult.tags); + //noinspection unchecked + entityDatas = (Literal>) args[0]; + return true; + } + + + @Override + public boolean check(Event event) { + if (entityDatas != null && event instanceof EntityEvent entityEvent) { + EntityData currentData = EntityUtils.toSkriptEntityData(entityEvent.getEntityType()); + boolean match = entityDatas.stream(event).anyMatch(entityData -> entityData.isSupertypeOf(currentData)); + + if (!match) + return false; + } + + if (type.isTrial()) { + return event instanceof TrialSpawnerSpawnEvent; + } else if (type.isMob()) { + return event instanceof SpawnerSpawnEvent; + } + + return true; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + SyntaxStringBuilder builder = new SyntaxStringBuilder(event, debug); + + builder.append(type.toString(), "spawner spawn"); + if (entityDatas != null) + builder.append("of", entityDatas); + + return builder.toString(); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/ExprSpawnLocation.java b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/ExprSpawnLocation.java new file mode 100644 index 00000000000..58c7c526b90 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/ExprSpawnLocation.java @@ -0,0 +1,74 @@ +package org.skriptlang.skript.bukkit.spawners.elements.expressions; + +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.EventRestrictedSyntax; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; +import com.destroystokyo.paper.event.entity.PreSpawnerSpawnEvent; +import org.bukkit.Location; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.registration.SyntaxInfo; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@Name("Spawn Location") +@Description(""" + Returns the spawned entity's spawn location in the pre-spawner-spawn event. + """) +@Example(""" + on pre spawner spawn: + broadcast the spawner entity's spawn location + """) +@Since("INSERT VERSION") +public class ExprSpawnLocation extends SimpleExpression implements EventRestrictedSyntax { + + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.EXPRESSION, SyntaxInfo.Expression.builder(ExprSpawnLocation.class, Location.class) + .supplier(ExprSpawnLocation::new) + .priority(SyntaxInfo.SIMPLE) + .addPatterns( + "[the] spawn location of [the] spawner entity", + "[the] spawner entity's spawn location") + .build() + ); + } + + @Override + public boolean init(Expression[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + return true; + } + + @Override + public Class[] supportedEvents() { + return CollectionUtils.array(PreSpawnerSpawnEvent.class); + } + + @Override + protected Location @Nullable [] get(Event event) { + if (!(event instanceof PreSpawnerSpawnEvent spawnEvent)) + return null; + return new Location[]{spawnEvent.getSpawnLocation()}; + } + + @Override + public boolean isSingle() { + return true; + } + + @Override + public Class getReturnType() { + return Location.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "the entity's spawn location"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/ExprSpawnerLocation.java b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/ExprSpawnerLocation.java new file mode 100644 index 00000000000..11f17b70b57 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/ExprSpawnerLocation.java @@ -0,0 +1,74 @@ +package org.skriptlang.skript.bukkit.spawners.elements.expressions; + +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.EventRestrictedSyntax; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; +import com.destroystokyo.paper.event.entity.PreSpawnerSpawnEvent; +import org.bukkit.Location; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.registration.SyntaxInfo; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@Name("Spawner Location") +@Description(""" + Returns the spawner's location in the pre-spawner-spawn event. + """) +@Example(""" + on pre spawner spawn: + broadcast the spawner's location + """) +@Since("INSERT VERSION") +public class ExprSpawnerLocation extends SimpleExpression implements EventRestrictedSyntax { + + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.EXPRESSION, SyntaxInfo.Expression.builder(ExprSpawnerLocation.class, Location.class) + .supplier(ExprSpawnerLocation::new) + .priority(SyntaxInfo.SIMPLE) + .addPatterns( + "[the] spawner's location", + "[the] location of [the] spawner") + .build() + ); + } + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + return true; + } + + @Override + public Class[] supportedEvents() { + return CollectionUtils.array(PreSpawnerSpawnEvent.class); + } + + @Override + protected Location @Nullable [] get(Event event) { + if (!(event instanceof PreSpawnerSpawnEvent spawnEvent)) + return null; + return new Location[]{spawnEvent.getSpawnerLocation()}; + } + + @Override + public boolean isSingle() { + return true; + } + + @Override + public Class getReturnType() { + return Location.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "the spawner's location"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/ExprSpawnDelay.java b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/ExprSpawnDelay.java new file mode 100644 index 00000000000..1323e448ebe --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/ExprSpawnDelay.java @@ -0,0 +1,120 @@ +package org.skriptlang.skript.bukkit.spawners.elements.expressions.spawner; + +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.*; +import ch.njol.skript.expressions.base.SimplePropertyExpression; +import ch.njol.skript.util.Timespan; +import ch.njol.skript.util.Timespan.TimePeriod; +import ch.njol.util.Math2; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.block.CreatureSpawner; +import org.bukkit.block.TrialSpawner; +import org.bukkit.event.Event; +import org.bukkit.spawner.Spawner; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.spawners.util.SpawnerUtils; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@Name("Spawn Delay") +@Description(""" + Returns the spawn delay of the spawner. The spawn delay is the time before the spawner attempts to \ + spawn its entries. If the spawner is inactive at the time of the attempt, the delay remains as 0 seconds and \ + the spawner will try to spawn every tick until it's successful. After a successful spawn, the delay is \ + reset to a random value between the spawner’s minimum and maximum spawn delays. + """) +@Example(""" + set the spawn delay of target block to 50 seconds + add 10 seconds to the spawn delay of target block + remove 5 seconds from the spawn delay of target block + reset the spawn delay of target block + """) +@Since("INSERT VERSION") +@RequiredPlugins("Minecraft 1.21.4+ (for trial spawners)") +public class ExprSpawnDelay extends SimplePropertyExpression { + + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.EXPRESSION, infoBuilder(ExprSpawnDelay.class, Timespan.class, + "spawn delay[s]", "blocks/entities", false) + .supplier(ExprSpawnDelay::new) + .build() + ); + } + + @Override + public @Nullable Timespan convert(Object object) { + if (SpawnerUtils.isMobSpawner(object)) { + return new Timespan(TimePeriod.TICK, SpawnerUtils.getMobSpawner(object).getDelay()); + } else if (SpawnerUtils.isTrialSpawner(object)) { + if (!SpawnerUtils.IS_RUNNING_1_21_4) { + error("Getting the spawn delay of a trial spawner requires Minecraft 1.21.4 or newer."); + return null; + } + TrialSpawner spawner = SpawnerUtils.getTrialSpawner(object); + long ticks = Math.max(0, spawner.getNextSpawnAttempt() - spawner.getWorld().getGameTime()); + return new Timespan(TimePeriod.TICK, ticks); + } + + return null; + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + return switch (mode) { + case SET, ADD, REMOVE, RESET -> CollectionUtils.array(Timespan.class); + default -> null; + }; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + Timespan timespan = delta != null ? (Timespan) delta[0] : null; + + int ticks = 0; + if (timespan != null) + ticks = (int) Math.min(timespan.getAs(TimePeriod.TICK), Integer.MAX_VALUE); + + for (Object object : getExpr().getArray(event)) { + if (SpawnerUtils.isMobSpawner(object)) { + Spawner mobSpawner = SpawnerUtils.getMobSpawner(object); + mobSpawner.setDelay(getNewDelay(mode, mobSpawner.getDelay(), ticks)); + + if (mobSpawner instanceof CreatureSpawner creatureSpawner) + creatureSpawner.update(true, false); + } else if (SpawnerUtils.isTrialSpawner(object)) { + TrialSpawner trialSpawner = SpawnerUtils.getTrialSpawner(object); + long gameTime = trialSpawner.getWorld().getGameTime(); + + if (mode == ChangeMode.RESET) { + long delay = SpawnerUtils.getTrialSpawnerConfiguration(trialSpawner).getDelay(); + trialSpawner.setNextSpawnAttempt(gameTime + delay); + } else { + long offset = mode == ChangeMode.REMOVE ? -ticks : ticks; + trialSpawner.setNextSpawnAttempt(gameTime + offset); + } + + trialSpawner.update(true, false); + } + } + } + + private int getNewDelay(ChangeMode mode, int current, int delta) { + return switch (mode) { + case SET -> delta; + case ADD -> Math2.fit(0, current + delta, Integer.MAX_VALUE); + case REMOVE -> Math2.fit(0, current - delta, Integer.MAX_VALUE); + case RESET -> -1; + default -> current; + }; + } + + @Override + public Class getReturnType() { + return Timespan.class; + } + + @Override + protected String getPropertyName() { + return "spawn delay"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/ExprSpawnerEntity.java b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/ExprSpawnerEntity.java new file mode 100644 index 00000000000..7b630dd57ad --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/ExprSpawnerEntity.java @@ -0,0 +1,118 @@ +package org.skriptlang.skript.bukkit.spawners.elements.expressions.spawner; + +import ch.njol.skript.bukkitutil.EntityUtils; +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.entity.EntityData; +import ch.njol.skript.expressions.base.SimplePropertyExpression; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.block.CreatureSpawner; +import org.bukkit.entity.EntitySnapshot; +import org.bukkit.entity.EntityType; +import org.bukkit.event.Event; +import org.bukkit.spawner.BaseSpawner; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.spawners.util.SpawnerUtils; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@Name("Spawner Entity") +@Description(""" + Returns the entity type or snapshot of a spawner. This represents the entity the spawner will spawn \ + and is displayed inside the spawner. If the spawner has multiple entries, \ + the type or snapshot corresponds to the entity that will spawn next. + """) +@Example(""" + on right click: + if event-block is spawner: + send "Spawner's type is %target block's spawner entity type%" + send "Spawner's snapshot is %target block's spawner entity snapshot%" + """) +@Since("2.4, 2.9.2 (trial spawner), INSERT VERSION (spawner minecart)") +public class ExprSpawnerEntity extends SimplePropertyExpression { + + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.EXPRESSION, infoBuilder(ExprSpawnerEntity.class, Object.class, + "spawner [entity] (type|:snapshot)[s]", "blocks/entities", false) + .supplier(ExprSpawnerEntity::new) + .build() + ); + } + + private boolean snapshot; + + @Override + public boolean init(Expression[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + snapshot = parseResult.hasTag("snapshot"); + return super.init(expressions, matchedPattern, isDelayed, parseResult); + } + + @Override + public @Nullable Object convert(Object object) { + if (!SpawnerUtils.isSpawner(object)) + return null; + + BaseSpawner spawner = SpawnerUtils.getSpawner(object); + Object entity = snapshot ? spawner.getSpawnedEntity() : spawner.getSpawnedType(); + + if (entity == null) + return null; + + if (snapshot) + return entity; + + //noinspection DataFlowIssue + return EntityUtils.toSkriptEntityData((EntityType) entity); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + return switch (mode) { + case SET, DELETE, RESET -> { + if (snapshot) + yield CollectionUtils.array(EntitySnapshot.class); + yield CollectionUtils.array(EntityData.class); + } + default -> null; + }; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + Object value = (delta != null) ? delta[0] : null; + + for (Object object : getExpr().getArray(event)) { + if (!SpawnerUtils.isSpawner(object)) + continue; + + BaseSpawner spawner = SpawnerUtils.getSpawner(object); + + if (snapshot) { + spawner.setSpawnedEntity((EntitySnapshot) value); + } else { + spawner.setSpawnedType((EntityType) value); + } + + if (spawner instanceof CreatureSpawner creatureSpawner) + creatureSpawner.update(true, false); + } + } + + @Override + public Class getReturnType() { + if (snapshot) + return EntitySnapshot.class; + return EntityData.class; + } + + @Override + protected String getPropertyName() { + return "spawner type"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/mobspawnerdata/ExprMaxNearbyEntities.java b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/mobspawnerdata/ExprMaxNearbyEntities.java new file mode 100644 index 00000000000..5cbb10f5a4f --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/mobspawnerdata/ExprMaxNearbyEntities.java @@ -0,0 +1,84 @@ +package org.skriptlang.skript.bukkit.spawners.elements.expressions.spawner.mobspawnerdata; + +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +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.event.Event; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.spawners.util.SpawnerUtils; +import org.skriptlang.skript.bukkit.spawners.util.spawnerdata.SkriptMobSpawnerData; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@Name("Maximum Nearby Entity Cap") +@Description(""" + Returns the maximum nearby entity cap of similar entities within the spawn range of the mob spawner data. The mob \ + spawner will no longer spawn entities if the value was surpassed. + + By default, the maximum nearby entity cap is 6. + """) +@Example(""" + set {_data} to mob spawner data of event-block + set {_data}'s maximum nearby entity cap to 8 + broadcast the maximum nearby entity cap of {_data} + """) +@Example(""" + modify the mob spawner data of event-block: + set maximum nearby entity cap of {_data} to 10 + add 5 to maximum nearby entity cap of {_data} + remove 2 from maximum nearby entity cap of {_data} + reset maximum nearby entity cap of {_data} + """) +@Since("INSERT VERSION") +public class ExprMaxNearbyEntities extends SimplePropertyExpression { + + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.EXPRESSION, infoBuilder(ExprMaxNearbyEntities.class, Integer.class, + "max[imum] nearby entity (count|amount|cap)[s]", "mobspawnerdatas", true) + .supplier(ExprMaxNearbyEntities::new) + .build() + ); + } + + @Override + public Integer convert(SkriptMobSpawnerData data) { + return data.getMaxNearbyEntityCap(); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + return switch (mode) { + case SET, ADD, REMOVE, RESET -> CollectionUtils.array(Integer.class); + default -> null; + }; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + int count = delta != null ? ((int) delta[0]) : 0; + + for (SkriptMobSpawnerData data: getExpr().getArray(event)) { + int base = data.getMaxNearbyEntityCap(); + data.setMaxNearbyEntityCap(switch (mode) { + case ADD -> base + count; + case REMOVE -> base - count; + case RESET -> SpawnerUtils.DEFAULT_MAX_NEARBY_ENTITIES; + default -> count; + }); + } + } + + @Override + public Class getReturnType() { + return Integer.class; + } + + @Override + protected String getPropertyName() { + return "maximum nearby entity cap"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/mobspawnerdata/ExprSpawnCount.java b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/mobspawnerdata/ExprSpawnCount.java new file mode 100644 index 00000000000..1aad424a1a8 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/mobspawnerdata/ExprSpawnCount.java @@ -0,0 +1,85 @@ +package org.skriptlang.skript.bukkit.spawners.elements.expressions.spawner.mobspawnerdata; + +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +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.event.Event; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.spawners.util.SpawnerUtils; +import org.skriptlang.skript.bukkit.spawners.util.spawnerdata.SkriptMobSpawnerData; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@Name("Spawn Count") +@Description(""" + Returns the spawn count of the mob spawner data. This is the amount of entities the mob spawner \ + will attempt to spawn each spawn attempt. Though, if the spawner is spawning items, the spawn count is the \ + amount of stacks of item to spawn. + + By default, the spawn count is 4. + """) +@Example(""" + set {_data} to mob spawner data of event-block + set spawn count of {_data} to 5 + broadcast {_data}'s spawn count + """) +@Example(""" + modify the mob spawner data of event-block: + set spawn count of {_data} to 5 + add 2 to spawn count of {_data} + remove 1 from spawn count of {_data} + reset spawn count of {_data} + """) +@Since("INSERT VERSION") +public class ExprSpawnCount extends SimplePropertyExpression { + + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.EXPRESSION, infoBuilder(ExprSpawnCount.class, Integer.class, + "spawn (count|amount)[s]", "mobspawnerdatas", true) + .supplier(ExprSpawnCount::new) + .build() + ); + } + + @Override + public Integer convert(SkriptMobSpawnerData data) { + return data.getSpawnCount(); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + return switch (mode) { + case SET, ADD, REMOVE, RESET -> CollectionUtils.array(Integer.class); + default -> null; + }; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + int count = delta != null ? ((int) delta[0]) : 0; + + for (SkriptMobSpawnerData data : getExpr().getArray(event)) { + int base = data.getSpawnCount(); + data.setSpawnCount(switch (mode) { + case ADD -> base + count; + case REMOVE -> base - count; + case RESET -> SpawnerUtils.DEFAULT_SPAWN_RANGE; + default -> count; + }); + } + } + + @Override + public Class getReturnType() { + return Integer.class; + } + + @Override + protected String getPropertyName() { + return "spawn count"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/spawnerdata/ExprActivationRange.java b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/spawnerdata/ExprActivationRange.java new file mode 100644 index 00000000000..a1ff0b6cadb --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/spawnerdata/ExprActivationRange.java @@ -0,0 +1,87 @@ +package org.skriptlang.skript.bukkit.spawners.elements.expressions.spawner.spawnerdata; + +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +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.event.Event; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.spawners.util.SpawnerUtils; +import org.skriptlang.skript.bukkit.spawners.util.spawnerdata.SkriptSpawnerData; +import org.skriptlang.skript.bukkit.spawners.util.spawnerdata.SkriptTrialSpawnerData; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@Name("Activation Range") +@Description(""" + Returns the activation range of the spawner data. This is the distance from the spawner within which \ + players must be present for it to remain active. A value of 0 or less makes the spawner always active \ + as long as a player is online. + + By default, the activation range is 16 for mob spawners and 14 for trial spawners. + """) +@Example(""" + set {_data} to spawner data of event-block + set activation range of {_data} to 20 + reset activation range of {_data} + """) +@Example(""" + modify the spawner data of event-block: + set the activation radius to 20 + add 5 to the activation radius + remove 3 from the activation range + """) +@Since("INSERT VERSION") +public class ExprActivationRange extends SimplePropertyExpression { + + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.EXPRESSION, infoBuilder(ExprActivationRange.class, Integer.class, + "activation (radi(us[es]|i)|range[s])", "spawnerdatas", true) + .supplier(ExprActivationRange::new) + .build() + ); + } + + @Override + public Integer convert(SkriptSpawnerData data) { + return data.getActivationRange(); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + return switch (mode) { + case SET, ADD, REMOVE, RESET -> CollectionUtils.array(Integer.class); + default -> null; + }; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + int count = delta != null ? ((int) delta[0]) : 0; + + for (SkriptSpawnerData data : getExpr().getArray(event)) { + int base = data.getActivationRange(); + data.setActivationRange(switch (mode) { + case ADD -> base + count; + case REMOVE -> base - count; + case RESET -> data instanceof SkriptTrialSpawnerData + ? SpawnerUtils.DEFAULT_TRIAL_ACTIVATION_RANGE + : SpawnerUtils.DEFAULT_ACTIVATION_RANGE; + default -> count; + }); + } + } + + @Override + public Class getReturnType() { + return Integer.class; + } + + @Override + protected String getPropertyName () { + return "activation range"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/spawnerdata/ExprEventSpawnerData.java b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/spawnerdata/ExprEventSpawnerData.java new file mode 100644 index 00000000000..f019fede4fc --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/spawnerdata/ExprEventSpawnerData.java @@ -0,0 +1,109 @@ +package org.skriptlang.skript.bukkit.spawners.elements.expressions.spawner.spawnerdata; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.EventRestrictedSyntax; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.spawners.util.SpawnerDataType; +import org.skriptlang.skript.bukkit.spawners.util.events.MobSpawnerDataEvent; +import org.skriptlang.skript.bukkit.spawners.util.events.SpawnerDataEvent; +import org.skriptlang.skript.bukkit.spawners.util.events.TrialSpawnerDataEvent; +import org.skriptlang.skript.bukkit.spawners.util.spawnerdata.SkriptSpawnerData; +import org.skriptlang.skript.registration.SyntaxInfo; +import org.skriptlang.skript.registration.SyntaxRegistry; + +import java.util.StringJoiner; + +@Name("Event Spawner Data") +@Description(""" + Returns the spawner data associated with the current event. This is only available within spawner data sections. + Use 'trial spawner data' exclusively in trial spawner data sections, \ + and 'mob spawner data' exclusively in mob spawner data sections. + """) +@Example(""" + modify the mob spawner data of event-block: + broadcast the mob spawner data + + set {_data} to the mob spawner data: + broadcast the mob spawner data + """) +@Example(""" + modify the trial spawner data of event-block: + broadcast the trial spawner data + + set {_data} to the trial spawner data: + broadcast the trial spawner data + """) +@Example(""" + modify the spawner data of event-block: + broadcast the spawner data + """) +@Since("INSERT VERSION") +public class ExprEventSpawnerData extends SimpleExpression implements EventRestrictedSyntax { + + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.EXPRESSION, SyntaxInfo.Expression.builder(ExprEventSpawnerData.class, SkriptSpawnerData.class) + .supplier(ExprEventSpawnerData::new) + .priority(SyntaxInfo.SIMPLE) + .addPattern("[the] [:mob|:trial] spawner data") + .build() + ); + } + + private SpawnerDataType dataType; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + dataType = SpawnerDataType.fromTags(parseResult.tags); + + if (dataType.isTrial() && !getParser().isCurrentEvent(TrialSpawnerDataEvent.class)) { + Skript.error("'trial spawner data' can only be used in the trial spawner data sections."); + return false; + } else if (dataType.isMob() && !getParser().isCurrentEvent(MobSpawnerDataEvent.class)) { + Skript.error("'mob spawner data' can only be used in the mob spawner data sections."); + return false; + } + + return true; + } + + @Override + public Class[] supportedEvents() { + return CollectionUtils.array(SpawnerDataEvent.class); + } + + @Override + protected SkriptSpawnerData @Nullable [] get(Event event) { + if (!(event instanceof SpawnerDataEvent dataEvent)) + return null; + + return new SkriptSpawnerData[]{dataEvent.getSpawnerData()}; + } + + @Override + public boolean isSingle() { + return true; + } + + @Override + public Class getReturnType() { + return dataType.getDataClass(); + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + StringJoiner joiner = new StringJoiner(" ", "the", "spawner data"); + joiner.add(dataType.toString()); + return joiner.toString(); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/spawnerdata/ExprMinMaxDelay.java b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/spawnerdata/ExprMinMaxDelay.java new file mode 100644 index 00000000000..86ce61af9ca --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/spawnerdata/ExprMinMaxDelay.java @@ -0,0 +1,139 @@ +package org.skriptlang.skript.bukkit.spawners.elements.expressions.spawner.spawnerdata; + +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +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.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.util.Timespan; +import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.spawners.util.SpawnerUtils; +import org.skriptlang.skript.bukkit.spawners.util.spawnerdata.SkriptSpawnerData; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@Name("Minimum/Maximum Spawn Delay") +@Description(""" + Returns the minimum or maximum spawn delay of a spawner. + + On each spawn attempt, the spawner selects a delay until the next attempt. \ + This delay is always within the defined range between the minimum and maximum values. \ + The minimum delay cannot exceed the maximum delay, and vice versa. + + Default values for mob spawners: + - Minimum spawn delay: 10 seconds (200 ticks) + - Maximum spawn delay: 40 seconds (800 ticks) + + Though, trial spawners behave differently. Their minimum and maximum spawn delays are fixed to the same value. \ + By default, both are set to 2 seconds (40 ticks). + """) +@Example(""" + set {_data} to spawner data of event-block + set maximum spawn delay of {_data} to 30 seconds + reset maximum spawn delay of {_data} + """) +@Example(""" + modify the spawner data of event-block: + set the minimum spawn delay to 2 seconds + add 1 second to the minimum spawn delay + remove 1.5 seconds from the minimum spawn delay + """) +@Since("INSERT VERSION") +public class ExprMinMaxDelay extends SimplePropertyExpression { + + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.EXPRESSION, infoBuilder(ExprMinMaxDelay.class, Timespan.class, + "(:max|min)[imum] spawn delay[s]", "spawnerdatas", true) + .supplier(ExprMinMaxDelay::new) + .build() + ); + } + + private boolean max; + + @Override + public boolean init(Expression[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + max = parseResult.hasTag("max"); + return super.init(expressions, matchedPattern, isDelayed, parseResult); + } + + @Override + public Timespan convert(SkriptSpawnerData data) { + if (max) + return data.getMaxSpawnDelay(); + return data.getMinSpawnDelay(); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + return switch (mode) { + case SET, ADD, REMOVE, RESET -> CollectionUtils.array(Timespan.class); + default -> null; + }; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + Timespan timespan = delta != null ? (Timespan) delta[0] : null; + + for (SkriptSpawnerData data : getExpr().getArray(event)) { + Timespan minMax = getSpawnDelay(data, max); + + Timespan value = switch (mode) { + case SET -> timespan; + case ADD -> minMax.add(timespan); + case REMOVE -> minMax.subtract(timespan); + case RESET -> max ? SpawnerUtils.DEFAULT_MAX_SPAWN_DELAY : SpawnerUtils.DEFAULT_MIN_SPAWN_DELAY; + default -> new Timespan(); + }; + + assert value != null; + + String error = getErrorMessage(value, getSpawnDelay(data, !max)); + if (error != null) { + error(error); + continue; + } + + if (max) { + data.setMaxSpawnDelay(value); + } else { + data.setMinSpawnDelay(value); + } + } + } + + private String getErrorMessage(Timespan value, Timespan compare) { + if (max && value.compareTo(compare) < 0) { + return "The maximum spawn delay cannot be lower than the minimum spawn delay, " + + "thus setting it to a value lower than the minimum spawn delay will do nothing."; + } else if (!max && value.compareTo(compare) > 0) { + return "The minimum spawn delay cannot be greater than the maximum spawn delay, " + + "thus setting it to a value higher than the maximum spawn delay will do nothing."; + } + + return null; + } + + private Timespan getSpawnDelay(SkriptSpawnerData data, boolean max) { + return max ? data.getMaxSpawnDelay() : data.getMinSpawnDelay(); + } + + @Override + public Class getReturnType() { + return Timespan.class; + } + + @Override + protected String getPropertyName() { + if (max) + return "maximum spawn delay"; + return "minimum spawn delay"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/spawnerdata/ExprSecSpawnerData.java b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/spawnerdata/ExprSecSpawnerData.java new file mode 100644 index 00000000000..490ea39b8f7 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/spawnerdata/ExprSecSpawnerData.java @@ -0,0 +1,114 @@ +package org.skriptlang.skript.bukkit.spawners.elements.expressions.spawner.spawnerdata; + +import ch.njol.skript.config.SectionNode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.SectionExpression; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.Trigger; +import ch.njol.skript.lang.TriggerItem; +import ch.njol.skript.lang.util.SectionUtils; +import ch.njol.skript.variables.Variables; +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.spawners.util.SpawnerDataType; +import org.skriptlang.skript.bukkit.spawners.util.events.MobSpawnerDataEvent; +import org.skriptlang.skript.bukkit.spawners.util.events.TrialSpawnerDataEvent; +import org.skriptlang.skript.bukkit.spawners.util.spawnerdata.SkriptMobSpawnerData; +import org.skriptlang.skript.bukkit.spawners.util.spawnerdata.SkriptSpawnerData; +import org.skriptlang.skript.bukkit.spawners.util.spawnerdata.SkriptTrialSpawnerData; +import org.skriptlang.skript.registration.SyntaxInfo; +import org.skriptlang.skript.registration.SyntaxRegistry; + +import java.util.List; + +@Name("New Spawner Data") +@Description("Returns a new trial or mob spawner data.") +@Example(""" + set spawner data of event-block to the mob spawner data: + set the spawn count to 2 + add 2 to the maximum nearby entity cap + remove 5 from the activation range + add {_entry} to the spawner entries + set the spawn range to 16 + """) +@Example(""" + set {_trial data} to the trial spawner data: + set the activation range to 32 + set the spawn range to 8 + add {_entries::*} to the spawner entries + set the base entity spawn count to 12 + + add loot table "minecraft:chests/simple_dungeon" to the reward entries + set the reward weight for loot table "minecraft:chests/simple_dungeon" to 12 + + set the ominous trial spawner data of event-block to {_trial data} + """) +@Since("INSERT VERSION") +public class ExprSecSpawnerData extends SectionExpression { + + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.EXPRESSION, SyntaxInfo.Expression.builder(ExprSecSpawnerData.class, SkriptSpawnerData.class) + .supplier(ExprSecSpawnerData::new) + .priority(SyntaxInfo.SIMPLE) + .addPatterns("[the] mob spawner data", "[the] trial spawner data") + .build()); + } + + private SpawnerDataType dataType; + private Trigger trigger; + + @Override + public boolean init( + Expression[] exprs, int pattern, Kleenean delayed, ParseResult result, + @Nullable SectionNode node, @Nullable List triggerItems + ) { + dataType = pattern == 0 ? SpawnerDataType.MOB : SpawnerDataType.TRIAL; + if (node != null) { + String name = dataType + " spawner data"; + trigger = SectionUtils.loadLinkedCode(name, (beforeLoading, afterLoading) -> + loadCode(node, name, beforeLoading, afterLoading, MobSpawnerDataEvent.class)); + return trigger != null; + } + return true; + } + + @Override + protected SkriptSpawnerData @Nullable [] get(Event event) { + SkriptSpawnerData data = (dataType.isMob()) + ? new SkriptMobSpawnerData() + : new SkriptTrialSpawnerData(); + + if (trigger != null) { + //noinspection DataFlowIssue + Event dataEvent = (dataType.isMob()) + ? new MobSpawnerDataEvent((SkriptMobSpawnerData) data) + : new TrialSpawnerDataEvent((SkriptTrialSpawnerData) data); + + Variables.withLocalVariables(event, dataEvent, () -> + TriggerItem.walk(trigger, dataEvent) + ); + } + return new SkriptSpawnerData[]{data}; + } + + @Override + public boolean isSingle() { + return true; + } + + @Override + public Class getReturnType() { + return dataType.getDataClass(); + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return dataType + " spawner data"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/spawnerdata/ExprSpawnRange.java b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/spawnerdata/ExprSpawnRange.java new file mode 100644 index 00000000000..8cea7112ee9 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/spawnerdata/ExprSpawnRange.java @@ -0,0 +1,84 @@ +package org.skriptlang.skript.bukkit.spawners.elements.expressions.spawner.spawnerdata; + +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +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.event.Event; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.spawners.util.SpawnerUtils; +import org.skriptlang.skript.bukkit.spawners.util.spawnerdata.SkriptSpawnerData; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@Name("Spawn Range") +@Description(""" + Returns the spawn range of the spawner data. The spawn range is the radius of the area in which the spawner \ + can spawn entities. + + By default, the spawn range is 4. + """) +@Example(""" + set {_data} to spawner data of event-block + set the spawn range of {_data} to 6 + add 3 to the spawn range of {_data} + reset the spawn range of {_data} + """) +@Example(""" + modify the spawner data of event-block: + set the spawn range to 10 + add 5 to the spawn range + remove 9 from the spawn range + """) +@Since("INSERT VERSION") +public class ExprSpawnRange extends SimplePropertyExpression { + + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.EXPRESSION, infoBuilder(ExprSpawnRange.class, Integer.class, + "spawn (radi(us[es]|i)|range[s])", "spawnerdatas", true) + .supplier(ExprSpawnRange::new) + .build() + ); + } + + @Override + public Integer convert(SkriptSpawnerData data) { + return data.getSpawnRange(); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + return switch (mode) { + case SET, ADD, REMOVE, RESET -> CollectionUtils.array(Integer.class); + default -> null; + }; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + int range = delta != null ? ((int) delta[0]) : 0; + + for (SkriptSpawnerData data : getExpr().getArray(event)) { + int base = data.getSpawnRange(); + data.setSpawnRange(switch (mode) { + case ADD -> base + range; + case REMOVE -> base - range; + case RESET -> SpawnerUtils.DEFAULT_SPAWN_RANGE; + default -> range; + }); + } + } + + @Override + public Class getReturnType() { + return Integer.class; + } + + @Override + protected String getPropertyName() { + return "spawn range"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/spawnerdata/ExprSpawnerData.java b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/spawnerdata/ExprSpawnerData.java new file mode 100644 index 00000000000..8932737ecd1 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/spawnerdata/ExprSpawnerData.java @@ -0,0 +1,198 @@ +package org.skriptlang.skript.bukkit.spawners.elements.expressions.spawner.spawnerdata; + +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.PropertyExpression; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.SyntaxStringBuilder; +import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.block.TrialSpawner; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.spawners.util.SpawnerDataType; +import org.skriptlang.skript.bukkit.spawners.util.SpawnerUtils; +import org.skriptlang.skript.bukkit.spawners.util.spawnerdata.SkriptMobSpawnerData; +import org.skriptlang.skript.bukkit.spawners.util.spawnerdata.SkriptSpawnerData; +import org.skriptlang.skript.bukkit.spawners.util.spawnerdata.SkriptTrialSpawnerData; +import org.skriptlang.skript.registration.SyntaxRegistry; + +import java.util.ArrayList; +import java.util.List; + +@Name("Spawner Data") +@Description(""" + Returns the spawner data of a spawner. Trial spawners use different data in their regular and ominous states. + Use 'ominous trial spawner data' to set data for the ominous state only, \ + or 'ominous and regular trial spawner data' to set data for both states at once. + """) +@Example(""" + set the spawner data of event-block to the mob spawner data: + set the spawn count to 5 + add 2 to the maximum nearby entity cap + remove 5 from the activation range + """) +@Example(""" + set {_data} to spawner data of event-block + add {_spawner entries::*} to spawner entries of {_data} + set spawn range of {_data} to 12 + add 6 to the activation range of {_data} + set the spawner data of event-block to {_data} + """) +@Example(""" + set {_trial data} to the trial spawner data: + set the activation range to 32 + set the spawn range to 8 + add {_entries::*} to the spawner entries + set the base entity spawn count to 12 + + set the trial spawner data of event-block to {_trial data} # regular state + set the ominous trial spawner data of event-block to {_trial data} # ominous state + set the ominous and regular trial spawner datas of event-block to {_trial data} # both states + """) +@Since("INSERT VERSION") +public class ExprSpawnerData extends PropertyExpression { + + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.EXPRESSION, infoBuilder(ExprSpawnerData.class, SkriptSpawnerData.class, + "[trial:[:ominous|:regular|:ominous and regular] trial|:mob] spawner data[s]", "blocks/entities", false) + .supplier(ExprSpawnerData::new) + .build() + ); + } + + private enum TrialSpawnerState { + OMINOUS, REGULAR, BOTH, CURRENT; + + public static TrialSpawnerState fromTags(List tags) { + if (tags.contains("ominous")) { + return OMINOUS; + } else if (tags.contains("ominous and regular")) { + return BOTH; + } else if (tags.contains("regular")) { + return REGULAR; + } else { + return CURRENT; + } + } + } + + private SpawnerDataType dataType; + private TrialSpawnerState state; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + setExpr(exprs[0]); + dataType = SpawnerDataType.fromTags(parseResult.tags); + state = TrialSpawnerState.fromTags(parseResult.tags); + return true; + } + + @Override + protected SkriptSpawnerData[] get(Event event, Object[] source) { + List datas = new ArrayList<>(); + + for (Object spawnerObject : source) { + if (!dataType.matches(spawnerObject)) + continue; + + if (SpawnerUtils.isMobSpawner(spawnerObject)) { + datas.add(SkriptMobSpawnerData.fromSpawner(SpawnerUtils.getMobSpawner(spawnerObject))); + continue; + } + + TrialSpawner trialSpawner = SpawnerUtils.getTrialSpawner(spawnerObject); + datas.addAll(switch (state) { + case CURRENT -> List.of(SkriptTrialSpawnerData.fromTrialSpawner(trialSpawner, trialSpawner.isOminous())); + case OMINOUS -> List.of(SkriptTrialSpawnerData.fromTrialSpawner(trialSpawner, true)); + case REGULAR -> List.of(SkriptTrialSpawnerData.fromTrialSpawner(trialSpawner, false)); + case BOTH -> List.of( + SkriptTrialSpawnerData.fromTrialSpawner(trialSpawner, true), + SkriptTrialSpawnerData.fromTrialSpawner(trialSpawner, false) + ); + }); + } + + return datas.toArray(SkriptSpawnerData[]::new); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + return switch (mode) { + case SET, RESET -> CollectionUtils.array(dataType.getDataClass()); + default -> null; + }; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + SkriptSpawnerData data = delta != null ? (SkriptSpawnerData) delta[0] : null; + + for (Object spawnerObject : getExpr().getArray(event)) { + if (!dataType.matches(spawnerObject)) { + continue; + } + + if (data == null) { + if (SpawnerUtils.isMobSpawner(spawnerObject)) { + data = new SkriptMobSpawnerData(); + } else if (SpawnerUtils.isTrialSpawner(spawnerObject)) { + data = new SkriptTrialSpawnerData(); + } + } + + if (data == null) { + continue; + } + + if (data instanceof SkriptMobSpawnerData mobData) { + mobData.applyData(SpawnerUtils.getMobSpawner(spawnerObject)); + } else if (data instanceof SkriptTrialSpawnerData trialData) { + TrialSpawner trialSpawner = SpawnerUtils.getTrialSpawner(spawnerObject); + switch (state) { + case OMINOUS -> trialData.applyData(trialSpawner, true); + case REGULAR -> trialData.applyData(trialSpawner, false); + case BOTH -> { + trialData.applyData(trialSpawner, true); + trialData.applyData(trialSpawner, false); + } + } + } + } + } + + @Override + public boolean isSingle() { + return getExpr().isSingle() && state != TrialSpawnerState.BOTH; + } + + @Override + public Class getReturnType() { + return dataType.getDataClass(); + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + SyntaxStringBuilder builder = new SyntaxStringBuilder(event, debug); + + builder.append("the"); + if (dataType.isTrial()) { + if (state == TrialSpawnerState.REGULAR) { + builder.append("regular"); + } else if (state == TrialSpawnerState.OMINOUS) { + builder.append("ominous"); + } else { + builder.append("ominous and regular"); + } + } + + builder.append(dataType.toString() + " spawner data of", getExpr()); + + return builder.toString(); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/spawnerdata/ExprSpawnerEntries.java b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/spawnerdata/ExprSpawnerEntries.java new file mode 100644 index 00000000000..af5d04bb7ea --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/spawnerdata/ExprSpawnerEntries.java @@ -0,0 +1,116 @@ +package org.skriptlang.skript.bukkit.spawners.elements.expressions.spawner.spawnerdata; + +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.PropertyExpression; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.spawners.util.SkriptSpawnerEntry; +import org.skriptlang.skript.bukkit.spawners.util.spawnerdata.SkriptSpawnerData; +import org.skriptlang.skript.registration.SyntaxInfo; +import org.skriptlang.skript.registration.SyntaxRegistry; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +@Name("Spawner Entries") +@Description(""" + Returns the spawner entries of the spawner data. \ + On each spawn attempt, the spawner selects a random entry from the list (typically the highest weighted one) \ + and spawns it. The spawner's type and entity snapshot are then overwritten with the chosen entry. + """) +@Example(""" + set {_data} to spawner data of event-block + set {_entries::*} to spawner entries of {_data} + delete the spawner entries of {_data} + """) +@Example(""" + modify the spawner data of event-block: + set {_entry} to the spawner entry of a zombie: + set the weight to 2 + set the spawner entry equipment to loot table "minecraft:equipment/trial_chamber" + set the drop chances for helmet, legs and boots to 100% + set the spawn rule to a spawn rule: + set the minimum block light spawn level to 10 + set the maximum block light spawn level to 12 + set the maximum sky light spawn level to 5 + add {_entry} to the spawner entries + """) +@Since("INSERT VERSION") +public class ExprSpawnerEntries extends PropertyExpression { + + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.EXPRESSION, SyntaxInfo.Expression.builder(ExprSpawnerEntries.class, SkriptSpawnerEntry.class) + .supplier(ExprSpawnerEntries::new) + .priority(DEFAULT_PRIORITY) + .addPatterns( + "[the] spawner entr(y|ies) [of %spawnerdatas%]", + "[%spawnerdatas%'[s]] spawner entr(y|ies)") + .build() + ); + } + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + //noinspection unchecked + setExpr((Expression) exprs[0]); + return true; + } + + @Override + protected SkriptSpawnerEntry @Nullable [] get(Event event, SkriptSpawnerData[] source) { + List entries = new ArrayList<>(); + + for (SkriptSpawnerData data : source) { + entries.addAll(data.getSpawnerEntries()); + } + + return entries.toArray(SkriptSpawnerEntry[]::new); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + if (mode == ChangeMode.REMOVE_ALL) + return null; + + return CollectionUtils.array(SkriptSpawnerEntry[].class); + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + Set entries = new HashSet<>(); + if (delta != null) { + for (Object object : delta) + entries.add((SkriptSpawnerEntry) object); + } + + for (SkriptSpawnerData data : getExpr().getArray(event)) { + switch (mode) { + case SET -> data.setSpawnerEntries(entries); + case ADD -> data.addSpawnerEntries(entries); + case REMOVE -> data.removeSpawnerEntries(entries); + case RESET, DELETE -> data.clearSpawnerEntries(); + } + } + } + + @Override + public Class getReturnType() { + return SkriptSpawnerEntry.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "the spawner entries of " + getExpr().toString(event, debug); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/trialspawner/ExprCooldownExpiry.java b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/trialspawner/ExprCooldownExpiry.java new file mode 100644 index 00000000000..709031c9378 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/trialspawner/ExprCooldownExpiry.java @@ -0,0 +1,102 @@ +package org.skriptlang.skript.bukkit.spawners.elements.expressions.spawner.trialspawner; + +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.*; +import ch.njol.skript.expressions.base.SimplePropertyExpression; +import ch.njol.skript.util.Timespan; +import ch.njol.skript.util.Timespan.TimePeriod; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.block.TrialSpawner; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.spawners.util.SpawnerUtils; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@Name("Trial Cooldown Expiry") +@Description(""" + Returns the time until the trial spawner's cooldown expires. After spawning all entities, \ + the trial spawner enters cooldown and does not not spawn entities again until it ends. + + By default, the cooldown lasts 30 minutes (36,000 ticks). + """) +@Example(""" + broadcast the trial cooldown expiry of event-block + add 5 minutes to the trial cooldown expiry of event-block + set the trial cooldown expiry of event-block to 10 minutes + remove 2 minutes from the trial cooldown expiry of event-block + reset the trial cooldown expiry of event-block + """) +@Since("INSERT VERSION") +@RequiredPlugins("Minecraft 1.21.4+") +public class ExprCooldownExpiry extends SimplePropertyExpression { + + public static void register(SyntaxRegistry registry) { + if (!SpawnerUtils.IS_RUNNING_1_21_4) + return; + registry.register(SyntaxRegistry.EXPRESSION, infoBuilder(ExprCooldownExpiry.class, Timespan.class, + "trial [spawner] cool[ ]down expir(y|ies)", "blocks", false) + .supplier(ExprCooldownExpiry::new) + .build() + ); + } + + @Override + public @Nullable Timespan convert(Block block) { + if (!SpawnerUtils.isTrialSpawner(block)) + return null; + + TrialSpawner spawner = SpawnerUtils.getTrialSpawner(block); + long ticks = Math.max(0, spawner.getCooldownEnd() - block.getWorld().getGameTime()); + return new Timespan(TimePeriod.TICK, ticks); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + if (mode == ChangeMode.REMOVE_ALL) + return null; + + return CollectionUtils.array(Timespan.class); + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + Timespan deltaTimespan = delta != null ? (Timespan) delta[0] : null; + + for (Block block : getExpr().getArray(event)) { + if (!SpawnerUtils.isTrialSpawner(block)) + continue; + + World world = block.getWorld(); + + TrialSpawner spawner = SpawnerUtils.getTrialSpawner(block); + long currentTicks = spawner.getCooldownEnd() - world.getGameTime(); + Timespan currentTimespan = new Timespan(TimePeriod.TICK, Math.max(0, currentTicks)); + + Timespan newTimespan = switch (mode) { + case SET -> deltaTimespan; + case ADD -> currentTimespan.add(deltaTimespan); + case REMOVE -> currentTimespan.subtract(deltaTimespan); + case RESET -> new Timespan(TimePeriod.TICK, spawner.getCooldownLength()); + case DELETE -> new Timespan(); + default -> currentTimespan; + }; + + assert newTimespan != null; + + spawner.setCooldownEnd(world.getGameTime() + newTimespan.getAs(TimePeriod.TICK)); + } + } + + @Override + public Class getReturnType() { + return Timespan.class; + } + + @Override + protected String getPropertyName() { + return "trial cooldown expiry"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/trialspawner/ExprCooldownLength.java b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/trialspawner/ExprCooldownLength.java new file mode 100644 index 00000000000..50e23ad8dec --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/trialspawner/ExprCooldownLength.java @@ -0,0 +1,97 @@ +package org.skriptlang.skript.bukkit.spawners.elements.expressions.spawner.trialspawner; + +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +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.skript.util.Timespan; +import ch.njol.skript.util.Timespan.TimePeriod; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.block.Block; +import org.bukkit.block.TrialSpawner; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.spawners.util.SpawnerUtils; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@Name("Trial Cooldown Length") +@Description(""" + Returns the cooldown length of a trial spawner. After spawning all entities, the trial spawner enters cooldown \ + and does not spawn entities again until it ends. + + By default, the cooldown length is 30 minutes (36,000 ticks). + """) +@Example(""" + broadcast the trial cooldown length of event-block + add 1 hour to the trial cooldown length of event-block + set the trial cooldown length of event-block to 25 minutes + reset the trial cooldown length of event-block + """) +@Since("INSERT VERSION") +public class ExprCooldownLength extends SimplePropertyExpression { + + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.EXPRESSION, infoBuilder(ExprCooldownLength.class, Timespan.class, + "trial [spawner] cool[ ]down length[s]", "blocks", false) + .supplier(ExprCooldownLength::new) + .build() + ); + } + + @Override + public @Nullable Timespan convert(Block block) { + if (!SpawnerUtils.isTrialSpawner(block)) + return null; + + TrialSpawner spawner = SpawnerUtils.getTrialSpawner(block); + return new Timespan(TimePeriod.TICK, spawner.getCooldownLength()); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + return switch (mode) { + case SET, ADD, REMOVE, RESET -> CollectionUtils.array(Timespan.class); + default -> null; + }; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + Timespan timespan = delta != null ? (Timespan) delta[0] : null; + + int ticks = 0; + if (timespan != null) + ticks = (int) Math.min(timespan.getAs(TimePeriod.TICK), Integer.MAX_VALUE); + + for (Block block : getExpr().getArray(event)) { + if (!SpawnerUtils.isTrialSpawner(block)) + continue; + + TrialSpawner spawner = SpawnerUtils.getTrialSpawner(block); + assert spawner != null; + + int base = spawner.getCooldownLength(); + spawner.setCooldownLength(switch (mode) { + case ADD -> base + ticks; + case REMOVE -> base - ticks; + case RESET -> (int) Math.min(SpawnerUtils.DEFAULT_COOLDOWN_LENGTH.getAs(TimePeriod.TICK), Integer.MAX_VALUE); + default -> ticks; + }); + + spawner.update(true, false); + } + } + + @Override + public Class getReturnType() { + return Timespan.class; + } + + @Override + protected String getPropertyName() { + return "trial cooldown length"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/trialspawner/ExprTrackedEntities.java b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/trialspawner/ExprTrackedEntities.java new file mode 100644 index 00000000000..59efbad28d0 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/trialspawner/ExprTrackedEntities.java @@ -0,0 +1,105 @@ +package org.skriptlang.skript.bukkit.spawners.elements.expressions.spawner.trialspawner; + +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.PropertyExpression; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.SyntaxStringBuilder; +import ch.njol.util.Kleenean; +import org.bukkit.block.Block; +import org.bukkit.block.TrialSpawner; +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.spawners.util.SpawnerUtils; +import org.skriptlang.skript.registration.SyntaxInfo; +import org.skriptlang.skript.registration.SyntaxRegistry; + +import java.util.ArrayList; +import java.util.List; + +@Name("Tracked Entities") +@Description(""" + Returns the entities (including players) tracked by the trial spawner. \ + Tracked players are those who enter the battle by stepping into the spawner's activation range, \ + while tracked entities (non-players) are those spawned by the trial spawner. + These entities contribute to the base and incremental entity counts of the trial spawner data. + """) +@Example(""" + broadcast the tracked players of event-block + add player to the tracked players of event-block + """) +@Example(""" + set tracked entities of event-block to the chickens within radius 5 of player + remove the target entity from the tracked entities of event-block + """) +@Since("INSERT VERSION") +public class ExprTrackedEntities extends PropertyExpression { + + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.EXPRESSION, SyntaxInfo.Expression.builder(ExprTrackedEntities.class, Entity.class) + .supplier(ExprTrackedEntities::new) + .priority(DEFAULT_PRIORITY) + .addPatterns(getPatterns("tracked player[s]", "blocks")) + .addPatterns(getPatterns("tracked entit(y|ies)", "blocks")) + .build() + ); + } + + private boolean players; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + //noinspection unchecked + setExpr((Expression) exprs[0]); + players = matchedPattern < 2; + return true; + } + + @Override + protected Entity[] get(Event event, Block[] source) { + List values = new ArrayList<>(); + + for (Block block : source) { + if (!SpawnerUtils.isTrialSpawner(block)) + continue; + + TrialSpawner spawner = SpawnerUtils.getTrialSpawner(block); + + if (players) { + values.addAll(spawner.getTrackedPlayers()); + } else { + values.addAll(spawner.getTrackedEntities()); + } + } + + return values.toArray(Entity[]::new); + } + + @Override + public Class getReturnType() { + if (players) + return Player.class; + return Entity.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + SyntaxStringBuilder builder = new SyntaxStringBuilder(event, debug); + + builder.append("tracked"); + if (players) { + builder.append("players"); + } else { + builder.append("entities"); + } + builder.append("of", getExpr()); + + return builder.toString(); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/trialspawnerdata/ExprBaseEntityCount.java b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/trialspawnerdata/ExprBaseEntityCount.java new file mode 100644 index 00000000000..a78135fa33a --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/trialspawnerdata/ExprBaseEntityCount.java @@ -0,0 +1,112 @@ +package org.skriptlang.skript.bukkit.spawners.elements.expressions.spawner.trialspawnerdata; + +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +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.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.spawners.util.SpawnerUtils; +import org.skriptlang.skript.bukkit.spawners.util.spawnerdata.SkriptTrialSpawnerData; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@Name("Base Mob Spawn Amount") +@Description(""" + Returns the base (simultaneous) mob spawn amount of the trial spawner data. + + The base mob spawn amount is the total number of mobs spawned (by default 6) when there is one tracked player. + + The base simultaneous mob spawn amount is the number of mobs spawned at once (by default 2) when there is \ + one tracked player. + + For tracked players beyond the first one, the total simultaneous and not simultaneous mob spawn amounts \ + will be increased by the incremental mob spawn amounts. + + Once the total mob amount has been spawned, the trial spawner enters cooldown \ + and will not spawn entities again until it ends. + """) +@Example(""" + set {_data} to trial spawner data of event-block + set base mob spawn amount of {_data} to 10 + add 2 to base simultaneous entity spawn amount of {_data} + broadcast "The base mob spawn amount of the trial spawner is %the base trial spawner mob count of {_data}%!" + """) +@Example(""" + modify the trial spawner data of event-block: + add 5 to base mob spawn amount + remove 3 from base simultaneous mob spawn amount + reset base mob spawn amount + add 10 to base concurrent entity spawn amount + """) +@Since("INSERT VERSION") +public class ExprBaseEntityCount extends SimplePropertyExpression { + + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.EXPRESSION, infoBuilder(ExprBaseEntityCount.class, Integer.class, + "base [concurrent:(concurrent|simultaneous)] (mob|entity) [spawn] (count|amount)[s]", "trialspawnerdatas", true) + .supplier(ExprBaseEntityCount::new) + .build() + ); + } + + private boolean concurrent; + + @Override + public boolean init(Expression[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + concurrent = parseResult.hasTag("concurrent"); + return super.init(expressions, matchedPattern, isDelayed, parseResult); + } + + @Override + public Integer convert(SkriptTrialSpawnerData data) { + if (concurrent) + return data.getConcurrentMobAmount(); + return data.getBaseMobAmount(); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + return switch (mode) { + case SET, ADD, REMOVE, RESET -> CollectionUtils.array(Integer.class); + default -> null; + }; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + int count = delta != null ? ((int) delta[0]) : 0; + + for (SkriptTrialSpawnerData data : getExpr().getArray(event)) { + int base = concurrent ? data.getConcurrentMobAmount() : data.getBaseMobAmount(); + int value = switch (mode) { + case ADD -> base + count; + case REMOVE -> base - count; + case RESET -> concurrent ? SpawnerUtils.DEFAULT_CONCURRENT_MOB_AMOUNT : SpawnerUtils.DEFAULT_BASE_MOB_AMOUNT; + default -> count; + }; + + if (concurrent) { + data.setConcurrentMobAmount(value); + } else { + data.setBaseMobAmount(value); + } + } + } + + @Override + public Class getReturnType() { + return Integer.class; + } + + @Override + protected String getPropertyName() { + return "base" + (concurrent ? "concurrent " : " ") + "mob count"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/trialspawnerdata/ExprIncrementalEntityCount.java b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/trialspawnerdata/ExprIncrementalEntityCount.java new file mode 100644 index 00000000000..f3dbf12adcc --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/trialspawnerdata/ExprIncrementalEntityCount.java @@ -0,0 +1,122 @@ +package org.skriptlang.skript.bukkit.spawners.elements.expressions.spawner.trialspawnerdata; + +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +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.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.spawners.util.SpawnerUtils; +import org.skriptlang.skript.bukkit.spawners.util.spawnerdata.SkriptTrialSpawnerData; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@Name("Incremental Mob Spawn Amount") +@Description(""" + Returns the incremental (concurrent) mob spawn amount of the trial spawner data. + + The incremental mob spawn amount determines how many additional mobs are added to the total mob spawn amount \ + for each tracked player beyond the first. + For example, if the incremental mob spawn amount is 2 \ + and there are 2 tracked players, 2 extra mobs are added (for the second player), resulting in 8 total mobs \ + when combined with the base amount of 6. By default, the incremental mob spawn amount is 2. + + The incremental simultaneous mob spawn amount works the same way, but applies to mobs that spawn at once. \ + This value determines how many additional simultaneous spawns are added for each tracked player beyond the first. + For example, if the incremental simultaneous mob spawn amount is 5 and there are 3 tracked players, \ + 10 extra mobs are added (for the second & third player), resulting in 12 simultaneous spawns when combined with \ + the base amount of 2. By default, the incremental simultaneous mob spawn amount is 2. + + The trial spawner will continue spawning mobs until the total mob spawn amount is reached, \ + with the number of entities present at once capped by the total simultaneous spawn amount \ + as seen in the formula below. + + The formulas are: + ``` + total mob spawn amount = base mob spawn amount + (incremental mob spawn amount × (tracked player count - 1)) + total simultaneous mob spawn amount = base simultaneous mob spawn amount + (incremental simultaneous mob spawn amount × (tracked player count - 1)) + ``` + """) +@Example(""" + set {_data} to trial spawner data of event-block + set incremental mob spawn amount of {_data} to 10 + add 2 to incremental concurrent entity spawn amount of {_data} + """) +@Example(""" + modify the trial spawner data of event-block: + add 5 to incremental mob spawn amount + remove 3 from additional simultaneous mob spawn amount + reset additional mob spawn amount + add 10 to incremental concurrent entity spawn amount + """) +@Since("INSERT VERSION") +public class ExprIncrementalEntityCount extends SimplePropertyExpression { + + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.EXPRESSION, infoBuilder(ExprIncrementalEntityCount.class, Integer.class, + "(incremental|additional) [concurrent:(concurrent|simultaneous)] (mob|entity) [spawn] (count|amount)[s]", "trialspawnerdatas", true) + .supplier(ExprIncrementalEntityCount::new) + .build() + ); + } + + private boolean concurrent; + + @Override + public boolean init(Expression[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + concurrent = parseResult.hasTag("concurrent"); + return super.init(expressions, matchedPattern, isDelayed, parseResult); + } + + @Override + public @Nullable Integer convert(SkriptTrialSpawnerData data) { + if (concurrent) + return data.getConcurrentMobAmountIncrement(); + return data.getBaseMobAmountIncrement(); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + return switch (mode) { + case SET, ADD, REMOVE, RESET -> CollectionUtils.array(Integer.class); + default -> null; + }; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + int count = delta != null ? ((int) delta[0]) : 0; + + for (SkriptTrialSpawnerData data : getExpr().getArray(event)) { + int base = concurrent ? data.getConcurrentMobAmountIncrement() : data.getBaseMobAmountIncrement(); + int value = switch (mode) { + case ADD -> base + count; + case REMOVE -> base - count; + case RESET -> concurrent ? SpawnerUtils.DEFAULT_CONCURRENT_PER_PLAYER_INCREMENT : SpawnerUtils.DEFAULT_BASE_PER_PLAYER_INCREMENT; + default -> count; + }; + + if (concurrent) { + data.setConcurrentMobAmountIncrement(value); + } else { + data.setBaseMobAmountIncrement(value); + } + } + } + + @Override + public Class getReturnType() { + return Integer.class; + } + + @Override + protected String getPropertyName() { + return "incremental" + (concurrent ? " concurrent " : " ") + "mob spawn count"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/trialspawnerdata/ExprRewardEntries.java b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/trialspawnerdata/ExprRewardEntries.java new file mode 100644 index 00000000000..51475f28c5f --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/trialspawnerdata/ExprRewardEntries.java @@ -0,0 +1,113 @@ +package org.skriptlang.skript.bukkit.spawners.elements.expressions.spawner.trialspawnerdata; + +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.PropertyExpression; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.event.Event; +import org.bukkit.loot.LootTable; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.spawners.util.spawnerdata.SkriptTrialSpawnerData; +import org.skriptlang.skript.registration.SyntaxInfo; +import org.skriptlang.skript.registration.SyntaxRegistry; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +@Name("Reward Entries") +@Description(""" + Returns the reward entries of the trial spawner data. Reward entries are loot tables that the trial spawner \ + can select from when ejecting rewards. Each entry has a weight that determines its chance of being chosen. \ + By default, all reward entries have a weight of 1. + """) +@Example(""" + set {_data} to the trial spawner data of event-block + set reward entry of {_data} to loot table "minecraft:chests/simple_dungeon" + set the reward weight for loot table "minecraft:chests/simple_dungeon" of {_data} to 5 + add loot table "minecraft:spawners/trial_chamber/items_to_drop_when_ominous" to the reward entries of {_data} + delete the reward entries of {_data} + """) +@Example(""" + modify the trial spawner data of event-block: + add loot table "minecraft:chests/simple_dungeon" to the reward entries + set the reward weight for loot table "minecraft:chests/simple_dungeon" to 5 + remove loot table "minecraft:chests/simple_dungeon" from the reward entries + """) +@Since("INSERT VERSION") +public class ExprRewardEntries extends PropertyExpression { + + public static void register(SyntaxRegistry syntaxRegistry) { + syntaxRegistry.register(SyntaxRegistry.EXPRESSION, SyntaxInfo.Expression.builder(ExprRewardEntries.class, LootTable.class) + .supplier(ExprRewardEntries::new) + .priority(DEFAULT_PRIORITY) + .addPatterns(getDefaultPatterns("reward entr(y|ies)", "trialspawnerdatas")) + .build() + ); + } + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + //noinspection unchecked + setExpr((Expression) exprs[0]); + return true; + } + + @Override + protected LootTable[] get(Event event, SkriptTrialSpawnerData[] source) { + return Arrays.stream(source) + .flatMap(data -> data.getRewardEntries().keySet().stream()) + .toArray(LootTable[]::new); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + return switch (mode) { + case SET, ADD, REMOVE, DELETE -> CollectionUtils.array(LootTable[].class); + default -> null; + }; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + Map lootTables = new HashMap<>(); + if (delta != null) { + for (Object object : delta) { + lootTables.put((LootTable) object, 1); + } + } + + for (SkriptTrialSpawnerData data : getExpr().getArray(event)) { + if (mode == ChangeMode.DELETE) { + data.clearRewardEntries(); + continue; + } + + Map currentEntries = data.getRewardEntries(); + switch (mode) { + case SET -> currentEntries = lootTables; + case ADD -> currentEntries.putAll(lootTables); + case REMOVE -> lootTables.keySet().forEach(currentEntries::remove); + } + + data.setRewardEntries(currentEntries); + } + } + + @Override + public Class getReturnType() { + return LootTable.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "reward entries of " + getExpr().toString(event, debug); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/trialspawnerdata/ExprRewardEntryWeight.java b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/trialspawnerdata/ExprRewardEntryWeight.java new file mode 100644 index 00000000000..71200554d75 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawner/trialspawnerdata/ExprRewardEntryWeight.java @@ -0,0 +1,133 @@ +package org.skriptlang.skript.bukkit.spawners.elements.expressions.spawner.trialspawnerdata; + +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.SyntaxStringBuilder; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.event.Event; +import org.bukkit.loot.LootTable; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.spawners.util.spawnerdata.SkriptTrialSpawnerData; +import org.skriptlang.skript.registration.SyntaxInfo; +import org.skriptlang.skript.registration.SyntaxRegistry; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +@Name("Reward Entry Weight") +@Description(""" + Returns the reward entry weight for the specified loot table(s) in the trial spawner data. \ + A higher weight increases the chance of that loot table being selected as a reward. + If a loot table is not already a reward entry, setting its weight will add it as a new entry \ + with the specified weight. + """) +@Example(""" + set {_data} to the trial spawner data of event-block + add loot table "minecraft:chests/simple_dungeon" to the reward entries of {_data} + set the reward weight for loot table "minecraft:chests/simple_dungeon" of {_data} to 10 + reset the reward weight for loot table "minecraft:chests/simple_dungeon" of {_data} # resets to default weight of 1 + """) +@Example(""" + modify the trial spawner data of event-block: + add 5 to the reward weight for loot table "minecraft:chests/simple_dungeon" + # now it's 6, since the default is 1 + remove 2 from the reward weight for loot table "minecraft:chests/simple_dungeon" + """) +@Since("INSERT VERSION") +public class ExprRewardEntryWeight extends SimpleExpression { + + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.EXPRESSION, SyntaxInfo.Expression.builder(ExprRewardEntryWeight.class, Integer.class) + .supplier(ExprRewardEntryWeight::new) + .priority(SyntaxInfo.COMBINED) + .addPatterns( + "[the] reward [entry] weight [of %trialspawnerdatas%] for %loottables%", + "%trialspawnerdatas%'[s] reward [entry] weight for %loottables%") + .build() + ); + } + + private Expression datas; + private Expression lootTables; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + //noinspection unchecked + datas = (Expression) exprs[0]; + //noinspection unchecked + lootTables = (Expression) exprs[1]; + return true; + } + + @Override + protected Integer @Nullable [] get(Event event) { + SkriptTrialSpawnerData[] datas = this.datas.getArray(event); + LootTable[] lootTables = this.lootTables.getArray(event); + + List weights = new ArrayList<>(lootTables.length * datas.length); + + for (SkriptTrialSpawnerData data : datas) { + Map weightedMap = data.getRewardEntries(); + for (LootTable lootTable : lootTables) { + Integer weight = weightedMap.get(lootTable); + if (weight == null) + continue; + + weights.add(weight); + } + } + + return weights.toArray(Integer[]::new); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + return switch (mode) { + case SET, ADD, REMOVE, RESET -> CollectionUtils.array(Integer.class); + default -> null; + }; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + int weight = delta != null ? (int) delta[0] : 1; + + for (SkriptTrialSpawnerData data : this.datas.getArray(event)) { + for (LootTable lootTable : this.lootTables.getArray(event)) { + data.setRewardEntry(lootTable, switch (mode) { + case SET, RESET -> weight; + case ADD -> Optional.of(data.getRewardWeight(lootTable)).orElse(1) + weight; + case REMOVE -> Optional.of(data.getRewardWeight(lootTable)).orElse(1) - weight; + default -> 1; + }); + } + } + } + + @Override + public boolean isSingle() { + return datas.isSingle() && lootTables.isSingle(); + } + + @Override + public Class getReturnType() { + return Integer.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + SyntaxStringBuilder builder = new SyntaxStringBuilder(event, debug); + builder.append("the reward weight of", datas, "for", lootTables); + return builder.toString(); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawnerentry/ExprSecSpawnerEntry.java b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawnerentry/ExprSecSpawnerEntry.java new file mode 100644 index 00000000000..dda06a46e6b --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawnerentry/ExprSecSpawnerEntry.java @@ -0,0 +1,124 @@ +package org.skriptlang.skript.bukkit.spawners.elements.expressions.spawnerentry; + +import ch.njol.skript.config.SectionNode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.entity.EntityData; +import ch.njol.skript.expressions.base.SectionExpression; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.Trigger; +import ch.njol.skript.lang.TriggerItem; +import ch.njol.skript.lang.util.SectionUtils; +import ch.njol.skript.variables.Variables; +import ch.njol.util.Kleenean; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntitySnapshot; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.spawners.util.SkriptSpawnerEntry; +import org.skriptlang.skript.bukkit.spawners.util.events.SpawnRuleEvent; +import org.skriptlang.skript.bukkit.spawners.util.events.SpawnerEntryEvent; +import org.skriptlang.skript.registration.SyntaxInfo; +import org.skriptlang.skript.registration.SyntaxRegistry; + +import java.util.List; + +@Name("New Spawner Entry") +@Description("Returns a new spawner entry from the given entity snapshot or data.") +@Example(""" + set {_entry} to the spawner entry of a zombie: + set the weight to 5 + set the spawn rule to a spawn rule: + set the maximum block light spawn level to 15 + set the minimum block light spawn level to 10 + set the maximum sky light spawn level to 15 + + set the spawner entry equipment to loot table "minecraft:equipment/trial_chamber" + set the drop chances for helmet, legs and boots to 100% + add {_entry} to the spawner entries of {_data} + """) +@Since("INSERT VERSION") +public class ExprSecSpawnerEntry extends SectionExpression { + + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.EXPRESSION, SyntaxInfo.Expression.builder(ExprSecSpawnerEntry.class, SkriptSpawnerEntry.class) + .supplier(ExprSecSpawnerEntry::new) + .priority(SyntaxInfo.COMBINED) + .addPattern("[a|the] spawner entry (of|using) %entitydata/entitysnapshot%") + .build() + ); + } + + private Trigger trigger; + private Expression entity; + + @Override + public boolean init( + Expression[] exprs, int pattern, Kleenean delayed, ParseResult result, @Nullable SectionNode node, + @Nullable List triggerItems + ) { + entity = exprs[0]; + if (node != null) { + trigger = SectionUtils.loadLinkedCode("spawner entry create", (beforeLoading, afterLoading) -> + loadCode(node, "spawner entry create", beforeLoading, afterLoading, SpawnRuleEvent.class) + ); + return trigger != null; + } + return true; + } + + @Override + protected SkriptSpawnerEntry @Nullable [] get(Event event) { + Object object = entity.getSingle(event); + if (object == null) + return null; + + EntitySnapshot snapshot = null; + + if (object instanceof EntityData entityData) { + Entity entity = entityData.create(); + if (entity == null) + return null; + + //noinspection UnstableApiUsage + snapshot = entity.createSnapshot(); + if (snapshot == null) + return null; + + entity.remove(); + } else if (object instanceof EntitySnapshot entitySnapshot) { + snapshot = entitySnapshot; + } + + assert snapshot != null; + + SkriptSpawnerEntry entry = new SkriptSpawnerEntry(snapshot); + if (trigger != null) { + SpawnerEntryEvent entryEvent = new SpawnerEntryEvent(entry); + Variables.withLocalVariables(event, entryEvent, () -> + TriggerItem.walk(trigger, entryEvent) + ); + } + + return new SkriptSpawnerEntry[]{entry}; + } + + @Override + public boolean isSingle() { + return true; + } + + @Override + public Class getReturnType() { + return SkriptSpawnerEntry.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "the spawner entry of " + entity.toString(event, debug); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawnerentry/ExprSpawnerEntry.java b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawnerentry/ExprSpawnerEntry.java new file mode 100644 index 00000000000..b499ab71b3f --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawnerentry/ExprSpawnerEntry.java @@ -0,0 +1,37 @@ +package org.skriptlang.skript.bukkit.spawners.elements.expressions.spawnerentry; + +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.EventValueExpression; +import org.skriptlang.skript.bukkit.spawners.util.SkriptSpawnerEntry; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@Name("Spawner Entry") +@Description(""" + The spawner entry used in the spawner entry section. + """) +@Examples(""" + the spawner entry + """) +@Since("INSERT VERSION") +public class ExprSpawnerEntry extends EventValueExpression { + + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.EXPRESSION, infoBuilder(ExprSpawnerEntry.class, SkriptSpawnerEntry.class, "[the] spawner entry") + .supplier(ExprSpawnerEntry::new) + .build() + ); + } + + public ExprSpawnerEntry() { + super(SkriptSpawnerEntry.class); + } + + @Override + public String toString() { + return "the spawner entry"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawnerentry/ExprSpawnerEntryDropChances.java b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawnerentry/ExprSpawnerEntryDropChances.java new file mode 100644 index 00000000000..075bd499889 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawnerentry/ExprSpawnerEntryDropChances.java @@ -0,0 +1,131 @@ +package org.skriptlang.skript.bukkit.spawners.elements.expressions.spawnerentry; + +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.SyntaxStringBuilder; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.event.Event; +import org.bukkit.inventory.EquipmentSlot; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.spawners.util.SkriptSpawnerEntry; +import org.skriptlang.skript.registration.SyntaxInfo; +import org.skriptlang.skript.registration.SyntaxRegistry; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +@Name("Equipment Drop Chance") +@Description(""" + Returns the drop chance for the specified equipment slot(s) in a spawner entry. \ + The drop chance is a float between 0 and 1, where 0 means the item will never drop \ + and 1 means it will always drop. + + Setting drop chances without an equipment loot table defined will have no effect. + """) +@Example(""" + set {_entry} to the spawner entry of a zombie: + set the spawner entry equipment to loot table "minecraft:equipment/trial_chamber" + set the drop chances for helmet, legs and boots to 100% + remove 50% from the drop chance for legs + clear the drop chances for all equipment slots + """) +@Since("INSERT VERSION") +public class ExprSpawnerEntryDropChances extends SimpleExpression { + + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.EXPRESSION, SyntaxInfo.Expression.builder(ExprSpawnerEntryDropChances.class, Float.class) + .supplier(ExprSpawnerEntryDropChances::new) + .priority(SyntaxInfo.COMBINED) + .addPatterns( + "[the] drop chance[s] [of %spawnerentries%] for %equipmentslots%", + "%spawnerentries%'[s] drop chance[s] for %equipmentslots%") + .build() + ); + } + + private Expression entries; + private Expression slots; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + //noinspection unchecked + entries = (Expression) exprs[0]; + //noinspection unchecked + slots = (Expression) exprs[1]; + return true; + } + + @Override + protected Float @Nullable [] get(Event event) { + SkriptSpawnerEntry[] entries = this.entries.getArray(event); + EquipmentSlot[] slots = this.slots.getArray(event); + + List dropChances = new ArrayList<>(slots.length * entries.length); + + for (SkriptSpawnerEntry entry : entries) { + Map dropChanceMap = entry.getDropChances(); + for (EquipmentSlot slot : slots) { + Float chance = dropChanceMap.get(slot); + if (chance == null) + continue; + + dropChances.add(chance); + } + } + + return dropChances.toArray(Float[]::new); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + return switch (mode) { + case SET, ADD, REMOVE, DELETE -> CollectionUtils.array(Float.class); + default -> null; + }; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + float chance = delta != null ? (float) delta[0] : 0; + + for (SkriptSpawnerEntry entry : this.entries.getArray(event)) { + for (EquipmentSlot slot : this.slots.getArray(event)) { + if (mode == ChangeMode.DELETE) + entry.removeDropChance(slot); + + entry.setDropChance(slot, switch (mode) { + case SET -> chance; + case ADD -> entry.getDropChances().getOrDefault(slot, 0f) + chance; + case REMOVE -> entry.getDropChances().getOrDefault(slot, 0f) - chance; + default -> 0; + }); + } + } + } + + @Override + public boolean isSingle() { + return entries.isSingle() && slots.isSingle(); + } + + @Override + public Class getReturnType() { + return Float.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + SyntaxStringBuilder builder = new SyntaxStringBuilder(event, debug); + builder.append("the drop chances of", entries, "for", slots); + return builder.toString(); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawnerentry/ExprSpawnerEntryEquipment.java b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawnerentry/ExprSpawnerEntryEquipment.java new file mode 100644 index 00000000000..b729758657b --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawnerentry/ExprSpawnerEntryEquipment.java @@ -0,0 +1,67 @@ +package org.skriptlang.skript.bukkit.spawners.elements.expressions.spawnerentry; + +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.expressions.base.SimplePropertyExpression; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.event.Event; +import org.bukkit.loot.LootTable; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.spawners.util.SkriptSpawnerEntry; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@Name("Spawner Entry Equipment") +@Description(""" + Returns the equipment loot table of a spawner entry. This loot table determines the equipment \ + (armor, weapons, tools, etc.) that the spawned entity will have. + Only loot tables specifically defined as equipment loot tables will have effect. + """) +@Example(""" + set {_entry} to the spawner entry of a pig: + set the spawner entry equipment to loot table "minecraft:equipment/trial_chamber" + set the drop chances for helmet and boots to 100% + """) +public class ExprSpawnerEntryEquipment extends SimplePropertyExpression { + + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.EXPRESSION, infoBuilder(ExprSpawnerEntryEquipment.class, LootTable.class, + "spawner entry equipment[s]", "spawnerentries", true) + .supplier(ExprSpawnerEntryEquipment::new) + .build() + ); + } + + @Override + public @Nullable LootTable convert(SkriptSpawnerEntry entry) { + return entry.getEquipmentLootTable(); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + return switch (mode) { + case SET, DELETE -> CollectionUtils.array(LootTable.class); + default -> null; + }; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + LootTable lootTable = delta != null ? (LootTable) delta[0] : null; + for (SkriptSpawnerEntry entry : getExpr().getArray(event)) { + entry.setEquipmentLootTable(lootTable); + } + } + + @Override + public Class getReturnType() { + return LootTable.class; + } + + @Override + protected String getPropertyName() { + return "spawner entry equipment"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawnerentry/ExprSpawnerEntryRule.java b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawnerentry/ExprSpawnerEntryRule.java new file mode 100644 index 00000000000..a39edcc0ae4 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawnerentry/ExprSpawnerEntryRule.java @@ -0,0 +1,72 @@ +package org.skriptlang.skript.bukkit.spawners.elements.expressions.spawnerentry; + +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +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.block.spawner.SpawnRule; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.spawners.util.SkriptSpawnerEntry; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@Name("Spawner Entry Rule") +@Description(""" + Returns the spawn rule of the spawner entry. Spawn rules determine the conditions under which the entry will be \ + spawned. + """) +@Example(""" + set {_entry} to a spawner entry of a cow: + set the spawn rule to a spawn rule: + set the maximum block light spawn level to 15 + set the minimum block light spawn level to 7 + set the maximum sky light spawn level to 11 + set the minimum sky light spawn level to 0 + delete the spawn rule + """) +@Since("INSERT VERSION") +public class ExprSpawnerEntryRule extends SimplePropertyExpression { + + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.EXPRESSION, infoBuilder(ExprSpawnerEntryRule.class, SpawnRule.class, + "spawn rule[s]", "spawnerentries", true) + .supplier(ExprSpawnerEntryRule::new) + .build() + ); + } + + @Override + public @Nullable SpawnRule convert(SkriptSpawnerEntry entry) { + return entry.getSpawnRule(); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + return switch (mode) { + case SET, DELETE -> CollectionUtils.array(SpawnRule.class); + default -> null; + }; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + SpawnRule rule = delta != null ? (SpawnRule) delta[0] : null; + for (SkriptSpawnerEntry entry : getExpr().getArray(event)) { + entry.setSpawnRule(rule); + } + } + + @Override + public Class getReturnType() { + return SpawnRule.class; + } + + @Override + protected String getPropertyName() { + return "spawner entry rule"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawnerentry/ExprSpawnerEntrySnapshot.java b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawnerentry/ExprSpawnerEntrySnapshot.java new file mode 100644 index 00000000000..4fcbf69d081 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawnerentry/ExprSpawnerEntrySnapshot.java @@ -0,0 +1,87 @@ +package org.skriptlang.skript.bukkit.spawners.elements.expressions.spawnerentry; + +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.entity.EntityData; +import ch.njol.skript.expressions.base.SimplePropertyExpression; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntitySnapshot; +import org.bukkit.event.Event; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.spawners.util.SkriptSpawnerEntry; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@Name("Spawner Entry Entity") +@Description(""" + Returns the entity snapshot of the spawner entry. The snapshot defines what entity the spawner entry represents. + """) +@Example(""" + set {_entry} to a spawner entry of a pig + broadcast "The entry is a %spawner entry snapshot of {_entry}%" # broadcasts "The entry is a pig" + """) +@Since("INSERT VERSION") +public class ExprSpawnerEntrySnapshot extends SimplePropertyExpression { + + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.EXPRESSION, infoBuilder(ExprSpawnerEntrySnapshot.class, EntitySnapshot.class, + "spawner entry [entity] snapshot[s]", "spawnerentries", true) + .supplier(ExprSpawnerEntrySnapshot::new) + .build() + ); + } + + @Override + public @NotNull EntitySnapshot convert(SkriptSpawnerEntry entry) { + return entry.getEntitySnapshot(); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + if (mode == ChangeMode.SET) + return CollectionUtils.array(EntitySnapshot.class, EntityData.class); + return null; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + assert delta != null; + Object object = delta[0]; + + EntitySnapshot snapshot = null; + if (object instanceof EntitySnapshot entitySnapshot) { + snapshot = entitySnapshot; + } else if (object instanceof EntityData entityData) { + Entity entity = entityData.create(); + if (entity == null) + return; + + //noinspection UnstableApiUsage + snapshot = entity.createSnapshot(); + if (snapshot == null) + return; + entity.remove(); + } + + assert snapshot != null; + + for (SkriptSpawnerEntry entry : getExpr().getArray(event)) { + entry.setEntitySnapshot(snapshot); + } + } + + @Override + public Class getReturnType() { + return EntitySnapshot.class; + } + + @Override + protected String getPropertyName() { + return "spawner entry snapshot"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawnrule/ExprSecSpawnRule.java b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawnrule/ExprSecSpawnRule.java new file mode 100644 index 00000000000..5002e9f8235 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawnrule/ExprSecSpawnRule.java @@ -0,0 +1,100 @@ +package org.skriptlang.skript.bukkit.spawners.elements.expressions.spawnrule; + +import ch.njol.skript.config.SectionNode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.SectionExpression; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.Trigger; +import ch.njol.skript.lang.TriggerItem; +import ch.njol.skript.lang.util.SectionUtils; +import ch.njol.skript.variables.Variables; +import ch.njol.util.Kleenean; +import org.bukkit.block.spawner.SpawnRule; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.spawners.util.events.SpawnRuleEvent; +import org.skriptlang.skript.registration.SyntaxInfo; +import org.skriptlang.skript.registration.SyntaxRegistry; + +import java.util.List; + +@Name("New Spawn Rule") +@Description(""" + Returns a new spawn rule. + """) +@Example(""" + set {_rule} to the spawn rule: + set the maximum block light spawn level to 12 + set the minimum block light spawn level to 8 + set the maximum sky light spawn level to 15 + set the minimum sky light spawn level to 4 + """) +@Example(""" + modify the spawner data of {_spawner}: + loop the spawner entries: + set the spawn rule to a spawn rule: + set the maximum block light spawn level to 15 + set the minimum block light spawn level to 10 + set the maximum sky light spawn level to 15 + set the minimum sky light spawn level to 5 + """) +@Since("INSERT VERSION") +public class ExprSecSpawnRule extends SectionExpression { + + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.EXPRESSION, SyntaxInfo.Expression.builder(ExprSecSpawnRule.class, SpawnRule.class) + .supplier(ExprSecSpawnRule::new) + .priority(SyntaxInfo.SIMPLE) + .addPattern("[a|the] spawn rule") + .build() + ); + } + + private Trigger trigger; + + @Override + public boolean init( + Expression[] exprs, int pattern, Kleenean isDelayed, ParseResult result, + @Nullable SectionNode node, @Nullable List triggerItems + ) { + if (node != null) { + trigger = SectionUtils.loadLinkedCode("spawn rule create", (beforeLoading, afterLoading) -> + loadCode(node, "spawn rule create", beforeLoading, afterLoading, SpawnRuleEvent.class) + ); + return trigger != null; + } + return true; + } + + @Override + protected SpawnRule @Nullable [] get(Event event) { + SpawnRule rule = new SpawnRule(0, 0, 0, 0); + if (trigger != null) { + SpawnRuleEvent ruleEvent = new SpawnRuleEvent(rule); + Variables.withLocalVariables(ruleEvent, ruleEvent, () -> + TriggerItem.walk(trigger, ruleEvent) + ); + } + return new SpawnRule[]{rule}; + } + + @Override + public boolean isSingle() { + return true; + } + + @Override + public Class getReturnType() { + return SpawnRule.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "the spawn rule"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawnrule/ExprSpawnRule.java b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawnrule/ExprSpawnRule.java new file mode 100644 index 00000000000..4d3b2657675 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawnrule/ExprSpawnRule.java @@ -0,0 +1,37 @@ +package org.skriptlang.skript.bukkit.spawners.elements.expressions.spawnrule; + +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.EventValueExpression; +import org.bukkit.block.spawner.SpawnRule; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@Name("Spawn Rule") +@Description(""" + The spawn rule used in the spawn rule section. + """) +@Examples(""" + the spawn rule + """) +@Since("INSERT VERSION") +public class ExprSpawnRule extends EventValueExpression { + + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.EXPRESSION, infoBuilder(ExprSpawnRule.class, SpawnRule.class, "[the] spawn rule") + .supplier(ExprSpawnRule::new) + .build() + ); + } + + public ExprSpawnRule() { + super(SpawnRule.class); + } + + @Override + public String toString() { + return "the spawn rule"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawnrule/ExprSpawnRuleLightLevel.java b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawnrule/ExprSpawnRuleLightLevel.java new file mode 100644 index 00000000000..ea8e3361a81 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/expressions/spawnrule/ExprSpawnRuleLightLevel.java @@ -0,0 +1,135 @@ +package org.skriptlang.skript.bukkit.spawners.elements.expressions.spawnrule; + +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +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.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import ch.njol.util.Math2; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.block.spawner.SpawnRule; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@Name("Spawn Rule Light Level") +@Description(""" + Returns the minimum or maximum block or sky light levels of a spawn rule. \ + Block light refers to the light level emitted by blocks, while sky light refers to sunlight. + + Valid values range from 0 to 15. The minimum value must be less than or equal to the maximum value. + """) +@Example(""" + set {_entry} to a spawner entry of a zombie: + set the spawn rule to a spawn rule: + set the maximum block light spawn level to 15 + set the minimum block light spawn level to 10 + set the maximum sky light spawn level to 15 + set the minimum sky light spawn level to 5 + """) +@Since("INSERT VERSION") +public class ExprSpawnRuleLightLevel extends SimplePropertyExpression { + + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.EXPRESSION, infoBuilder(ExprSpawnRuleLightLevel.class, Integer.class, + "(:max|min)[imum] (block|:sky) light [entity] spawn (level|value)[s]", "spawnrules", true) + .supplier(ExprSpawnRuleLightLevel::new) + .build() + ); + } + + private boolean max; + private boolean skyLight; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + max = parseResult.hasTag("max"); + skyLight = parseResult.hasTag("sky"); + return super.init(exprs, matchedPattern, isDelayed, parseResult); + } + + @Override + public Integer convert(SpawnRule rule) { + return getLightLevel(rule, max); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + return switch (mode) { + case SET, ADD, REMOVE -> CollectionUtils.array(Integer.class); + default -> null; + }; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + assert delta != null; + int light = Math2.fit(0, (int) delta[0], 15); + + for (SpawnRule rule : getExpr().getArray(event)) { + int minMax = getLightLevel(rule, max); + + int value = switch (mode) { + case SET -> light; + case ADD -> minMax + light; + case REMOVE -> minMax - light; + default -> 0; + }; + + value = Math2.fit(0, value, 15); + + String error = getErrorMessage(value, getLightLevel(rule, !max)); + if (error != null) { + error(error); + continue; + } + + if (skyLight) { + if (max) { + rule.setMaxSkyLight(value); + } else { + rule.setMinSkyLight(value); + } + } else { + if (max) { + rule.setMaxBlockLight(value); + } else { + rule.setMinBlockLight(value); + } + } + } + } + + private String getErrorMessage(int value, int compare) { + if (max && value < compare) { + return "The maximum block light level cannot be less than the minimum block light level, " + + " thus setting it to a value less than the minimum block light level will do nothing."; + } else if (!max && value > compare) { + return "The minimum block light spawn level cannot be greater than the maximum block light spawn level, " + + "thus setting it to a value greater than the maximum block light spawn level will do nothing."; + } + + return null; + } + + private int getLightLevel(SpawnRule rule, boolean max) { + if (skyLight) + return max ? rule.getMaxSkyLight() : rule.getMinSkyLight(); + return max ? rule.getMaxBlockLight() : rule.getMinBlockLight(); + } + + @Override + public Class getReturnType() { + return Integer.class; + } + + @Override + protected String getPropertyName() { + return (max ? "maximum" : "minimum") + " block light spawn level"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/sections/SecModifySpawnerData.java b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/sections/SecModifySpawnerData.java new file mode 100644 index 00000000000..596e531f2f2 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/spawners/elements/sections/SecModifySpawnerData.java @@ -0,0 +1,168 @@ +package org.skriptlang.skript.bukkit.spawners.elements.sections; + +import ch.njol.skript.config.SectionNode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.*; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.util.SectionUtils; +import ch.njol.skript.variables.Variables; +import ch.njol.util.Kleenean; +import org.bukkit.block.TrialSpawner; +import org.bukkit.event.Event; +import org.bukkit.spawner.Spawner; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.spawners.util.SpawnerDataType; +import org.skriptlang.skript.bukkit.spawners.util.SpawnerUtils; +import org.skriptlang.skript.bukkit.spawners.util.events.MobSpawnerDataEvent; +import org.skriptlang.skript.bukkit.spawners.util.events.SpawnerDataEvent; +import org.skriptlang.skript.bukkit.spawners.util.events.TrialSpawnerDataEvent; +import org.skriptlang.skript.bukkit.spawners.util.spawnerdata.SkriptMobSpawnerData; +import org.skriptlang.skript.bukkit.spawners.util.spawnerdata.SkriptTrialSpawnerData; +import org.skriptlang.skript.registration.SyntaxInfo; +import org.skriptlang.skript.registration.SyntaxRegistry; + +import java.util.List; + +@Name("Modify Spawner Data") +@Description(""" + Directly modifies the mob, trial, or spawner data of the given spawners. \ + For example, 'modify the ominous and regular trial spawner data of {_trial spawner}' \ + updates both trial spawner's states. +""") +@Example(""" + modify the spawner data of event-block: + set the activation range to 14 + add 2 to the maximum spawn delay + delete the spawner entries + """) +@Example(""" + modify the mob spawner data of event-block: + set the spawn count to 4 + add 5 to the minimum spawn delay + add 3 to the maximum nearby entity cap + """) +@Example(""" + modify the trial spawner data of event-block: + set the base entity spawn count to 10 + add {_entry} to the spawner entries + set the reward entry weight for loot table "minecraft:chests/simple_dungeon" to 15 + """) +@Since("INSERT VERSION") +public class SecModifySpawnerData extends Section { + + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.SECTION, SyntaxInfo.builder(SecModifySpawnerData.class) + .supplier(SecModifySpawnerData::new) + .priority(SyntaxInfo.COMBINED) + .addPatterns( + "modify [the] [:mob] spawner data of %blocks/entities%", + "modify [the] [:ominous|:regular|:ominous and regular] trial:trial spawner data of %blocks%") + .build()); + } + + private enum TrialSpawnerState { + OMINOUS, REGULAR, BOTH, CURRENT; + + public static TrialSpawnerState fromTags(List tags) { + if (tags.contains("ominous")) { + return OMINOUS; + } else if (tags.contains("ominous and regular")) { + return BOTH; + } else if (tags.contains("regular")) { + return REGULAR; + } else { + return CURRENT; + } + } + } + + + private Expression spawners; + private SpawnerDataType dataType; + private TrialSpawnerState state; + + private Trigger trigger; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult, SectionNode sectionNode, List triggerItems) { + spawners = exprs[0]; + dataType = SpawnerDataType.fromTags(parseResult.tags); + state = TrialSpawnerState.fromTags(parseResult.tags); + + trigger = SectionUtils.loadLinkedCode("modify spawner data", (beforeLoading, afterLoading) + -> loadCode(sectionNode, "modify spawner data", beforeLoading, afterLoading, SpawnerDataEvent.class)); + return trigger != null; + } + + @Override + protected @Nullable TriggerItem walk(Event event) { + for (Object spawnerObject : spawners.getArray(event)) { + if (!dataType.matches(spawnerObject)) + continue; + + if (SpawnerUtils.isMobSpawner(spawnerObject)) { + Spawner mobSpawner = SpawnerUtils.getMobSpawner(spawnerObject); + SkriptMobSpawnerData mobData = SkriptMobSpawnerData.fromSpawner(mobSpawner); + + MobSpawnerDataEvent mobEvent = new MobSpawnerDataEvent(mobData); + Variables.withLocalVariables(event, mobEvent, () -> TriggerItem.walk(trigger, mobEvent)); + + mobData.applyData(mobSpawner); + } else { + TrialSpawner trialSpawner = SpawnerUtils.getTrialSpawner(spawnerObject); + SkriptTrialSpawnerData trialData = switch (state) { + case CURRENT -> SkriptTrialSpawnerData.fromTrialSpawner(trialSpawner, trialSpawner.isOminous()); + case OMINOUS -> SkriptTrialSpawnerData.fromTrialSpawner(trialSpawner, true); + // start with regular, then modify the ominous if needed + case REGULAR, BOTH -> SkriptTrialSpawnerData.fromTrialSpawner(trialSpawner, false); + }; + + TrialSpawnerDataEvent regularEvent = new TrialSpawnerDataEvent(trialData); + Variables.withLocalVariables(event, regularEvent, () -> TriggerItem.walk(trigger, regularEvent)); + + if (state == TrialSpawnerState.BOTH) { + // guaranteed to be the regular data here + trialData.applyData(trialSpawner, false); + + // modify the ominous data + trialData = SkriptTrialSpawnerData.fromTrialSpawner(trialSpawner, true); + TrialSpawnerDataEvent ominousEvent = new TrialSpawnerDataEvent(trialData); + Variables.withLocalVariables(event, ominousEvent, () -> TriggerItem.walk(trigger, ominousEvent)); + trialData.applyData(trialSpawner, true); + } else { + trialData.applyData(trialSpawner, switch (state) { + case CURRENT -> trialSpawner.isOminous(); + case OMINOUS -> true; + case REGULAR -> false; + case BOTH -> throw new IllegalStateException(); + }); + } + } + } + + return super.walk(event, false); + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + SyntaxStringBuilder builder = new SyntaxStringBuilder(event, debug); + + builder.append("modify the"); + if (dataType.isTrial()) { + if (state == TrialSpawnerState.REGULAR) { + builder.append("regular"); + } else if (state == TrialSpawnerState.OMINOUS) { + builder.append("ominous"); + } else { + builder.append("ominous and regular"); + } + } + builder.append(dataType.toString(), "spawner data of", spawners); + + return builder.toString(); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/spawners/util/SkriptSpawnerEntry.java b/src/main/java/org/skriptlang/skript/bukkit/spawners/util/SkriptSpawnerEntry.java new file mode 100644 index 00000000000..75e5d2504c1 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/spawners/util/SkriptSpawnerEntry.java @@ -0,0 +1,189 @@ +package org.skriptlang.skript.bukkit.spawners.util; + +import ch.njol.skript.lang.util.common.AnyWeighted; +import com.google.common.base.Preconditions; +import org.bukkit.block.spawner.SpawnRule; +import org.bukkit.block.spawner.SpawnerEntry; +import org.bukkit.block.spawner.SpawnerEntry.Equipment; +import org.bukkit.entity.EntitySnapshot; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.loot.LootTable; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.UnknownNullability; + +import java.util.HashMap; +import java.util.Map; + +/** + * Represents a Skript spawner entry, which is a wrapper around a Bukkit {@link SpawnerEntry}. + */ +public class SkriptSpawnerEntry implements AnyWeighted { + + private @NotNull EntitySnapshot entitySnapshot; + private @Nullable SpawnRule spawnRule; + private @Nullable LootTable equipmentLootTable; + private @NotNull Map dropChances = new HashMap<>(); + + private int weight = 1; + + /** + * Creates a new SkriptSpawnerEntry with the given entity snapshot. + * @param entitySnapshot the entity snapshot + */ + public SkriptSpawnerEntry(@NotNull EntitySnapshot entitySnapshot) { + Preconditions.checkNotNull(entitySnapshot, "snapshot cannot be null"); + this.entitySnapshot = entitySnapshot; + } + + /** + * Creates a SkriptSpawnerEntry from a Bukkit SpawnerEntry. + * @param entry the Bukkit SpawnerEntry to convert + * @return a SkriptSpawnerEntry with the same properties as the Bukkit SpawnerEntry + */ + public static SkriptSpawnerEntry fromSpawnerEntry(@NotNull SpawnerEntry entry) { + Preconditions.checkNotNull(entry, "entry cannot be null"); + + SkriptSpawnerEntry skriptEntry = new SkriptSpawnerEntry(entry.getSnapshot()); + skriptEntry.setWeight(entry.getSpawnWeight()); + skriptEntry.setSpawnRule(entry.getSpawnRule()); + + Equipment equipment = entry.getEquipment(); + if (equipment != null) { + skriptEntry.setEquipmentLootTable(equipment.getEquipmentLootTable()); + skriptEntry.setDropChances(equipment.getDropChances()); + } + + return skriptEntry; + } + + /** + * Converts a SkriptSpawnerEntry to a Bukkit SpawnerEntry. + * @param skriptEntry the SkriptSpawnerEntry to convert + * @return a Bukkit SpawnerEntry with the same properties as the SkriptSpawnerEntry + */ + public static SpawnerEntry toSpawnerEntry(@NotNull SkriptSpawnerEntry skriptEntry) { + Preconditions.checkNotNull(skriptEntry, "skriptEntry cannot be null"); + + SpawnerEntry entry = new SpawnerEntry( + skriptEntry.getEntitySnapshot(), + skriptEntry.weight().intValue(), + skriptEntry.getSpawnRule() + ); + + LootTable lootTable = skriptEntry.getEquipmentLootTable(); + Map dropChances = skriptEntry.getDropChances(); + if (lootTable != null) + entry.setEquipment(new Equipment(lootTable, dropChances)); + + return entry; + } + + @Override + public @UnknownNullability Number weight() { + return weight; + } + + @Override + public boolean supportsWeightChange() { + return true; + } + + @Override + public void setWeight(Number weight) { + this.weight = weight.intValue(); + } + + /** + * Gets the entity snapshot of this spawner entry. + * @return the entity snapshot + */ + public @NotNull EntitySnapshot getEntitySnapshot() { + return entitySnapshot; + } + + /** + * Sets the entity snapshot of this spawner entry. + * @param entitySnapshot the new entity snapshot + */ + public void setEntitySnapshot(@NotNull EntitySnapshot entitySnapshot) { + Preconditions.checkNotNull(entitySnapshot, "snapshot cannot be null"); + this.entitySnapshot = entitySnapshot; + } + + /** + * Gets the spawn rule of this spawner entry. + * @return the spawn rule, or null if not set + */ + public @Nullable SpawnRule getSpawnRule() { + return spawnRule; + } + + /** + * Sets the spawn rule of this spawner entry. + * @param spawnRule the new spawn rule, or null to remove + */ + public void setSpawnRule(@Nullable SpawnRule spawnRule) { + this.spawnRule = spawnRule; + } + + /** + * Gets the equipment loot table of this spawner entry, which represents the loot for the entity's equipment. + * @return the equipment loot table, or null if not set + */ + public @Nullable LootTable getEquipmentLootTable() { + return equipmentLootTable; + } + + /** + * Sets the equipment loot table of this spawner entry. + * @param equipmentLootTable the new equipment loot table, or null to remove + */ + public void setEquipmentLootTable(@Nullable LootTable equipmentLootTable) { + this.equipmentLootTable = equipmentLootTable; + } + + /** + * Gets the drop chances for each equipment slot in this spawner entry. + * @return a map of equipment slots to their drop chances, can be empty + */ + public @NotNull Map getDropChances() { + return Map.copyOf(dropChances); + } + + /** + * Sets the drop chances for each equipment slot in this spawner entry. + * @param dropChances a map of equipment slots to their drop chances, cannot be null + */ + public void setDropChances(@NotNull Map dropChances) { + Preconditions.checkNotNull(dropChances, "dropChances cannot be null"); + this.dropChances = new HashMap<>(dropChances); + } + + /** + * Sets the drop chance for a specific equipment slot in this spawner entry. + * @param slot the equipment slot to set the drop chance for + * @param chance the drop chance to set 1 is 100%, 0 is 0% + */ + public void setDropChance(@NotNull EquipmentSlot slot, float chance) { + Preconditions.checkNotNull(slot, "slot cannot be null"); + this.dropChances.put(slot, chance); + } + + /** + * Removes the drop chance for a specific equipment slot in this spawner entry. + * @param slot the equipment slot to remove the drop chance for + */ + public void removeDropChance(@NotNull EquipmentSlot slot) { + Preconditions.checkNotNull(slot, "slot cannot be null"); + this.dropChances.remove(slot); + } + + /** + * Clears all drop chances in this spawner entry. + */ + public void clearDropChances() { + this.dropChances.clear(); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/spawners/util/SpawnerDataType.java b/src/main/java/org/skriptlang/skript/bukkit/spawners/util/SpawnerDataType.java new file mode 100644 index 00000000000..5c963194f84 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/spawners/util/SpawnerDataType.java @@ -0,0 +1,90 @@ +package org.skriptlang.skript.bukkit.spawners.util; + +import org.skriptlang.skript.bukkit.spawners.util.spawnerdata.SkriptMobSpawnerData; +import org.skriptlang.skript.bukkit.spawners.util.spawnerdata.SkriptSpawnerData; +import org.skriptlang.skript.bukkit.spawners.util.spawnerdata.SkriptTrialSpawnerData; + +import java.util.List; +import java.util.Locale; + +/** + * Represents the type of spawner data. + */ +public enum SpawnerDataType { + + MOB(SkriptMobSpawnerData.class), + TRIAL(SkriptTrialSpawnerData.class), + ANY(SkriptSpawnerData.class); + + /** + * Creates a SpawnerDataType from the given parse result tags. + * @param tags the tags from the parse result + * @return the corresponding SpawnerDataType + */ + public static SpawnerDataType fromTags(List tags) { + if (tags.contains("trial")) { + return TRIAL; + } else if (tags.contains("mob")) { + return MOB; + } + return ANY; + } + + private final Class dataClass; + + SpawnerDataType(Class dataClass) { + this.dataClass = dataClass; + } + + /** + * Gets the class of the SkriptSpawnerData associated with this type. + * @return the class of SkriptSpawnerData + */ + public Class getDataClass() { + return dataClass; + } + + /** + * @return whether the type is {@link #MOB} + */ + public boolean isMob() { + return this == MOB; + } + + /** + * @return whether the type is {@link #TRIAL} + */ + public boolean isTrial() { + return this == TRIAL; + } + + /** + * @return whether the type is {@link #ANY} + */ + public boolean isAny() { + return this == ANY; + } + + /** + * Checks if the given spawner object matches this spawner data type. + * @param spawnerObject the spawner object to check + * @return true if the spawner object matches the type, false otherwise + */ + public boolean matches(Object spawnerObject) { + if (isTrial()) { + return SpawnerUtils.isTrialSpawner(spawnerObject); + } else if (isMob()) { + return SpawnerUtils.isMobSpawner(spawnerObject); + } + + return SpawnerUtils.isMobSpawner(spawnerObject) || SpawnerUtils.isTrialSpawner(spawnerObject); + } + + @Override + public String toString() { + if (isAny()) + return ""; + return name().toLowerCase(Locale.ENGLISH); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/spawners/util/SpawnerUtils.java b/src/main/java/org/skriptlang/skript/bukkit/spawners/util/SpawnerUtils.java new file mode 100644 index 00000000000..2733c1e5952 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/spawners/util/SpawnerUtils.java @@ -0,0 +1,135 @@ +package org.skriptlang.skript.bukkit.spawners.util; + +import ch.njol.skript.Skript; +import ch.njol.skript.util.Timespan; +import ch.njol.skript.util.Timespan.TimePeriod; +import org.bukkit.block.Block; +import org.bukkit.block.TrialSpawner; +import org.bukkit.spawner.BaseSpawner; +import org.bukkit.spawner.Spawner; +import org.bukkit.spawner.TrialSpawnerConfiguration; +import org.jetbrains.annotations.UnknownNullability; + +/** + * Utility class for spawners. + */ +@SuppressWarnings("UnstableApiUsage") +public class SpawnerUtils { + + public static boolean IS_RUNNING_1_21_4 = Skript.isRunningMinecraft(1, 21, 4); + + public static final int DEFAULT_ACTIVATION_RANGE = 16; + public static final int DEFAULT_MAX_NEARBY_ENTITIES = 6; + public static final int DEFAULT_SPAWN_RANGE = 4; + public static final int DEFAULT_SPAWN_COUNT = 4; + + public static final Timespan DEFAULT_MAX_SPAWN_DELAY = new Timespan(TimePeriod.TICK, 800); + public static final Timespan DEFAULT_MIN_SPAWN_DELAY = new Timespan(TimePeriod.TICK, 200); + public static final Timespan DEFAULT_COOLDOWN_LENGTH = new Timespan(TimePeriod.TICK, 36_000); + public static final Timespan DEFAULT_TRIAL_SPAWN_DELAY = new Timespan(TimePeriod.TICK, 40); + + public static final int DEFAULT_TRIAL_ACTIVATION_RANGE = 14; + public static final int DEFAULT_BASE_MOB_AMOUNT = 6; + public static final int DEFAULT_BASE_PER_PLAYER_INCREMENT = 2; + public static final int DEFAULT_CONCURRENT_MOB_AMOUNT = 2; + public static final int DEFAULT_CONCURRENT_PER_PLAYER_INCREMENT = 1; + + /** + * Checks if the given object is a spawner (mob spawner or trial spawner). + * @param object the object to check + * @return whether the object is a spawner + */ + public static boolean isSpawner(Object object) { + return isMobSpawner(object) || isTrialSpawner(object); + } + + /** + * Gets the spawner (mob spawner or trial spawner configuration, by default non-ominous) from the given object. + * @param object the object to get the spawner from + * @return the spawner, or null if the object is not a spawner + * @see #isSpawner(Object) + */ + public static @UnknownNullability BaseSpawner getSpawner(Object object) { + if (isMobSpawner(object)) { + return getMobSpawner(object); + } else if (isTrialSpawner(object)) { + return getTrialSpawnerConfiguration(getTrialSpawner(object)); + } + + return null; + } + + /** + * Checks if the given object is a mob spawner. + * @param object the object to check + * @return whether the object is a mob spawner + */ + public static boolean isMobSpawner(Object object) { + if (object instanceof Block block) + return block.getState() instanceof Spawner; + return object instanceof Spawner; + } + + /** + * Retrieves the mob spawner from the given object. + * @param object the object to retrieve the mob spawner from + * @return the mob spawner + * @see #isMobSpawner(Object) + */ + public static Spawner getMobSpawner(Object object) { + if (object instanceof Block block) + return (Spawner) block.getState(); + return (Spawner) object; + } + + /** + * Checks if the given object is a trial spawner. + * + * @param object the object to check + * @return whether the object is a trial spawner + * @see #getTrialSpawner(Object) + */ + public static boolean isTrialSpawner(Object object) { + if (object instanceof Block block) + return block.getState() instanceof TrialSpawner; + return object instanceof TrialSpawner; + } + + /** + * Retrieves the trial spawner from the given object. + * + * @param object the object to retrieve the trial spawner from. + * @return the trial spawner + * @see #isTrialSpawner(Object) + */ + public static TrialSpawner getTrialSpawner(Object object) { + if (object instanceof Block block) + return (TrialSpawner) block.getState(); + return (TrialSpawner) object; + } + + /** + * Returns the trial spawner configuration for the given trial spawner. + * + * @param trialSpawner the trial spawner to retrieve the configuration for + * @param ominous whether to retrieve the ominous configuration + * @return the trial spawner configuration + */ + public static TrialSpawnerConfiguration getTrialSpawnerConfiguration(TrialSpawner trialSpawner, boolean ominous) { + if (ominous) + return trialSpawner.getOminousConfiguration(); + return trialSpawner.getNormalConfiguration(); + } + + /** + * Returns the trial spawner configuration for the given trial spawner. + * Automatically determines whether to retrieve the ominous configuration. + * + * @param trialSpawner the trial spawner to retrieve the configuration for + * @return the trial spawner configuration + */ + public static TrialSpawnerConfiguration getTrialSpawnerConfiguration(TrialSpawner trialSpawner) { + return getTrialSpawnerConfiguration(trialSpawner, trialSpawner.isOminous()); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/spawners/util/events/MobSpawnerDataEvent.java b/src/main/java/org/skriptlang/skript/bukkit/spawners/util/events/MobSpawnerDataEvent.java new file mode 100644 index 00000000000..c91907e43b9 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/spawners/util/events/MobSpawnerDataEvent.java @@ -0,0 +1,14 @@ +package org.skriptlang.skript.bukkit.spawners.util.events; + +import org.skriptlang.skript.bukkit.spawners.util.spawnerdata.SkriptMobSpawnerData; + +/** + * Event to allow retrieving the mob spawner data in the spawner data sections. + */ +public class MobSpawnerDataEvent extends SpawnerDataEvent { + + public MobSpawnerDataEvent(SkriptMobSpawnerData data) { + super(data); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/spawners/util/events/SpawnRuleEvent.java b/src/main/java/org/skriptlang/skript/bukkit/spawners/util/events/SpawnRuleEvent.java new file mode 100644 index 00000000000..cb1283746dc --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/spawners/util/events/SpawnRuleEvent.java @@ -0,0 +1,32 @@ +package org.skriptlang.skript.bukkit.spawners.util.events; + +import org.bukkit.block.spawner.SpawnRule; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Event to allow retrieving the spawn rule in the spawn rule sections. + */ +public class SpawnRuleEvent extends Event { + + private final SpawnRule rule; + + public SpawnRuleEvent(SpawnRule rule) { + this.rule = rule; + } + + /** + * Gets the spawn rule associated with this event. + * @return the spawn rule + */ + public SpawnRule getSpawnRule() { + return rule; + } + + @Override + public @NotNull HandlerList getHandlers() { + throw new UnsupportedOperationException(); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/spawners/util/events/SpawnerDataEvent.java b/src/main/java/org/skriptlang/skript/bukkit/spawners/util/events/SpawnerDataEvent.java new file mode 100644 index 00000000000..d8fe893f3fb --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/spawners/util/events/SpawnerDataEvent.java @@ -0,0 +1,28 @@ +package org.skriptlang.skript.bukkit.spawners.util.events; + +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; +import org.skriptlang.skript.bukkit.spawners.util.spawnerdata.SkriptSpawnerData; + +/** + * Event to allow retrieving the spawner datas in the spawner data sections. + */ +public class SpawnerDataEvent extends Event { + + private final T data; + + public SpawnerDataEvent(T data) { + this.data = data; + } + + public T getSpawnerData() { + return data; + } + + @Override + public @NotNull HandlerList getHandlers() { + throw new UnsupportedOperationException(); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/spawners/util/events/SpawnerEntryEvent.java b/src/main/java/org/skriptlang/skript/bukkit/spawners/util/events/SpawnerEntryEvent.java new file mode 100644 index 00000000000..76b952d6403 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/spawners/util/events/SpawnerEntryEvent.java @@ -0,0 +1,32 @@ +package org.skriptlang.skript.bukkit.spawners.util.events; + +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; +import org.skriptlang.skript.bukkit.spawners.util.SkriptSpawnerEntry; + +/** + * Event to allow retrieving the spawner entry in the spawner entry sections. + */ +public class SpawnerEntryEvent extends Event { + + private final SkriptSpawnerEntry entry; + + public SpawnerEntryEvent(SkriptSpawnerEntry entry) { + this.entry = entry; + } + + /** + * Gets the Skript spawner entry associated with this event. + * @return the Skript spawner entry + */ + public SkriptSpawnerEntry getSpawnerEntry() { + return entry; + } + + @Override + public @NotNull HandlerList getHandlers() { + throw new UnsupportedOperationException(); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/spawners/util/events/TrialSpawnerDataEvent.java b/src/main/java/org/skriptlang/skript/bukkit/spawners/util/events/TrialSpawnerDataEvent.java new file mode 100644 index 00000000000..2016d0ed1d2 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/spawners/util/events/TrialSpawnerDataEvent.java @@ -0,0 +1,14 @@ +package org.skriptlang.skript.bukkit.spawners.util.events; + +import org.skriptlang.skript.bukkit.spawners.util.spawnerdata.SkriptTrialSpawnerData; + +/** + * Event to allow retrieving the trial spawner data in the spawner data sections. + */ +public class TrialSpawnerDataEvent extends SpawnerDataEvent { + + public TrialSpawnerDataEvent(SkriptTrialSpawnerData data) { + super(data); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/spawners/util/spawnerdata/SkriptMobSpawnerData.java b/src/main/java/org/skriptlang/skript/bukkit/spawners/util/spawnerdata/SkriptMobSpawnerData.java new file mode 100644 index 00000000000..71ad1f8ab7b --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/spawners/util/spawnerdata/SkriptMobSpawnerData.java @@ -0,0 +1,127 @@ +package org.skriptlang.skript.bukkit.spawners.util.spawnerdata; + +import ch.njol.skript.util.Timespan; +import ch.njol.skript.util.Timespan.TimePeriod; +import ch.njol.yggdrasil.Fields; +import ch.njol.yggdrasil.YggdrasilSerializable.YggdrasilExtendedSerializable; +import com.google.common.base.Preconditions; +import org.bukkit.block.CreatureSpawner; +import org.bukkit.entity.minecart.SpawnerMinecart; +import org.bukkit.spawner.Spawner; +import org.jetbrains.annotations.NotNull; +import org.skriptlang.skript.bukkit.spawners.util.SpawnerUtils; + +import java.io.StreamCorruptedException; + +/** + * Represents the data of a {@link Spawner}, which may be a {@link CreatureSpawner} or a {@link SpawnerMinecart} + * + * @see SkriptTrialSpawnerData + * @see SkriptSpawnerData + */ +public class SkriptMobSpawnerData extends SkriptSpawnerData implements YggdrasilExtendedSerializable { + + private int maxNearbyEntityCap = SpawnerUtils.DEFAULT_MAX_NEARBY_ENTITIES; + private int spawnCount = SpawnerUtils.DEFAULT_SPAWN_COUNT; + + public SkriptMobSpawnerData() {} + + /** + * Creates a new SkriptSpawnerData instance from the given Bukkit {@link Spawner}. + * @param spawner the Bukkit spawner to convert + * @return a new SkriptSpawnerData instance containing the data from the Bukkit spawner + */ + public static SkriptMobSpawnerData fromSpawner(@NotNull Spawner spawner) { + Preconditions.checkNotNull(spawner, "spawner cannot be null"); + + SkriptMobSpawnerData data = new SkriptMobSpawnerData(); + + SkriptSpawnerData.applyToSpawnerData(spawner, data); + data.setMaxNearbyEntityCap(spawner.getMaxNearbyEntities()); + data.setSpawnCount(spawner.getSpawnCount()); + data.setMaxSpawnDelay(new Timespan(TimePeriod.TICK, spawner.getMaxSpawnDelay())); + data.setMinSpawnDelay(new Timespan(TimePeriod.TICK, spawner.getMinSpawnDelay())); + + return data; + } + + /** + * Applies this SkriptSpawnerData to the given spawner. + * @param spawner the spawner to apply the data to + */ + public void applyData(@NotNull Spawner spawner) { + Preconditions.checkNotNull(spawner, "spawner cannot be null"); + + super.applyToSpawner(spawner); + spawner.setMaxNearbyEntities(getMaxNearbyEntityCap()); + spawner.setSpawnCount(getSpawnCount()); + spawner.setMaxSpawnDelay((int) Math.max(getMaxSpawnDelay().getAs(TimePeriod.TICK), Integer.MAX_VALUE)); + spawner.setMinSpawnDelay((int) Math.max(getMinSpawnDelay().getAs(TimePeriod.TICK), Integer.MAX_VALUE)); + + if (spawner instanceof CreatureSpawner creatureSpawner) + creatureSpawner.update(true, false); + } + + /** + * Returns the maximum number of nearby similar entities that can be spawned by this spawner. + *

+ * The default value is 6. + * @return the maximum nearby entity cap + */ + public int getMaxNearbyEntityCap() { + return maxNearbyEntityCap; + } + + /** + * Sets the maximum number of nearby similar entities that can be spawned by this spawner. + *

+ * The default value is 6. + * @param maxNearbyEntityCap the maximum nearby entity cap + */ + public void setMaxNearbyEntityCap(int maxNearbyEntityCap) { + this.maxNearbyEntityCap = maxNearbyEntityCap; + } + + /** + * Returns the number of entities that the spawner will attempt to spawn each spawn attempt. + *

+ * The default value is 4. + * @return the spawn count + */ + public int getSpawnCount() { + return spawnCount; + } + + /** + * Sets the number of entities that the spawner will attempt to spawn each spawn attempt. + *

+ * The default value is 4. + * @param spawnCount the spawn count + */ + public void setSpawnCount(int spawnCount) { + this.spawnCount = spawnCount; + } + + /* + * YggdrasilExtendedSerializable + */ + + @Override + public Fields serialize() { + Fields fields = super.serialize(); + + fields.putPrimitive("max_nearby_entity_cap", this.maxNearbyEntityCap); + fields.putPrimitive("spawn_count", this.spawnCount); + + return fields; + } + + @Override + public void deserialize(@NotNull Fields fields) throws StreamCorruptedException { + super.deserialize(fields); + + this.maxNearbyEntityCap = fields.getPrimitive("max_nearby_entity_cap", int.class); + this.spawnCount = fields.getPrimitive("spawn_count", int.class); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/spawners/util/spawnerdata/SkriptSpawnerData.java b/src/main/java/org/skriptlang/skript/bukkit/spawners/util/spawnerdata/SkriptSpawnerData.java new file mode 100644 index 00000000000..94b6ccb50ad --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/spawners/util/spawnerdata/SkriptSpawnerData.java @@ -0,0 +1,291 @@ +package org.skriptlang.skript.bukkit.spawners.util.spawnerdata; + +import ch.njol.skript.util.Timespan; +import ch.njol.yggdrasil.Fields; +import com.google.common.base.Preconditions; +import org.bukkit.spawner.BaseSpawner; +import org.jetbrains.annotations.NotNull; +import org.skriptlang.skript.bukkit.spawners.util.SkriptSpawnerEntry; +import org.skriptlang.skript.bukkit.spawners.util.SpawnerUtils; + +import java.io.StreamCorruptedException; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Abstract class representing the data of a trial spawner or a regular spawner. + */ +public abstract class SkriptSpawnerData { + + private int activationRange = SpawnerUtils.DEFAULT_ACTIVATION_RANGE; + private int spawnRange = SpawnerUtils.DEFAULT_SPAWN_RANGE; + + private Timespan minSpawnDelay = SpawnerUtils.DEFAULT_MIN_SPAWN_DELAY; + private Timespan maxSpawnDelay = SpawnerUtils.DEFAULT_MAX_SPAWN_DELAY; + + private @NotNull Set spawnerEntries = new HashSet<>(); + + /** + * Applies the spawner data from the given spawner to this SkriptSpawnerData instance. + * @param spawner the spawner to apply the data from + */ + protected static void applyToSpawnerData(@NotNull BaseSpawner spawner, @NotNull SkriptSpawnerData data) { + Preconditions.checkNotNull(spawner, "spawner cannot be null"); + + data.setActivationRange(spawner.getRequiredPlayerRange()); + data.setSpawnRange(spawner.getSpawnRange()); + data.setSpawnerEntries(spawner.getPotentialSpawns().stream() + .map(SkriptSpawnerEntry::fromSpawnerEntry) + .collect(Collectors.toSet()) + ); + } + + /** + * Applies the current spawner data to the given spawner. + * @param spawner the spawner to apply the data to + */ + protected void applyToSpawner(@NotNull BaseSpawner spawner) { + Preconditions.checkNotNull(spawner, "spawner cannot be null"); + + spawner.setRequiredPlayerRange(getActivationRange()); + spawner.setSpawnRange(getSpawnRange()); + if (!getSpawnerEntries().isEmpty()) { + spawner.setPotentialSpawns(getSpawnerEntries().stream() + .map(SkriptSpawnerEntry::toSpawnerEntry) + .toList() + ); + } + } + + /** + * The distance a player must be from the spawner for it to be activated. + *

+ * If the value is less than or equal to 0, the spawner will always be active (given that there are players online). + *

+ * The default value is 16. + * @return the activation range + */ + public int getActivationRange() { + return activationRange; + } + + /** + * Sets the distance a player must be from the spawner for it to be activated. + *

+ * If the value is less than or equal to 0, the spawner will always be active (given that there are players online). + *

+ * The default value is 16. + * @param activationRange the activation range + */ + public void setActivationRange(int activationRange) { + this.activationRange = activationRange; + } + + /** + * Gets the radius around which the spawner will attempt to spawn mobs. + *

+ * This area is square, includes the block the spawner is in, and is centered on the spawner's x, z coordinates - not the spawner itself. + *

+ * The default value is 4. + * @return the spawn range + */ + public int getSpawnRange() { + return spawnRange; + } + + /** + * Sets the radius around which the spawner will attempt to spawn mobs. + *

+ * This area is square, includes the block the spawner is in, and is centered on the spawner's x, z coordinates - not the spawner itself. + *

+ * The default value is 4. + * @param spawnRange the spawn range + */ + public void setSpawnRange(int spawnRange) { + Preconditions.checkArgument(spawnRange > 0, "Spawn range must be > 0"); + this.spawnRange = spawnRange; + } + + /** + * Returns the maximum spawn delay of the spawner. + *
+ * The spawn delay for regular spawners is chosen randomly between the minimum and maximum spawn delays, + * which determines how long the spawner will wait before attempting to spawn entities again. + *

+ * The maximum spawn delay is always greater than or equal to the minimum spawn delay. + *

+ * The default value for regular spawners is 40 seconds (800 ticks). + * @return the maximum spawn delay + */ + public @NotNull Timespan getMaxSpawnDelay() { + return maxSpawnDelay; + } + + /** + * Sets the maximum spawn delay of the spawner. + *
+ * The spawn delay for regular spawners is chosen randomly between the minimum and maximum spawn delays, + * which determines how long the spawner will wait before attempting to spawn entities again. + *

+ * The maximum spawn delay must be greater than or equal to the minimum spawn delay. + *

+ * The default value for regular spawners is 40 seconds (800 ticks). + *

+ * For trial spawners, the minimum and maximum spawn delays are always identical. This results in a fixed delay, + * rather than a random range. + *

+ * The default value for trial spawners is 2 seconds (40 ticks). + * @param maxSpawnDelay the maximum spawn delay to set + */ + public void setMaxSpawnDelay(@NotNull Timespan maxSpawnDelay) { + Preconditions.checkNotNull(maxSpawnDelay, "Maximum spawn delay cannot be null"); + Preconditions.checkArgument(maxSpawnDelay.compareTo(minSpawnDelay) >= 0, + "Maximum spawn delay cannot be less than minimum spawn delay"); + this.maxSpawnDelay = maxSpawnDelay; + } + + /** + * Returns the minimum spawn delay of the spawner. + *
+ * The spawn delay for regular spawners is chosen randomly between the minimum and maximum spawn delays, + * which determines how long the spawner will wait before attempting to spawn entities again. + *

+ * The minimum spawn delay is always less than or equal to the maximum spawn delay. + *

+ * The default value for regular spawners is 10 seconds (200 ticks). + *

+ * For trial spawners, the minimum and maximum spawn delays are always identical. This results in a fixed delay, + * rather than a random range. + *

+ * The default value for trial spawners is 2 seconds (40 ticks). + * @return the minimum spawn delay + */ + public @NotNull Timespan getMinSpawnDelay() { + return minSpawnDelay; + } + + /** + * Sets the minimum spawn delay of the spawner. + *
+ * The spawn delay for regular spawners is chosen randomly between the minimum and maximum spawn delays, + * which determines how long the spawner will wait before attempting to spawn entities again. + *

+ * The minimum spawn delay must not be greater than the maximum spawn delay. + *

+ * The default value for regular spawners is 10 seconds (200 ticks). + *

+ * For trial spawners, the minimum and maximum spawn delays are always identical. This results in a fixed delay, + * rather than a random range. + *

+ * The default value for trial spawners is 2 seconds (40 ticks). + * @param minSpawnDelay the minimum spawn delay to set + */ + public void setMinSpawnDelay(@NotNull Timespan minSpawnDelay) { + Preconditions.checkNotNull(minSpawnDelay, "Minimum spawn delay cannot be null"); + Preconditions.checkArgument(minSpawnDelay.compareTo(maxSpawnDelay) <= 0, + "Minimum spawn delay cannot be greater than the maximum spawn delay"); + this.minSpawnDelay = minSpawnDelay; + } + + /** + * Gets the set of spawner entries that the spawner will use to spawn entities. + *

+ * If this is not empty, the spawner will use these entries to determine what entities to spawn. + * @return a set of spawner entries, or an empty set + */ + public @NotNull Set getSpawnerEntries() { + return Set.copyOf(spawnerEntries); + } + + /** + * Sets the list of spawner entries that the spawner will use to spawn entities. + *

+ * If this is not empty, the spawner will use these entries to determine what entities to spawn. + * @param spawnerEntries the list of spawner entries to set, or an empty list to clear + */ + public void setSpawnerEntries(@NotNull Set spawnerEntries) { + Preconditions.checkNotNull(spawnerEntries, "spawnerEntries cannot be null"); + this.spawnerEntries = new HashSet<>(spawnerEntries); + } + + /** + * Adds spawner entries to the list of spawner entries. + * @param spawnerEntries the spawner entry to add + */ + public void addSpawnerEntries(@NotNull Set spawnerEntries) { + Preconditions.checkNotNull(spawnerEntries, "spawnerEntry cannot be null"); + this.spawnerEntries.addAll(spawnerEntries); + } + + /** + * Adds a single spawner entry to the list of spawner entries. + * @param spawnerEntry the spawner entry to add + */ + public void addSpawnerEntry(@NotNull SkriptSpawnerEntry spawnerEntry) { + Preconditions.checkNotNull(spawnerEntry, "spawnerEntry cannot be null"); + this.spawnerEntries.add(spawnerEntry); + } + + /** + * Removes spawner entries from the list of spawner entries. + * @param spawnerEntries the spawner entry to remove + */ + public void removeSpawnerEntries(@NotNull Set spawnerEntries) { + Preconditions.checkNotNull(spawnerEntries, "spawnerEntry cannot be null"); + this.spawnerEntries.removeAll(spawnerEntries); + } + + /** + * Removes a single spawner entry from the list of spawner entries. + * @param spawnerEntry the spawner entry to remove + */ + public void removeSpawnerEntry(@NotNull SkriptSpawnerEntry spawnerEntry) { + Preconditions.checkNotNull(spawnerEntry, "spawnerEntry cannot be null"); + this.spawnerEntries.remove(spawnerEntry); + } + + /** + * Clears the list of spawner entries. + *

+ * This will remove all entries and set the spawner to not use any entries for spawning. + */ + public void clearSpawnerEntries() { + spawnerEntries.clear(); + } + + /* + * Serialization + */ + + protected Fields serialize() { + Fields fields = new Fields(); + fields.putPrimitive("activation_range", this.activationRange); + fields.putPrimitive("spawn_range", this.spawnRange); + fields.putObject("min_spawn_delay", this.minSpawnDelay); + fields.putObject("max_spawn_delay", this.maxSpawnDelay); + + int count = 0; + for (SkriptSpawnerEntry entry : this.spawnerEntries) { + fields.putObject("spawner_entry_" + count, entry); + count++; + } + + return fields; + } + + protected void deserialize(@NotNull Fields fields) throws StreamCorruptedException { + this.activationRange = fields.getPrimitive("activation_range", int.class); + this.spawnRange = fields.getPrimitive("spawn_range", int.class); + + this.minSpawnDelay = fields.getObject("min_spawn_delay", Timespan.class); + this.maxSpawnDelay = fields.getObject("max_spawn_delay", Timespan.class); + + int count = 0; + while (fields.contains("spawner_entry_" + count)) { + this.spawnerEntries.add(fields.getObject("spawner_entry_" + count, SkriptSpawnerEntry.class)); + count++; + } + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/spawners/util/spawnerdata/SkriptTrialSpawnerData.java b/src/main/java/org/skriptlang/skript/bukkit/spawners/util/spawnerdata/SkriptTrialSpawnerData.java new file mode 100644 index 00000000000..cc89cdf1b44 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/spawners/util/spawnerdata/SkriptTrialSpawnerData.java @@ -0,0 +1,356 @@ +package org.skriptlang.skript.bukkit.spawners.util.spawnerdata; + +import ch.njol.skript.util.Timespan; +import ch.njol.skript.util.Timespan.TimePeriod; +import ch.njol.yggdrasil.Fields; +import ch.njol.yggdrasil.YggdrasilSerializable.YggdrasilExtendedSerializable; +import com.google.common.base.Preconditions; +import org.bukkit.block.TrialSpawner; +import org.bukkit.loot.LootTable; +import org.jetbrains.annotations.NotNull; +import org.skriptlang.skript.bukkit.spawners.util.SpawnerUtils; + +import java.io.StreamCorruptedException; +import java.util.HashMap; +import java.util.Map; + +/** + * Represents the data of a {@link TrialSpawner} and its configuration. + * + * @see SkriptMobSpawnerData + * @see SkriptSpawnerData + */ +@SuppressWarnings("UnstableApiUsage") +public class SkriptTrialSpawnerData extends SkriptSpawnerData implements YggdrasilExtendedSerializable { + + private int activationRange = SpawnerUtils.DEFAULT_TRIAL_ACTIVATION_RANGE; + private int baseMobAmount = SpawnerUtils.DEFAULT_BASE_MOB_AMOUNT; + private int baseMobAmountIncrement = SpawnerUtils.DEFAULT_BASE_PER_PLAYER_INCREMENT; + private int concurrentMobAmount = SpawnerUtils.DEFAULT_CONCURRENT_MOB_AMOUNT; + private int concurrentMobAmountIncrement = SpawnerUtils.DEFAULT_CONCURRENT_PER_PLAYER_INCREMENT; + + private Timespan spawnDelay = SpawnerUtils.DEFAULT_TRIAL_SPAWN_DELAY; + private @NotNull Map rewardEntries = new HashMap<>(); + + /** + * Creates a new {@code SkriptTrialSpawnerData} instance. + */ + public SkriptTrialSpawnerData() {} + + /** + * Creates a new {@code SkriptTrialSpawnerData} instance from the given Bukkit {@link TrialSpawner}. + * @param trialSpawner the Bukkit trial spawner to convert + * @param ominous whether the trial spawner is ominous + * @return a new {@code SkriptTrialSpawnerData} instance containing the data from the Bukkit trial spawner + */ + public static SkriptTrialSpawnerData fromTrialSpawner(@NotNull TrialSpawner trialSpawner, boolean ominous) { + SkriptTrialSpawnerData data = new SkriptTrialSpawnerData(); + + var config = SpawnerUtils.getTrialSpawnerConfiguration(trialSpawner, ominous); + SkriptSpawnerData.applyToSpawnerData(config, data); + data.setMaxSpawnDelay(new Timespan(TimePeriod.TICK, config.getDelay())); + data.setRewardEntries(config.getPossibleRewards()); + + data.setBaseMobAmount((int) config.getBaseSpawnsBeforeCooldown()); + data.setBaseMobAmountIncrement((int) config.getAdditionalSpawnsBeforeCooldown()); + data.setConcurrentMobAmount((int) config.getBaseSimultaneousEntities()); + data.setConcurrentMobAmountIncrement((int) config.getAdditionalSimultaneousEntities()); + + return data; + } + + /** + * Applies this SkriptTrialSpawnerData to the given Bukkit trial spawner. + * @param trialSpawner the Bukkit trial spawner to apply the data to + */ + public void applyData(@NotNull TrialSpawner trialSpawner, boolean ominous) { + Preconditions.checkNotNull(trialSpawner, "trialSpawner cannot be null"); + + var config = SpawnerUtils.getTrialSpawnerConfiguration(trialSpawner, ominous); + super.applyToSpawner(config); + + config.setPossibleRewards(rewardEntries); + + config.setBaseSpawnsBeforeCooldown(getBaseMobAmount()); + config.setAdditionalSpawnsBeforeCooldown(getBaseMobAmountIncrement()); + config.setBaseSimultaneousEntities(getConcurrentMobAmount()); + config.setAdditionalSimultaneousEntities(getConcurrentMobAmountIncrement()); + config.setDelay((int) Math.max(spawnDelay.getAs(TimePeriod.TICK), Integer.MAX_VALUE)); + + trialSpawner.update(true, false); + } + + @Override + public int getActivationRange() { + return activationRange; + } + + @Override + public void setActivationRange(int activationRange) { + this.activationRange = activationRange; + } + + /** + * {@inheritDoc} + *
+ *
+ * For trial spawners, the minimum and maximum spawn delays are always identical. This results in a fixed delay, + * rather than a random range. + *

+ * The default value for trial spawners is 2 seconds (40 ticks). + */ + @Override + public @NotNull Timespan getMaxSpawnDelay() { + return spawnDelay; + } + + /** + * {@inheritDoc} + *
+ *
+ * For trial spawners, the minimum and maximum spawn delays are always identical. This results in a fixed delay, + * rather than a random range. + *

+ * The default value for trial spawners is 2 seconds (40 ticks). + */ + @Override + public void setMaxSpawnDelay(@NotNull Timespan maxSpawnDelay) { + this.spawnDelay = maxSpawnDelay; + } + + /** + * {@inheritDoc} + *
+ *
+ * For trial spawners, the minimum and maximum spawn delays are always identical. This results in a fixed delay, + * rather than a random range. + *

+ * The default value for trial spawners is 2 seconds (40 ticks). + */ + @Override + public @NotNull Timespan getMinSpawnDelay() { + return spawnDelay; + } + + /** + * {@inheritDoc} + *
+ *
+ * For trial spawners, the minimum and maximum spawn delays are always identical. This results in a fixed delay, + * rather than a random range. + *

+ * The default value for trial spawners is 2 seconds (40 ticks). + */ + @Override + public void setMinSpawnDelay(@NotNull Timespan minSpawnDelay) { + this.spawnDelay = minSpawnDelay; + } + + /** + * Returns the reward entries for this trial spawner data. + * @return a map of loot tables and their corresponding weights + */ + public @NotNull Map getRewardEntries() { + return Map.copyOf(rewardEntries); + } + + /** + * Returns the weight of the specified loot table in this trial spawner data. + * @param lootTable the loot table to get the weight for + * @return the weight of the loot table, or null if it does not exist in the map + */ + public Integer getRewardWeight(@NotNull LootTable lootTable) { + Preconditions.checkNotNull(lootTable, "lootTable cannot be null"); + return rewardEntries.get(lootTable); + } + + /** + * Sets the reward entries for this trial spawner data. + * @param rewardEntries a map of loot tables and their corresponding weights + */ + public void setRewardEntries(@NotNull Map rewardEntries) { + Preconditions.checkNotNull(rewardEntries, "rewardEntries cannot be null"); + this.rewardEntries = new HashMap<>(rewardEntries); + } + + /** + * Adds a reward entry to the map of reward entries. + * @param lootTable the loot table + * @param weight the weight of the loot table + */ + public void setRewardEntry(@NotNull LootTable lootTable, int weight) { + Preconditions.checkNotNull(lootTable, "lootTable cannot be null"); + this.rewardEntries.put(lootTable, weight); + } + + /** + * Removes a reward entry from the map of reward entries. + * @param lootTable the loot table to remove + */ + public void removeRewardEntry(@NotNull LootTable lootTable) { + Preconditions.checkNotNull(lootTable, "lootTable cannot be null"); + this.rewardEntries.remove(lootTable); + } + + /** + * Clears all reward entries from this trial spawner data. + */ + public void clearRewardEntries() { + this.rewardEntries.clear(); + } + + /** + * Returns the total number of mobs this spawner will spawn for a single player before going into cooldown. + *

+ * The formula for calculating the total mob amount, taking into account multiple players, is: + *


+	 * totalMobAmount = baseMobAmount + (baseMobAmountIncrement * (numberOfPlayers - 1))
+	 * 
+ * where {@code numberOfPlayers} is the number of players within range of the spawner. + *

+ * The default base mob amount is {@code 6}. + * + * @return the base mob amount + * @see #getBaseMobAmountIncrement() + */ + public int getBaseMobAmount() { + return baseMobAmount; + } + + /** + * Sets the base number of mobs this spawner will spawn for a single player before going into cooldown. + * @param mobAmount the base mob amount to set + * @see #getBaseMobAmount() + * @see #getBaseMobAmountIncrement() + */ + public void setBaseMobAmount(int mobAmount) { + baseMobAmount = mobAmount; + } + + /** + * Returns how many mobs this spawner will add to the total mob spawn amount for each additional player. + *

+ * The formula for calculating the total mob amount, taking into account multiple players, is: + *


+	 * totalMobAmount = baseMobAmount + (baseMobAmountIncrement * (numberOfPlayers - 1))
+	 * 
+ * where {@code numberOfPlayers} is the number of players within range of the spawner. + *

+ * The default value is {@code 2}. + * + * @return the number of additional mobs spawned per extra player + */ + public int getBaseMobAmountIncrement() { + return baseMobAmountIncrement; + } + + /** + * Sets how many mobs this spawner will add to the total mob spawn amount for each additional player. + * @param incrementPerPlayer the number of additional mobs spawned per extra player + * @see #getBaseMobAmount() + * @see #getBaseMobAmountIncrement() + */ + public void setBaseMobAmountIncrement(int incrementPerPlayer) { + baseMobAmountIncrement = incrementPerPlayer; + } + + /** + * Returns the maximum amount of mobs this spawner allows to exist concurrently for a single player. + *

+ * The formula for calculating the total concurrent mob amount, taking into account multiple players, is: + *


+	 *     totalConcurrentMobAmount = concurrentMobAmount + (concurrentMobAmountIncrement * (numberOfPlayers - 1))
+	 * 
+ * where {@code numberOfPlayers} is the number of players within range of the spawner. + *

+ * The default value is {@code 6}. + * @return the maximum amount of mobs that can exist concurrently for a single player + * @see #getConcurrentMobAmountIncrement() + */ + public int getConcurrentMobAmount() { + return concurrentMobAmount; + } + + /** + * Sets the maximum amount of mobs this spawner allows to exist concurrently for a single player. + * @param mobAmount the maximum amount of mobs that can exist concurrently for a single player + * @see #getConcurrentMobAmount() + * @see #getConcurrentMobAmountIncrement() + */ + public void setConcurrentMobAmount(int mobAmount) { + concurrentMobAmount = mobAmount; + } + + /** + * Returns how many mobs this spawner will add to the concurrent mob spawn amount for each additional player. + *

+ * The formula for calculating the total concurrent mob amount, taking into account multiple players, is: + *


+	 *     totalConcurrentMobAmount = concurrentMobAmount + (concurrentMobAmountIncrement * (numberOfPlayers - 1))
+	 * 
+ * where {@code numberOfPlayers} is the number of players within range of the spawner. + *

+ * The default value is {@code 2}. + * @return the number of additional mobs spawned concurrently per extra player + * @see #getConcurrentMobAmount() + */ + public int getConcurrentMobAmountIncrement() { + return concurrentMobAmountIncrement; + } + + /** + * Sets how many mobs this spawner will add to the concurrent mob spawn amount for each additional player. + * @param incrementPerPlayer the number of additional mobs spawned concurrently per extra player + * @see #getConcurrentMobAmount() + * @see #getBaseMobAmountIncrement() + */ + public void setConcurrentMobAmountIncrement(int incrementPerPlayer) { + concurrentMobAmountIncrement = incrementPerPlayer; + } + + /* + * YggdrasilExtendedSerializable + */ + + @Override + public Fields serialize() { + Fields fields = super.serialize(); + + fields.putPrimitive("activation_range", this.activationRange); + fields.putPrimitive("base_mob_amount", this.baseMobAmount); + fields.putPrimitive("base_mob_amount_increment", this.baseMobAmountIncrement); + fields.putPrimitive("concurrent_mob_amount", this.concurrentMobAmount); + fields.putPrimitive("concurrent_mob_amount_increment", this.concurrentMobAmountIncrement); + fields.putObject("spawn_delay", this.spawnDelay); + + int count = 0; + for (var entrySet : this.rewardEntries.entrySet()) { + fields.putObject("loot_table_" + count, entrySet.getKey()); + fields.putPrimitive("loot_table_weight_" + count, entrySet.getValue()); + count++; + } + + return fields; + } + + @Override + public void deserialize(@NotNull Fields fields) throws StreamCorruptedException { + super.deserialize(fields); + + this.activationRange = fields.getPrimitive("activation_range", int.class); + this.baseMobAmount = fields.getPrimitive("base_mob_amount", int.class); + this.baseMobAmountIncrement = fields.getPrimitive("base_mob_amount_increment", int.class); + this.concurrentMobAmount = fields.getPrimitive("concurrent_mob_amount", int.class); + this.concurrentMobAmountIncrement = fields.getPrimitive("concurrent_mob_amount_increment", int.class); + this.spawnDelay = fields.getObject("spawn_delay", Timespan.class); + + int count = 0; + while (fields.contains("loot_table_" + count)) { + LootTable lootTable = fields.getObject("loot_table_" + count, LootTable.class); + Integer weight = fields.getPrimitive("loot_table_weight_" + count, int.class); + this.rewardEntries.put(lootTable, weight); + count++; + } + } + +} \ No newline at end of file diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index d050ec1fa9a..234808b5562 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -2826,6 +2826,12 @@ types: numbered: numbered thing¦s @a valued: valued thing¦s @a containing: container¦s @a + weighted: weighted thing¦s @a + spawnerdata: spawner data¦s @a + mobspawnerdata: mob spawner data¦s @a + trialspawnerdata: trial spawner data¦s @a + spawnrule: spawn rule¦s @a + spawnerentry: spawner entr¦y¦ies @a # Hooks money: money