diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index 8bd3a557a35..7de7de2d9db 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -98,6 +98,7 @@ import org.skriptlang.skript.bukkit.displays.DisplayModule; import org.skriptlang.skript.bukkit.fishing.FishingModule; import org.skriptlang.skript.bukkit.input.InputModule; +import org.skriptlang.skript.bukkit.loottables.LootTableModule; import org.skriptlang.skript.lang.comparator.Comparator; import org.skriptlang.skript.lang.comparator.Comparators; import org.skriptlang.skript.lang.converter.Converter; @@ -549,6 +550,7 @@ public void onEnable() { BreedingModule.load(); DisplayModule.load(); InputModule.load(); + LootTableModule.load(); } catch (final Exception e) { exception(e, "Could not load required .class files: " + e.getLocalizedMessage()); setEnabled(false); diff --git a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java index f9a2accca7d..7bf52b41c74 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java @@ -1629,24 +1629,6 @@ public ItemType get(PlayerStopUsingItemEvent event) { }, EventValues.TIME_NOW); } - // LootGenerateEvent - if (Skript.classExists("org.bukkit.event.world.LootGenerateEvent")) { - EventValues.registerEventValue(LootGenerateEvent.class, Entity.class, new Getter() { - @Override - @Nullable - public Entity get(LootGenerateEvent event) { - return event.getEntity(); - } - }, EventValues.TIME_NOW); - EventValues.registerEventValue(LootGenerateEvent.class, Location.class, new Getter() { - @Override - @Nullable - public Location get(LootGenerateEvent event) { - return event.getLootContext().getLocation(); - } - }, EventValues.TIME_NOW); - } - // EntityResurrectEvent EventValues.registerEventValue(EntityResurrectEvent.class, Slot.class, new Getter() { @Override diff --git a/src/main/java/ch/njol/skript/events/SimpleEvents.java b/src/main/java/ch/njol/skript/events/SimpleEvents.java index 03a8279e581..7ff81d23f55 100644 --- a/src/main/java/ch/njol/skript/events/SimpleEvents.java +++ b/src/main/java/ch/njol/skript/events/SimpleEvents.java @@ -602,21 +602,7 @@ public class SimpleEvents { "\t\tset chat format to \"<orange>[player]<light gray>: <white>[message]\"" ) .since("1.4.1"); - if (Skript.classExists("org.bukkit.event.world.LootGenerateEvent")) { - Skript.registerEvent("Loot Generate", SimpleEvent.class, LootGenerateEvent.class, "loot generat(e|ing)") - .description( - "Called when a loot table of an inventory is generated in the world.", - "For example, when opening a shipwreck chest." - ) - .examples( - "on loot generate:", - "\tchance of 10%", - "\tadd 64 diamonds to the loot", - "\tsend \"You hit the jackpot at %event-location%!\"" - ) - .since("2.7") - .requiredPlugins("MC 1.16+"); - } + if (Skript.classExists("io.papermc.paper.event.player.PlayerDeepSleepEvent")) { Skript.registerEvent("Player Deep Sleep", SimpleEvent.class, PlayerDeepSleepEvent.class, "[player] deep sleep[ing]") .description( diff --git a/src/main/java/org/skriptlang/skript/bukkit/loottables/LootContextCreateEvent.java b/src/main/java/org/skriptlang/skript/bukkit/loottables/LootContextCreateEvent.java new file mode 100644 index 00000000000..4f00c85ed74 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/loottables/LootContextCreateEvent.java @@ -0,0 +1,27 @@ +package org.skriptlang.skript.bukkit.loottables; + +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.skriptlang.skript.bukkit.loottables.elements.expressions.ExprSecCreateLootContext; + +/** + * The event used in the {@link ExprSecCreateLootContext} section. + */ +public class LootContextCreateEvent extends Event { + + private final LootContextWrapper contextWrapper; + + public LootContextCreateEvent(LootContextWrapper context) { + this.contextWrapper = context; + } + + public LootContextWrapper getContextWrapper() { + return contextWrapper; + } + + @Override + public HandlerList getHandlers() { + throw new UnsupportedOperationException(); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/loottables/LootContextWrapper.java b/src/main/java/org/skriptlang/skript/bukkit/loottables/LootContextWrapper.java new file mode 100644 index 00000000000..e2afc7ed2c3 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/loottables/LootContextWrapper.java @@ -0,0 +1,112 @@ +package org.skriptlang.skript.bukkit.loottables; + +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.loot.LootContext; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Wrapper for a LootContext.Builder to allow easier creation of LootContexts. + */ +public class LootContextWrapper { + + private @NotNull Location location; + private transient @Nullable LootContext cachedLootContext; + private @Nullable Player killer; + private @Nullable Entity entity; + private float luck; + + /** + * Creates a new LootContextWrapper at the given location. + * @param location the location of the LootContext. + */ + public LootContextWrapper(@NotNull Location location) { + this.location = location; + } + + /** + * Gets the LootContext from the wrapper. + * @return the LootContext. + */ + public LootContext getContext() { + if (cachedLootContext == null) + cachedLootContext = new LootContext.Builder(location) + .killer(killer) + .lootedEntity(entity) + .luck(luck) + .build(); + + return cachedLootContext; + } + + /** + * Sets the location of the LootContext. + * @param location the location. + */ + public void setLocation(@NotNull Location location) { + this.location = location; + cachedLootContext = null; + } + + /** + * Sets the killer of the LootContext. + * @param killer the killer. + */ + public void setKiller(@Nullable Player killer) { + this.killer = killer; + cachedLootContext = null; + } + + /** + * Sets the entity of the LootContext. + * @param entity the entity. + */ + public void setEntity(@Nullable Entity entity) { + this.entity = entity; + cachedLootContext = null; + } + + /** + * Sets the luck of the LootContext. + * @param luck the luck value. + */ + public void setLuck(float luck) { + this.luck = luck; + cachedLootContext = null; + } + + /** + * Gets the location of the LootContext. + * @return the location. + */ + public Location getLocation() { + return location; + } + + /** + * Gets the killer of the LootContext. + * @return the killer. + */ + public @Nullable Player getKiller() { + return killer; + } + + /** + * Gets the entity of the LootContext. + * @return the entity. + */ + public @Nullable Entity getEntity() { + return entity; + } + + /** + * Gets the luck of the LootContext. + * @return the luck value. + */ + public float getLuck() { + return luck; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/loottables/LootTableModule.java b/src/main/java/org/skriptlang/skript/bukkit/loottables/LootTableModule.java new file mode 100644 index 00000000000..504ab269889 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/loottables/LootTableModule.java @@ -0,0 +1,127 @@ +package org.skriptlang.skript.bukkit.loottables; + +import ch.njol.skript.Skript; +import ch.njol.skript.classes.ClassInfo; +import ch.njol.skript.classes.Parser; +import ch.njol.skript.expressions.base.EventValueExpression; +import ch.njol.skript.lang.ParseContext; +import ch.njol.skript.lang.util.SimpleEvent; +import ch.njol.skript.registrations.Classes; +import ch.njol.skript.registrations.EventValues; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.NamespacedKey; +import org.bukkit.entity.Entity; +import org.bukkit.event.world.LootGenerateEvent; +import org.bukkit.loot.LootContext; +import org.bukkit.loot.LootTable; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; + +public class LootTableModule { + + public static void load() throws IOException { + + // --- CLASSES --- // + + Classes.registerClass(new ClassInfo<>(LootTable.class, "loottable") + .user("loot ?tables?") + .name("Loot Table") + .description( + "Loot tables represent what items should be in naturally generated containers, " + + "what items should be dropped when killing a mob, or what items can be fished.", + "You can find more information about this in https://minecraft.wiki/w/Loot_table" + ) + .since("INSERT VERSION") + .parser(new Parser<>() { + @Override + public @Nullable LootTable parse(String key, ParseContext context) { + NamespacedKey namespacedKey = NamespacedKey.fromString(key); + if (namespacedKey == null) + return null; + return Bukkit.getLootTable(namespacedKey); + } + + @Override + public String toString(LootTable o, int flags) { + return "loot table \"" + o.getKey() + '\"'; + } + + @Override + public String toVariableNameString(LootTable o) { + return "loot table:" + o.getKey(); + } + }) + ); + + Classes.registerClass(new ClassInfo<>(LootContext.class, "lootcontext") + .user("loot ?contexts?") + .name("Loot Context") + .description( + "Represents additional information a loot table can use to modify its generated loot.", + "", + "Some loot tables will require some values (i.e. looter, location, looted entity) " + + "in a loot context when generating loot whereas others may not.", + "For example, the loot table of a simple dungeon chest will only require a location, " + + "whereas the loot table of a cow will require a looting player, looted entity, and location.", + "You can find more information about this in https://minecraft.wiki/w/Loot_context" + ) + .since("INSERT VERSION") + .defaultExpression(new EventValueExpression<>(LootContext.class)) + .parser(new Parser<>() { + @Override + public boolean canParse(ParseContext context) { + return false; + } + + @Override + public String toString(LootContext context, int flags) { + StringBuilder builder = new StringBuilder("loot context at ") + .append(Classes.toString(context.getLocation())); + + if (context.getLootedEntity() != null) + builder.append(" with entity ").append(Classes.toString(context.getLootedEntity())); + if (context.getKiller() != null) + builder.append(" with killer ").append(Classes.toString(context.getKiller())); + if (context.getLuck() != 0) + builder.append(" with luck ").append(context.getLuck()); + + return builder.toString(); + } + + @Override + public String toVariableNameString(LootContext context) { + return "loot context:" + context.hashCode(); + } + }) + ); + + Skript.getAddonInstance().loadClasses("org.skriptlang.skript.bukkit.loottables", "elements"); + + // --- SIMPLE EVENTS --- // + + Skript.registerEvent("Loot Generate", SimpleEvent.class, LootGenerateEvent.class, "loot generat(e|ing)") + .description( + "Called when a loot table of an inventory is generated in the world.", + "For example, when opening a shipwreck chest." + ) + .examples( + "on loot generate:", + "\tchance of 10%", + "\tadd 64 diamonds to the loot", + "\tsend \"You hit the jackpot at %event-location%!\"" + ) + .since("2.7") + .requiredPlugins("MC 1.16+"); + + // --- EVENT VALUES --- // + + // LootGenerateEvent + EventValues.registerEventValue(LootGenerateEvent.class, Entity.class, LootGenerateEvent::getEntity); + EventValues.registerEventValue(LootGenerateEvent.class, Location.class, event -> event.getLootContext().getLocation()); + EventValues.registerEventValue(LootGenerateEvent.class, LootTable.class, LootGenerateEvent::getLootTable); + EventValues.registerEventValue(LootGenerateEvent.class, LootContext.class, LootGenerateEvent::getLootContext); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/loottables/LootTableUtils.java b/src/main/java/org/skriptlang/skript/bukkit/loottables/LootTableUtils.java new file mode 100644 index 00000000000..de998f8c6c0 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/loottables/LootTableUtils.java @@ -0,0 +1,55 @@ +package org.skriptlang.skript.bukkit.loottables; + +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.loot.LootTable; +import org.bukkit.loot.Lootable; + +/** + * Utility class for loot tables. + */ +public class LootTableUtils { + + /** + * * Checks whether a block or entity is an instance of {@link Lootable}. This is done because a block is not an instance of Lootable, but a block state is. + * @param object the object to check. + * @return whether the object is lootable. + */ + public static boolean isLootable(Object object) { + if (object instanceof Block block) + object = block.getState(); + return object instanceof Lootable; + } + + /** + * Gets the Lootable instance of an object. You should call {@link #isLootable(Object)} before calling this method. + * @param object the object to get the Lootable instance of. + * @return the Lootable instance of the object. + */ + public static Lootable getAsLootable(Object object) { + if (object instanceof Block block) + object = block.getState(); + if (object instanceof Lootable lootable) + return lootable; + return null; + } + + /** + * Gets the loot table of an object. You should call {@link #isLootable(Object)} before calling this method. + * @param object the object to get the loot table of. + * @return returns the LootTable of the object. + */ + public static LootTable getLootTable(Object object) { + return getAsLootable(object).getLootTable(); + } + + /** + * Updates the state of a Lootable. This is done because setting the LootTable or seed of a BlockState changes the NBT value, but is never updated. + * @param lootable the Lootable to update the state of. + */ + public static void updateState(Lootable lootable) { + if (lootable instanceof BlockState blockState) + blockState.update(true, false); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/loottables/elements/conditions/CondHasLootTable.java b/src/main/java/org/skriptlang/skript/bukkit/loottables/elements/conditions/CondHasLootTable.java new file mode 100644 index 00000000000..b02336fca21 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/loottables/elements/conditions/CondHasLootTable.java @@ -0,0 +1,42 @@ +package org.skriptlang.skript.bukkit.loottables.elements.conditions; + +import ch.njol.skript.conditions.base.PropertyCondition; +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 org.skriptlang.skript.bukkit.loottables.LootTableUtils; + +@Name("Has Loot Table") +@Description( + "Checks whether an entity or block has a loot table. " + + "The loot tables of chests will be deleted when the chest is opened or broken." +) +@Examples({ + "set event-block to chest", + "if event-block has a loot table:", + "\t# this will never happen, because it doesn't have a loot table.", + "", + "set loot table of event-block to \"minecraft:chests/simple_dungeon\"", + "", + "if event-block has a loot table:", + "\t# this will happen, because it now has a loot table." +}) +@Since("INSERT VERSION") +public class CondHasLootTable extends PropertyCondition { + + static { + register(CondHasLootTable.class, PropertyType.HAVE, "[a] loot[ ]table", "blocks/entities"); + } + + @Override + public boolean check(Object object) { + return LootTableUtils.isLootable(object) && LootTableUtils.getLootTable(object) != null; + } + + @Override + protected String getPropertyName() { + return "a loot table"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/loottables/elements/conditions/CondIsLootable.java b/src/main/java/org/skriptlang/skript/bukkit/loottables/elements/conditions/CondIsLootable.java new file mode 100644 index 00000000000..9178fe16563 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/loottables/elements/conditions/CondIsLootable.java @@ -0,0 +1,48 @@ +package org.skriptlang.skript.bukkit.loottables.elements.conditions; + +import ch.njol.skript.conditions.base.PropertyCondition; +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 org.skriptlang.skript.bukkit.loottables.LootTableUtils; + +@Name("Is Lootable") +@Description( + "Checks whether an entity or block is lootable. " + + "Lootables are entities or blocks that can have a loot table." +) +@Examples({ + "spawn a pig at event-location", + "set {_pig} to last spawned entity", + "if {_pig} is lootable:", + "\tset loot table of {_pig} to \"minecraft:entities/cow\"", + "\t# the pig will now drop the loot of a cow when killed, because it is indeed a lootable entity.", + + "set block at event-location to chest", + "if block at event-location is lootable:", + "\tset loot table of block at event-location to \"minecraft:chests/simple_dungeon\"", + "\t# the chest will now generate the loot of a simple dungeon when opened, because it is indeed a lootable block.", + + "set block at event-location to wool block", + "if block at event-location is lootable:", + "\t# uh oh, nothing will happen because a wool is not a lootable block." +}) +@Since("INSERT VERSION") +public class CondIsLootable extends PropertyCondition { + + static { + register(CondIsLootable.class, "lootable", "blocks/entities"); + } + + @Override + public boolean check(Object object) { + return LootTableUtils.isLootable(object); + } + + @Override + protected String getPropertyName() { + return "lootable"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/loottables/elements/effects/EffGenerateLoot.java b/src/main/java/org/skriptlang/skript/bukkit/loottables/elements/effects/EffGenerateLoot.java new file mode 100644 index 00000000000..ceab5b0928d --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/loottables/elements/effects/EffGenerateLoot.java @@ -0,0 +1,93 @@ +package org.skriptlang.skript.bukkit.loottables.elements.effects; + +import ch.njol.skript.Skript; +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.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.Bukkit; +import org.bukkit.event.Event; +import org.bukkit.inventory.Inventory; +import org.bukkit.loot.LootContext; +import org.bukkit.loot.LootTable; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.loottables.LootContextWrapper; + +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; + +@Name("Generate Loot") +@Description({ + "Generates the loot in the specified inventories from a loot table using a loot context. " + + "Not specifying a loot context will use a loot context with a location at the world's origin.", + "Note that if the inventory is full, it will cause warnings in the console due to over-filling the inventory." +}) +@Examples({ + "generate loot of loot table \"minecraft:chests/simple_dungeon\" using loot context at player in {_inventory}", + "generate loot using \"minecraft:chests/shipwreck_supply\" in {_inventory}" +}) +@Since("INSERT VERSION") +public class EffGenerateLoot extends Effect { + + static { + Skript.registerEffect(EffGenerateLoot.class, + "generate [the] loot (of|using) %loottable% [(with|using) %-lootcontext%] in %inventories%" + ); + } + + private Expression lootTable; + private Expression context; + private Expression inventories; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + lootTable = (Expression) exprs[0]; + context = (Expression) exprs[1]; + inventories = (Expression) exprs[2]; + return true; + } + + @Override + protected void execute(Event event) { + Random random = ThreadLocalRandom.current(); + + LootContext context; + if (this.context != null) { + context = this.context.getSingle(event); + if (context == null) + return; + } else { + context = new LootContextWrapper(Bukkit.getWorlds().get(0).getSpawnLocation()).getContext(); + } + + LootTable table = lootTable.getSingle(event); + if (table == null) + return; + + for (Inventory inventory : inventories.getArray(event)) { + try { + // todo: perhaps runtime error in the future + table.fillInventory(inventory, random, context); + } catch (IllegalArgumentException ignore) {} + } + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + SyntaxStringBuilder builder = new SyntaxStringBuilder(event, debug); + + builder.append("generate loot using", lootTable); + if (context != null) + builder.append("with", context); + builder.append("in", inventories); + + return builder.toString(); + } + +} diff --git a/src/main/java/ch/njol/skript/expressions/ExprLoot.java b/src/main/java/org/skriptlang/skript/bukkit/loottables/elements/expressions/ExprLoot.java similarity index 62% rename from src/main/java/ch/njol/skript/expressions/ExprLoot.java rename to src/main/java/org/skriptlang/skript/bukkit/loottables/elements/expressions/ExprLoot.java index 9622e808a7e..fe01cd2c1d4 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprLoot.java +++ b/src/main/java/org/skriptlang/skript/bukkit/loottables/elements/expressions/ExprLoot.java @@ -1,4 +1,4 @@ -package ch.njol.skript.expressions; +package org.skriptlang.skript.bukkit.loottables.elements.expressions; import ch.njol.skript.Skript; import ch.njol.skript.classes.Changer.ChangeMode; @@ -25,17 +25,16 @@ @Description("The loot that will be generated in a 'loot generate' event.") @Examples({ "on loot generate:", - "\tchance of %10", - "\tadd 64 diamonds", - "\tsend \"You hit the jackpot!!\"" + "\tchance of %10", + "\tadd 64 diamonds to loot", + "\tsend \"You hit the jackpot!!\"" }) @Since("2.7") @RequiredPlugins("MC 1.16+") public class ExprLoot extends SimpleExpression { static { - if (Skript.classExists("org.bukkit.event.world.LootGenerateEvent")) - Skript.registerExpression(ExprLoot.class, ItemStack.class, ExpressionType.SIMPLE, "[the] loot"); + Skript.registerExpression(ExprLoot.class, ItemStack.class, ExpressionType.SIMPLE, "[the] loot"); } @Override @@ -49,31 +48,25 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye @Override @Nullable - protected ItemStack[] get(Event event) { - if (!(event instanceof LootGenerateEvent)) + protected ItemStack @Nullable [] get(Event event) { + if (!(event instanceof LootGenerateEvent lootEvent)) return new ItemStack[0]; - return ((LootGenerateEvent) event).getLoot().toArray(new ItemStack[0]); + return lootEvent.getLoot().toArray(new ItemStack[0]); } @Override @Nullable - public Class[] acceptChange(ChangeMode mode) { - switch (mode) { - case ADD: - case REMOVE: - case SET: - case DELETE: - return CollectionUtils.array(ItemStack[].class); - default: - return null; - } + public Class @Nullable [] acceptChange(ChangeMode mode) { + return switch (mode) { + case DELETE, ADD, REMOVE, SET -> CollectionUtils.array(ItemStack[].class); + default -> null; + }; } @Override - public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { - if (!(event instanceof LootGenerateEvent)) + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + if (!(event instanceof LootGenerateEvent lootEvent)) return; - LootGenerateEvent lootEvent = (LootGenerateEvent) event; List items = null; if (delta != null) { @@ -83,18 +76,10 @@ public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { } switch (mode) { - case ADD: - lootEvent.getLoot().addAll(items); - break; - case REMOVE: - lootEvent.getLoot().removeAll(items); - break; - case SET: - lootEvent.setLoot(items); - break; - case DELETE: - lootEvent.getLoot().clear(); - break; + case ADD -> lootEvent.getLoot().addAll(items); + case REMOVE -> lootEvent.getLoot().removeAll(items); + case SET -> lootEvent.setLoot(items); + case DELETE -> lootEvent.getLoot().clear(); } } diff --git a/src/main/java/org/skriptlang/skript/bukkit/loottables/elements/expressions/ExprLootContext.java b/src/main/java/org/skriptlang/skript/bukkit/loottables/elements/expressions/ExprLootContext.java new file mode 100644 index 00000000000..599fe0ea419 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/loottables/elements/expressions/ExprLootContext.java @@ -0,0 +1,32 @@ +package org.skriptlang.skript.bukkit.loottables.elements.expressions; + +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.loot.LootContext; + +@Name("Loot Context") +@Description("The loot context involved in the context create section.") +@Examples({ + "set {_context} to a new loot context at {_location}:", + "\tbroadcast loot context" +}) +@Since("INSERT VERSION") +public class ExprLootContext extends EventValueExpression { + + static { + register(ExprLootContext.class, LootContext.class, "loot[ ]context"); + } + + public ExprLootContext() { + super(LootContext.class); + } + + @Override + public String toString() { + return "the loot context"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/loottables/elements/expressions/ExprLootContextEntity.java b/src/main/java/org/skriptlang/skript/bukkit/loottables/elements/expressions/ExprLootContextEntity.java new file mode 100644 index 00000000000..45b16ccb8cc --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/loottables/elements/expressions/ExprLootContextEntity.java @@ -0,0 +1,71 @@ +package org.skriptlang.skript.bukkit.loottables.elements.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.classes.Changer.ChangeMode; +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.SimplePropertyExpression; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.entity.Entity; +import org.bukkit.event.Event; +import org.bukkit.loot.LootContext; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.loottables.LootContextCreateEvent; + +@Name("Looted Entity of Loot Context") +@Description("Returns the looted entity of a loot context.") +@Examples({ + "set {_entity} to looted entity of {_context}", + "", + "set {_context} to a loot context at player:", + "\tset loot luck value to 10", + "\tset looter to player", + "\tset looted entity to last spawned pig" +}) +@Since("INSERT VERSION") +public class ExprLootContextEntity extends SimplePropertyExpression { + + static { + registerDefault(ExprLootContextEntity.class, Entity.class, "looted entity", "lootcontexts"); + } + + @Override + public @Nullable Entity convert(LootContext context) { + return context.getLootedEntity(); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + if (!getParser().isCurrentEvent(LootContextCreateEvent.class)) { + Skript.error("You cannot set the looted entity of an existing loot context."); + return null; + } + + return switch (mode) { + case SET, DELETE, RESET -> CollectionUtils.array(Entity.class); + default -> null; + }; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + if (!(event instanceof LootContextCreateEvent createEvent)) + return; + + Entity entity = delta != null ? (Entity) delta[0] : null; + createEvent.getContextWrapper().setEntity(entity); + } + + @Override + public Class getReturnType() { + return Entity.class; + } + + @Override + protected String getPropertyName() { + return "looted entity"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/loottables/elements/expressions/ExprLootContextLocation.java b/src/main/java/org/skriptlang/skript/bukkit/loottables/elements/expressions/ExprLootContextLocation.java new file mode 100644 index 00000000000..c6b9f344266 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/loottables/elements/expressions/ExprLootContextLocation.java @@ -0,0 +1,69 @@ +package org.skriptlang.skript.bukkit.loottables.elements.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.classes.Changer.ChangeMode; +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.SimplePropertyExpression; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.Location; +import org.bukkit.event.Event; +import org.bukkit.loot.LootContext; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.loottables.LootContextCreateEvent; + +@Name("Loot Location of Loot Context") +@Description("Returns the loot location of a loot context.") +@Examples({ + "set {_player} to player", + "set {_context} to a loot context at player:", + "\tif {_player} is in \"world_nether\":", + "\t\tset loot location to location of last spawned pig", + "send loot location of {_context} to player" +}) +@Since("INSERT VERSION") +public class ExprLootContextLocation extends SimplePropertyExpression { + + static { + registerDefault(ExprLootContextLocation.class, Location.class, "loot[ing] [context] location", "lootcontexts"); + } + + @Override + public Location convert(LootContext context) { + return context.getLocation(); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + if (!getParser().isCurrentEvent(LootContextCreateEvent.class)) { + Skript.error("You cannot set the loot context location of an existing loot context."); + return null; + } + + if (mode == ChangeMode.SET) + return CollectionUtils.array(Location.class); + return null; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + if (!(event instanceof LootContextCreateEvent createEvent)) + return; + + assert delta != null; + createEvent.getContextWrapper().setLocation((Location) delta[0]); + } + + @Override + public Class getReturnType() { + return Location.class; + } + + @Override + protected String getPropertyName() { + return "loot location"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/loottables/elements/expressions/ExprLootContextLooter.java b/src/main/java/org/skriptlang/skript/bukkit/loottables/elements/expressions/ExprLootContextLooter.java new file mode 100644 index 00000000000..81ec1ac8fc9 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/loottables/elements/expressions/ExprLootContextLooter.java @@ -0,0 +1,76 @@ +package org.skriptlang.skript.bukkit.loottables.elements.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.classes.Changer.ChangeMode; +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.SimplePropertyExpression; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.loot.LootContext; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.loottables.LootContextCreateEvent; + +@Name("Looter of Loot Context") +@Description( + "Returns the looter of a loot context. " + + "Note that setting the looter will read the looter's tool enchantments (e.g. looting) when generating loot." +) +@Examples({ + "set {_killer} to looter of {_context}", + "", + "set {_context} to a loot context at player:", + "\tset loot luck value to 10", + "\tset looter to player", + "\tset looted entity to last spawned pig" +}) +@Since("INSERT VERSION") +public class ExprLootContextLooter extends SimplePropertyExpression { + + static { + registerDefault(ExprLootContextLooter.class, Player.class, "(looter|looting player)", "lootcontexts"); + } + + @Override + public @Nullable Player convert(LootContext context) { + if (context.getKiller() instanceof Player player) + return player; + return null; + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + if (!getParser().isCurrentEvent(LootContextCreateEvent.class)) { + Skript.error("You cannot set the looting player of an existing loot context."); + return null; + } + + return switch (mode) { + case SET, DELETE, RESET -> CollectionUtils.array(Player.class); + default -> null; + }; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + if (!(event instanceof LootContextCreateEvent createEvent)) + return; + + Player player = delta != null ? (Player) delta[0] : null; + createEvent.getContextWrapper().setKiller(player); + } + + @Override + public Class getReturnType() { + return Player.class; + } + + @Override + protected String getPropertyName() { + return "looting player"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/loottables/elements/expressions/ExprLootContextLuck.java b/src/main/java/org/skriptlang/skript/bukkit/loottables/elements/expressions/ExprLootContextLuck.java new file mode 100644 index 00000000000..b9120d66381 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/loottables/elements/expressions/ExprLootContextLuck.java @@ -0,0 +1,77 @@ +package org.skriptlang.skript.bukkit.loottables.elements.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.classes.Changer.ChangeMode; +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.SimplePropertyExpression; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.event.Event; +import org.bukkit.loot.LootContext; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.loottables.LootContextCreateEvent; +import org.skriptlang.skript.bukkit.loottables.LootContextWrapper; + +@Name("Luck of Loot Context") +@Description("Returns the luck of a loot context as a float. This represents the luck potion effect that an entity can have.") +@Examples({ + "set {_luck} to loot luck value of {_context}", + "", + "set {_context} to a loot context at player:", + "\tset loot luck value to 10", + "\tset looter to player", + "\tset looted entity to last spawned pig" +}) +@Since("INSERT VERSION") +public class ExprLootContextLuck extends SimplePropertyExpression { + + static { + registerDefault(ExprLootContextLuck.class, Float.class, "loot[ing] [context] luck [value|factor]", "lootcontexts"); + } + + @Override + public @Nullable Float convert(LootContext context) { + return context.getLuck(); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + if (!getParser().isCurrentEvent(LootContextCreateEvent.class)) { + Skript.error("You cannot set the loot context luck of an existing loot context."); + return null; + } + + return switch (mode) { + case SET, DELETE, RESET, ADD, REMOVE -> CollectionUtils.array(Float.class); + default -> null; + }; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + if (!(event instanceof LootContextCreateEvent createEvent)) + return; + + LootContextWrapper wrapper = createEvent.getContextWrapper(); + float luck = delta != null ? (float) delta[0] : 0f; + + switch (mode) { + case SET, DELETE, RESET -> wrapper.setLuck(luck); + case ADD -> wrapper.setLuck(wrapper.getLuck() + luck); + case REMOVE -> wrapper.setLuck(wrapper.getLuck() - luck); + } + } + + @Override + public Class getReturnType() { + return Float.class; + } + + @Override + protected String getPropertyName() { + return "loot luck factor"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/loottables/elements/expressions/ExprLootItems.java b/src/main/java/org/skriptlang/skript/bukkit/loottables/elements/expressions/ExprLootItems.java new file mode 100644 index 00000000000..b07a5c07691 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/loottables/elements/expressions/ExprLootItems.java @@ -0,0 +1,105 @@ +package org.skriptlang.skript.bukkit.loottables.elements.expressions; + +import ch.njol.skript.Skript; +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.lang.Expression; +import ch.njol.skript.lang.ExpressionType; +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 org.bukkit.Bukkit; +import org.bukkit.event.Event; +import org.bukkit.inventory.ItemStack; +import org.bukkit.loot.LootContext; +import org.bukkit.loot.LootTable; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.loottables.LootContextWrapper; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; + +@Name("Loot of Loot Table") +@Description( + "Returns the items of a loot table using a loot context. " + + "Not specifying a loot context will use a loot context with a location at the world's origin." +) +@Examples({ + "set {_items::*} to loot items of the loot table \"minecraft:chests/simple_dungeon\" with loot context {_context}", + "# this will set {_items::*} to the items that would be dropped from the simple dungeon loot table with the given loot context", + "", + "give player loot items of entity's loot table with loot context {_context}", + "# this will give the player the items that the entity would drop with the given loot context" +}) +@Since("INSERT VERSION") +public class ExprLootItems extends SimpleExpression { + + static { + Skript.registerExpression(ExprLootItems.class, ItemStack.class, ExpressionType.COMBINED, + "[the] loot of %loottables% [(with|using) %-lootcontext%]", + "%loottables%'[s] loot [(with|using) %-lootcontext%]" + ); + } + + private Expression lootTables; + private Expression context; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + lootTables = (Expression) exprs[0]; + context = (Expression) exprs[1]; + return true; + } + + @Override + protected ItemStack @Nullable [] get(Event event) { + LootContext context; + if (this.context != null) { + context = this.context.getSingle(event); + if (context == null) + return new ItemStack[0]; + } else { + context = new LootContextWrapper(Bukkit.getWorlds().get(0).getSpawnLocation()).getContext(); + } + + List items = new ArrayList<>(); + + Random random = ThreadLocalRandom.current(); + for (LootTable lootTable : lootTables.getArray(event)) { + try { + // todo: perhaps runtime error in the future + items.addAll(lootTable.populateLoot(random, context)); + } catch (IllegalArgumentException ignore) {} + } + + return items.toArray(new ItemStack[0]); + } + + @Override + public boolean isSingle() { + return false; + } + + @Override + public Class getReturnType() { + return ItemStack.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + SyntaxStringBuilder builder = new SyntaxStringBuilder(event, debug); + + builder.append("the loot of", lootTables); + if (context != null) + builder.append("with", context); + + return builder.toString(); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/loottables/elements/expressions/ExprLootTable.java b/src/main/java/org/skriptlang/skript/bukkit/loottables/elements/expressions/ExprLootTable.java new file mode 100644 index 00000000000..fe745a0feda --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/loottables/elements/expressions/ExprLootTable.java @@ -0,0 +1,77 @@ +package org.skriptlang.skript.bukkit.loottables.elements.expressions; + +import ch.njol.skript.classes.Changer.ChangeMode; +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.SimplePropertyExpression; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.event.Event; +import org.bukkit.loot.LootTable; +import org.bukkit.loot.Lootable; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.loottables.LootTableUtils; + +@Name("Loot Table") +@Description({ + "Returns the loot table of an entity or block.", + "Setting the loot table of a block will update the block state, and once opened will " + + "generate loot of the specified loot table. Please note that doing so may cause " + + "warnings in the console due to over-filling the chest.", + "Please note that resetting/deleting the loot table of an ENTITY will reset the entity's loot table to its default.", +}) +@Examples({ + "set loot table of event-entity to \"minecraft:entities/ghast\"", + "# this will set the loot table of the entity to a ghast's loot table, thus dropping ghast tears and gunpowder", + "", + "set loot table of event-block to \"minecraft:chests/simple_dungeon\"", +}) +@Since("INSERT VERSION") +public class ExprLootTable extends SimplePropertyExpression { + + static { + register(ExprLootTable.class, LootTable.class, "loot[ ]table[s]", "entities/blocks"); + } + + @Override + public @Nullable LootTable convert(Object object) { + if (LootTableUtils.isLootable(object)) + return LootTableUtils.getLootTable(object); + return null; + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + return switch (mode) { + case SET, DELETE, RESET -> 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 (Object object : getExpr().getArray(event)) { + if (!LootTableUtils.isLootable(object)) + continue; + + Lootable lootable = LootTableUtils.getAsLootable(object); + + lootable.setLootTable(lootTable); + LootTableUtils.updateState(lootable); + } + } + + @Override + public Class getReturnType() { + return LootTable.class; + } + + @Override + protected String getPropertyName() { + return "loot table"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/loottables/elements/expressions/ExprLootTableFromString.java b/src/main/java/org/skriptlang/skript/bukkit/loottables/elements/expressions/ExprLootTableFromString.java new file mode 100644 index 00000000000..d0e0781497c --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/loottables/elements/expressions/ExprLootTableFromString.java @@ -0,0 +1,74 @@ +package org.skriptlang.skript.bukkit.loottables.elements.expressions; + +import ch.njol.skript.Skript; +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.lang.Expression; +import ch.njol.skript.lang.ExpressionType; +import ch.njol.skript.lang.SkriptParser; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.util.Kleenean; +import org.bukkit.Bukkit; +import org.bukkit.NamespacedKey; +import org.bukkit.event.Event; +import org.bukkit.loot.LootTable; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +@Name("Loot Table from Key") +@Description("Returns the loot table from a namespaced key.") +@Examples("set {_table} to loot table \"minecraft:chests/simple_dungeon\"") +@Since("INSERT VERSION") +public class ExprLootTableFromString extends SimpleExpression { + + static { + Skript.registerExpression(ExprLootTableFromString.class, LootTable.class, ExpressionType.COMBINED, + "[the] loot[ ]table[s] %strings%" + ); + } + + private Expression keys; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, SkriptParser.ParseResult parseResult) { + //noinspection unchecked + keys = (Expression) exprs[0]; + return true; + } + + @Override + protected LootTable @Nullable [] get(Event event) { + List lootTables = new ArrayList<>(); + for (String key : keys.getArray(event)) { + NamespacedKey namespacedKey = NamespacedKey.fromString(key); + if (namespacedKey == null) + continue; + + LootTable lootTable = Bukkit.getLootTable(namespacedKey); + if (lootTable != null) + lootTables.add(lootTable); + } + + return lootTables.toArray(new LootTable[0]); + } + + @Override + public boolean isSingle() { + return keys.isSingle(); + } + + @Override + public Class getReturnType() { + return LootTable.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "the loot table of " + keys.toString(event, debug); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/loottables/elements/expressions/ExprLootTableSeed.java b/src/main/java/org/skriptlang/skript/bukkit/loottables/elements/expressions/ExprLootTableSeed.java new file mode 100644 index 00000000000..e73d2f72cb8 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/loottables/elements/expressions/ExprLootTableSeed.java @@ -0,0 +1,65 @@ +package org.skriptlang.skript.bukkit.loottables.elements.expressions; + +import ch.njol.skript.classes.Changer.ChangeMode; +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.SimplePropertyExpression; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.event.Event; +import org.bukkit.loot.Lootable; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.loottables.LootTableUtils; + +@Name("Seed of Loot Table") +@Description("Returns the seed of a loot table. Setting the seed of a block or entity that does not have a loot table will not do anything.") +@Examples({ + "set {_seed} loot table seed of block", + "set loot table seed of entity to 123456789" +}) +@Since("INSERT VERSION") +public class ExprLootTableSeed extends SimplePropertyExpression { + + static { + register(ExprLootTableSeed.class, Long.class, "loot[[ ]table] seed[s]", "entities/blocks"); + } + + @Override + public @Nullable Long convert(Object object) { + Lootable lootable = LootTableUtils.getAsLootable(object); + return lootable != null ? lootable.getSeed() : null; + } + + public Class @Nullable [] acceptChange(ChangeMode mode) { + if (mode == ChangeMode.SET) + return CollectionUtils.array(Number.class); + return null; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + assert delta != null; + long seedValue = ((Number) delta[0]).longValue(); + + for (Object object : getExpr().getArray(event)) { + if (!LootTableUtils.isLootable(object)) + continue; + + Lootable lootable = LootTableUtils.getAsLootable(object); + lootable.setSeed(seedValue); + LootTableUtils.updateState(lootable); + } + } + + @Override + public Class getReturnType() { + return Long.class; + } + + @Override + protected String getPropertyName() { + return "loot table seed"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/loottables/elements/expressions/ExprSecCreateLootContext.java b/src/main/java/org/skriptlang/skript/bukkit/loottables/elements/expressions/ExprSecCreateLootContext.java new file mode 100644 index 00000000000..2dfea4abc8f --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/loottables/elements/expressions/ExprSecCreateLootContext.java @@ -0,0 +1,96 @@ +package org.skriptlang.skript.bukkit.loottables.elements.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.config.SectionNode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.SectionExpression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.*; +import ch.njol.skript.registrations.EventValues; +import ch.njol.skript.util.Direction; +import ch.njol.skript.variables.Variables; +import ch.njol.util.Kleenean; +import org.bukkit.Location; +import org.bukkit.event.Event; +import org.bukkit.loot.LootContext; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.loottables.LootContextCreateEvent; +import org.skriptlang.skript.bukkit.loottables.LootContextWrapper; + +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +@Name("Create Loot Context") +@Description("Create a loot context.") +@Examples({ + "set {_player} to player", + "set {_context} to a loot context at player:", + "\tset loot luck value to 10", + "\tset looter to {_player}", + "\tset looted entity to last spawned pig", + "give player loot items of loot table \"minecraft:entities/iron_golem\" with loot context {_context}" +}) +@Since("INSERT VERSION") +public class ExprSecCreateLootContext extends SectionExpression { + + static { + Skript.registerExpression(ExprSecCreateLootContext.class, LootContext.class, ExpressionType.COMBINED, + "[a] loot context %direction% %location%"); + EventValues.registerEventValue(LootContextCreateEvent.class, LootContext.class, event -> event.getContextWrapper().getContext()); + } + + private Trigger trigger; + private Expression location; + + @Override + public boolean init(Expression[] exprs, int pattern, Kleenean isDelayed, ParseResult result, @Nullable SectionNode node, @Nullable List triggerItems) { + if (node != null) { + AtomicBoolean delayed = new AtomicBoolean(false); + Runnable afterLoading = () -> delayed.set(!getParser().getHasDelayBefore().isFalse()); + //noinspection unchecked + trigger = loadCode(node, "create loot context", afterLoading, LootContextCreateEvent.class); + if (delayed.get()) { + Skript.error("Delays cannot be used within a 'create loot context' section."); + return false; + } + } + //noinspection unchecked + location = Direction.combine((Expression) exprs[0], (Expression) exprs[1]); + return true; + } + + @Override + protected LootContext @Nullable [] get(Event event) { + Location loc = location.getSingle(event); + if (loc == null) + return new LootContext[0]; + + LootContextWrapper wrapper = new LootContextWrapper(loc); + if (trigger != null) { + LootContextCreateEvent contextEvent = new LootContextCreateEvent(wrapper); + Variables.withLocalVariables(event, contextEvent, () -> + TriggerItem.walk(trigger, contextEvent) + ); + } + return new LootContext[]{wrapper.getContext()}; + } + + @Override + public boolean isSingle() { + return true; + } + + @Override + public Class getReturnType() { + return LootContext.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "a loot context " + location.toString(event, debug); + } + +} diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index 638683d821f..7ce573c244a 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -2543,6 +2543,8 @@ types: experiencecooldownchangereason: experience cooldown change reason¦s @a inputkey: input key¦s @an entitysnapshot: entity snapshot¦s @an + loottable: loot table¦s @a + lootcontext: loot context¦s @a # Skript weathertype: weather type¦s @a diff --git a/src/test/java/org/skriptlang/skript/test/tests/syntaxes/expressions/ExprLootContextLooterTest.java b/src/test/java/org/skriptlang/skript/test/tests/syntaxes/expressions/ExprLootContextLooterTest.java new file mode 100644 index 00000000000..431e307d3b8 --- /dev/null +++ b/src/test/java/org/skriptlang/skript/test/tests/syntaxes/expressions/ExprLootContextLooterTest.java @@ -0,0 +1,27 @@ +package org.skriptlang.skript.test.tests.syntaxes.expressions; + +import ch.njol.skript.test.runner.SkriptJUnitTest; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.player.PlayerJoinEvent; +import org.easymock.EasyMock; +import org.junit.Test; + +public class ExprLootContextLooterTest extends SkriptJUnitTest { + + static { + setShutdownDelay(1); + } + + @Test + public void test() { + Player player = EasyMock.niceMock(Player.class); + Location location = new Location(Bukkit.getWorld("world"), 0, 0, 0); + + EasyMock.expect(player.getLocation()).andReturn(location); + EasyMock.replay(player); + + Bukkit.getPluginManager().callEvent(new PlayerJoinEvent(player, "ok")); + } +} diff --git a/src/test/skript/junit/ExprLootContextLooter.sk b/src/test/skript/junit/ExprLootContextLooter.sk new file mode 100644 index 00000000000..31364456b06 --- /dev/null +++ b/src/test/skript/junit/ExprLootContextLooter.sk @@ -0,0 +1,22 @@ +options: + test: "org.skriptlang.skript.test.tests.syntaxes.expressions.ExprLootContextLooterTest" + +test "ExprLootContextKillerJUnit" when running JUnit: + set {_tests::1} to "set killer" + set {_tests::2} to "clear killer" + ensure junit test {@test} completes {_tests::*} + +on join: + junit test is {@test} + + set {_player} to player + set {_context} to a loot context at player's location: + set looter to {_player} + + if looter is {_player}: + complete objective "set killer" for {@test} + + reset looter + + if looter is not set: + complete objective "clear killer" for {@test} diff --git a/src/test/skript/tests/syntaxes/conditions/CondHasLootTable.sk b/src/test/skript/tests/syntaxes/conditions/CondHasLootTable.sk new file mode 100644 index 00000000000..b16cef8ef84 --- /dev/null +++ b/src/test/skript/tests/syntaxes/conditions/CondHasLootTable.sk @@ -0,0 +1,30 @@ +test "loot table condition": + spawn a pig at test-location + set {_pig} to last spawned pig + + assert {_pig} has loot table with "default has loot table failed" + + set loot table of {_pig} to loot table "minecraft:entities/cow" + assert {_pig} has loot table with "simple has loot table failed" + + set loot table of {_pig} to "bleh blah bluh" parsed as loot table + assert {_pig} has loot table with "pig doesn't have loot table after setting invalid loot table" + + delete entity within {_pig} + + set {_pastBlock} to block data of test-block + + set test-block to chest + + assert test-block doesn't have loot table with "has loot table without setting it" + + set loot table of test-block to loot table "minecraft:chests/simple_dungeon" + assert test-block has loot table with "simple has loot table failed" + + clear loot table of test-block + assert test-block doesn't have a loot table with "block still has loot table after clearing loot table" + + set loot table of test-block to "bleh blah bluh" parsed as loot table + assert test-block doesn't have a loot table with "block still has loot table after setting invalid loot table" + + set test-block to {_pastBlock} diff --git a/src/test/skript/tests/syntaxes/conditions/CondIsLootable.sk b/src/test/skript/tests/syntaxes/conditions/CondIsLootable.sk new file mode 100644 index 00000000000..2fe22856d58 --- /dev/null +++ b/src/test/skript/tests/syntaxes/conditions/CondIsLootable.sk @@ -0,0 +1,17 @@ +test "is lootable": + spawn a pig at test-location + set {_pig} to last spawned pig + + assert {_pig} is lootable with "is lootable entity failed" + + delete entity within {_pig} + + set {_pastBlock} to block data of test-block + + set test-block to wool block + assert test-block is not lootable with "is not lootable failed" + + set test-block to chest + assert test-block is lootable with "is lootable chest failed" + + set test-block to {_pastBlock} diff --git a/src/test/skript/tests/syntaxes/expressions/ExprLootContext.sk b/src/test/skript/tests/syntaxes/expressions/ExprLootContext.sk new file mode 100644 index 00000000000..e71e7828484 --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprLootContext.sk @@ -0,0 +1,43 @@ +test "loot context": + + set {_context} to a loot context at test-location + assert loot location of {_context} is test-location with "loot context location failed" + + spawn a pig at test-location + set {_pig} to last spawned pig + + set {_context} to a loot context at test-location: + assert loot context is set with "loot context not set" + set looted entity to {_pig} + set loot luck value to 10 + set loot location to location(1,1,1, world "world") + assert loot location is location(1,1,1, world "world") with "loot context location set failed" + + assert looted entity of {_context} is {_pig} with "loot context entity failed" + assert loot luck value of {_context} is 10 with "simple loot context luck value failed" + + delete entity within {_pig} + + set {_context} to a loot context at test-location: + set loot luck value to -1 + assert the loot luck of {_context} is -1 with "negative loot luck set failed" + + set {_context} to a loot context at test-location: + set loot luck value to 2 + assert the loot luck of {_context} is 2 with "existing loot luck set failed" + + set {_context} to a loot context at test-location: + set loot luck value to 3.3 + assert the loot luck of {_context} is 3.3 with "decimal loot luck set failed" + + set {_context} to a loot context at test-location: + set loot luck value to 3.999 + assert the loot luck of {_context} is 3.999 with "close decimal loot luck set failed" + + set {_context} to a loot context at test-location: + set loot luck value to -3.3 + assert the loot luck of {_context} is -3.3 with "negative decimal loot luck set failed" + + set {_context} to a loot context at test-location: + set loot luck value to -3.999 + assert the loot luck of {_context} is -3.999 with "negative close decimal loot luck set failed" diff --git a/src/test/skript/tests/syntaxes/expressions/ExprLootItems.sk b/src/test/skript/tests/syntaxes/expressions/ExprLootItems.sk new file mode 100644 index 00000000000..886f467c266 --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprLootItems.sk @@ -0,0 +1,12 @@ +test "loot items": + + set {_context} to a loot context at test-location + set {_items::*} to loot of loot table "minecraft:chests/simple_dungeon" with {_context} + + assert {_items::*} is set with "loot items not set" + + set {_items::*} to loot of loot table "minecraft:entities/pig" with {_context} + assert {_items::*} is not set with "loot items set" + + set {_items::*} to loot of loot table "minecraft:chests/simple_dungeon" + assert {_items::*} is set with "loot items with chest loot table without context not set" diff --git a/src/test/skript/tests/syntaxes/expressions/ExprLootTable.sk b/src/test/skript/tests/syntaxes/expressions/ExprLootTable.sk new file mode 100644 index 00000000000..d1d3c9a01fd --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprLootTable.sk @@ -0,0 +1,23 @@ +test "loot table": + + spawn a pig at test-location + set {_pig} to last spawned pig + + assert loot table of {_pig} is loot table "minecraft:entities/pig" with "entity loot table is not 'minecraft:entities/pig'" + + delete entity within {_pig} + + set {_pastBlock} to block data of test-block + + set test-block to chest + + set loot table of test-block to loot table "minecraft:chests/simple_dungeon" + assert loot table of test-block is loot table "minecraft:chests/simple_dungeon" with "block loot table is not 'minecraft:chests/simple_dungeon'" + + set loot table of test-block to loot table "minecraft:entities/pig" + assert loot table of test-block is loot table "minecraft:entities/pig" with "block loot table is not 'minecraft:entities/pig'" + + set loot table of test-block to "invalidloottable" parsed as loot table + assert loot table of test-block is not set with "block loot table is set" + + set test-block to {_pastBlock} diff --git a/src/test/skript/tests/syntaxes/expressions/ExprLootTableSeed.sk b/src/test/skript/tests/syntaxes/expressions/ExprLootTableSeed.sk new file mode 100644 index 00000000000..639c7adf792 --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprLootTableSeed.sk @@ -0,0 +1,48 @@ +test "loot table seed": + + spawn a pig at test-location + set {_pig} to last spawned pig + + assert the loot seed of {_pig} is 0 with "default entity loot seed failed" + set loot seed of {_pig} to 5 + assert the loot seed of {_pig} is 5 with "simple entity loot seed set failed" + set loot seed of {_pig} to -1 + assert the loot seed of {_pig} is -1 with "negative entity loot seed set failed" + set loot seed of {_pig} to 2 + assert the loot seed of {_pig} is 2 with "existing entity loot seed set failed" + set loot seed of {_pig} to 3.3 + assert the loot seed of {_pig} is 3 with "decimal entity loot seed set failed" + set loot seed of {_pig} to 3.999 + assert the loot seed of {_pig} is 3 with "close entity decimal loot seed set failed" + set loot seed of {_pig} to NaN value + assert the loot seed of {_pig} is 0 with "NaN value entity loot seed set failed" + set loot seed of {_pig} to infinity value + assert the loot seed of {_pig} is 9223372036854775807 with "infinity value entity loot seed set failed" + + delete entity within {_pig} + + set {_pastBlock} to block data of test-block + + set test-block to chest + + assert the loot seed of test-block is 0 with "default block loot seed before setting loot table failed" + + set loot table of test-block to loot table "minecraft:chests/simple_dungeon" + + assert the loot seed of test-block is 0 with "default block loot after setting loot table seed failed" + set loot seed of test-block to 5 + assert the loot seed of test-block is 5 with "simple block loot seed set failed" + set loot seed of test-block to -1 + assert the loot seed of test-block is -1 with "negative block loot seed set failed" + set loot seed of test-block to 2 + assert the loot seed of test-block is 2 with "existing block loot seed set failed" + set loot seed of test-block to 3.3 + assert the loot seed of test-block is 3 with "decimal block loot seed set failed" + set loot seed of test-block to 3.999 + assert the loot seed of test-block is 3 with "close block decimal loot seed set failed" + set loot seed of test-block to NaN value + assert the loot seed of test-block is 0 with "NaN value block loot seed set failed" + set loot seed of test-block to infinity value + assert the loot seed of test-block is 9223372036854775807 with "infinity value block loot seed set failed" + + set test-block to {_pastBlock}