diff --git a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java index ba0a66a1f41..c8142acb5fd 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java @@ -38,6 +38,7 @@ import org.bukkit.GameRule; import org.bukkit.Location; import org.bukkit.Material; +import org.bukkit.NamespacedKey; import org.bukkit.OfflinePlayer; import org.bukkit.Registry; import org.bukkit.SoundCategory; @@ -49,6 +50,8 @@ import org.bukkit.block.BlockState; import org.bukkit.block.DoubleChest; import org.bukkit.block.data.BlockData; +import org.bukkit.block.structure.Mirror; +import org.bukkit.block.structure.StructureRotation; import org.bukkit.command.CommandSender; import org.bukkit.enchantments.Enchantment; import org.bukkit.enchantments.EnchantmentOffer; @@ -77,6 +80,7 @@ import org.bukkit.metadata.Metadatable; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; +import org.bukkit.structure.Structure; import org.bukkit.util.CachedServerIcon; import org.bukkit.util.Vector; @@ -102,6 +106,7 @@ import ch.njol.skript.util.BlockUtils; import ch.njol.skript.util.PotionEffectUtils; import ch.njol.skript.util.StringMode; +import ch.njol.skript.util.Utils; import ch.njol.util.StringUtils; import ch.njol.yggdrasil.Fields; import io.papermc.paper.world.MoonPhase; @@ -1526,6 +1531,60 @@ public String toVariableNameString(EnchantmentOffer eo) { .name("Transform Reason") .description("Represents a transform reason of an entity transform event.") .since("2.8.0")); + + if (Skript.classExists("org.bukkit.structure.Structure")) { + Classes.registerClass(new ClassInfo<>(Structure.class, "structure") + .user("structures?") + .name("Structure") + .description("Represents a structure in a namespace.") + .defaultExpression(new EventValueExpression<>(Structure.class)) + .requiredPlugins("Minecraft 1.17.1") + .since("INSERT VERSION") + .parser(new Parser() { + @Override + @Nullable + public Structure parse(String input, ParseContext context) { + NamespacedKey key = Utils.getNamespacedKey(input); + if (key == null) + return null; + return Bukkit.getStructureManager().loadStructure(key, false); + } + + @Override + public boolean canParse(ParseContext context) { + return context != ParseContext.CONFIG; + } + + @Override + public String toString(Structure structure, int flags) { + return Bukkit.getStructureManager().getStructures().entrySet().stream() + .filter(entry -> entry.getValue().equals(structure)) + .map(entry -> "structure " + entry.getKey().asString()) + .findFirst() + .orElse("structure " + structure.toString()); + } + + @Override + public String toVariableNameString(Structure structure) { + return toString(structure, 0); + } + })); + + Classes.registerClass(new EnumClassInfo<>(StructureRotation.class, "structurerotation", "structure rotations") + .user("structure ?rotations?") + .name("Structure Rotations") + .description("Represents a rotation a structure can be rotated as when placed.") + .requiredPlugins("Minecraft 1.17.1") + .since("INSERT VERSION")); + + Classes.registerClass(new EnumClassInfo<>(Mirror.class, "mirror", "mirrors") + .user("mirrors?") + .name("mirror") + .description("Represents a mirror setting a structure can be when placed.") + .requiredPlugins("Minecraft 1.17.1") + .since("INSERT VERSION")); + } + } } diff --git a/src/main/java/ch/njol/skript/conditions/CondStructureExists.java b/src/main/java/ch/njol/skript/conditions/CondStructureExists.java new file mode 100644 index 00000000000..5cef42823d8 --- /dev/null +++ b/src/main/java/ch/njol/skript/conditions/CondStructureExists.java @@ -0,0 +1,80 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see . + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.conditions; + +import org.bukkit.Bukkit; +import org.bukkit.NamespacedKey; +import org.bukkit.event.Event; +import org.bukkit.structure.StructureManager; +import org.jetbrains.annotations.Nullable; + +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.RequiredPlugins; +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.util.Utils; +import ch.njol.util.Kleenean; + +@Name("Structure Exists") +@Description("Check if structures exist.") +@Examples("if structure named \"Example\" does exist") +@RequiredPlugins("Minecraft 1.17.1+") +@Since("INSERT VERSION") +public class CondStructureExists extends Condition { + + static { + if (Skript.classExists("org.bukkit.structure.Structure")) + Skript.registerCondition(CondStructureExists.class, + "structure[s] [named] %strings% [do[es]] exist[s]", + "structure[s] [named] %strings% (do[es]n't|do[es] not) exist" + ); + } + + private Expression names; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + names = (Expression) exprs[0]; + setNegated(matchedPattern == 1); + return true; + } + + @Override + public boolean check(Event event) { + StructureManager manager = Bukkit.getStructureManager(); + return names.check(event, name -> { + NamespacedKey key = Utils.getNamespacedKey(name); + if (key == null) + return false; + return manager.loadStructure(key, false) != null; + }, isNegated()); + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "structures " + names.toString(event, debug) + (names.isSingle() ? " does " : " do ") + (isNegated() ? "not " : "") + " exist"; + } + +} diff --git a/src/main/java/ch/njol/skript/effects/EffStructureSaveUnregister.java b/src/main/java/ch/njol/skript/effects/EffStructureSaveUnregister.java new file mode 100644 index 00000000000..70c271b3e8c --- /dev/null +++ b/src/main/java/ch/njol/skript/effects/EffStructureSaveUnregister.java @@ -0,0 +1,103 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see . + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.effects; + +import java.io.IOException; + +import org.bukkit.Bukkit; +import org.bukkit.NamespacedKey; +import org.bukkit.event.Event; +import org.bukkit.structure.Structure; +import org.bukkit.structure.StructureManager; +import org.jetbrains.annotations.Nullable; + +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.RequiredPlugins; +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.util.Utils; +import ch.njol.util.Kleenean; + +@Name("Structure Save/Unregister") +@Description("Unregisters or saves a structure by it's namespace key. Unregister will unload if the structure was loaded vs Delete.") +@Examples("unregister structure named \"Example\"") +@RequiredPlugins("Spigot 1.17.1+") +@Since("INSERT VERSION") +public class EffStructureSaveUnregister extends Effect { + + static { + if (Skript.classExists("org.bukkit.structure.Structure")) + Skript.registerEffect(EffStructureSaveUnregister.class, "(:unregister|delete) structure[s] [with name|named] %strings%", "save [structure] %structure% [with name|named] %string%"); + } + + private Expression structure; + private Expression names; + private boolean save, unregister; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + names = (Expression) exprs[matchedPattern]; + unregister = parseResult.hasTag("unregister"); + if (save = matchedPattern == 1) + structure = (Expression) exprs[0]; + return true; + } + + @Override + protected void execute(Event event) { + StructureManager manager = Bukkit.getStructureManager(); + if (save) { + String name = this.names.getSingle(event); + if (name == null) + return; + Structure structure = this.structure.getSingle(event); + if (structure == null) + return; + try { + manager.saveStructure(Utils.getNamespacedKey(name), structure); + } catch (IOException e) { + Skript.exception(e, "Failed to save structure " + name); + } + } else { + for (String name : names.getArray(event)) { + NamespacedKey key = Utils.getNamespacedKey(name); + if (key == null) + continue; + try { + manager.deleteStructure(key, unregister); + } catch (IOException e) { + Skript.exception(e, "Failed to delete structure " + name); + } + } + + } + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return (save ? "save" : "delete") + " structures " + names.toString(event, debug); + } + +} diff --git a/src/main/java/ch/njol/skript/expressions/ExprStructure.java b/src/main/java/ch/njol/skript/expressions/ExprStructure.java new file mode 100644 index 00000000000..dd50569111b --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprStructure.java @@ -0,0 +1,139 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see . + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.event.Event; +import org.bukkit.structure.Structure; +import org.bukkit.structure.StructureManager; +import org.bukkit.util.BlockVector; +import org.jetbrains.annotations.Nullable; + +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.RequiredPlugins; +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.util.SimpleExpression; +import ch.njol.skript.util.BlockUtils; +import ch.njol.skript.util.Utils; +import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; + +@Name("Structure Between/Name") +@Description({ + "A structure is a utility that allows you to save a cuboid of blocks and entities.", + "This syntax will return an existing structure from memory/datapacks or you can also create a structure between two locations.", + "The register tag adds the structure to the 'all structures' expression.", + "If the name contains a collon, it'll grab from the Minecraft structure space (Data packs included for namespaces).", +}) +@Examples({ + "set {_structure} to a new structure between {location1} and {location2} named \"Example\"", + "set {_structure} to structure \"Example\"", + "set {_structure} to structure \"minecraft:end_city\"" +}) +@RequiredPlugins("Minecraft 1.17.1+") +@Since("INSERT VERSION") +public class ExprStructure extends SimpleExpression { + + static { + if (Skript.classExists("org.bukkit.structure.Structure")) + Skript.registerExpression(ExprStructure.class, Structure.class, ExpressionType.COMBINED, + "structure[s] [named] %strings% [register:and don't register]", + "[a] [new] structure between %location% (and|to) %location% [(including|with) entities:entities]" + ); + } + + @Nullable + private Expression location1, location2; + + @Nullable + private Expression names; + private boolean entities, register = true; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (matchedPattern == 0) { + names = (Expression) exprs[0]; + return true; + } + entities = parseResult.hasTag("entities"); + register = !parseResult.hasTag("register"); + location1 = (Expression) exprs[0]; + location2 = (Expression) exprs[1]; + return true; + } + + @Override + protected Structure[] get(Event event) { + StructureManager manager = Bukkit.getStructureManager(); + + // Returning existing structures. + if (names != null) { + return names.stream(event) + .map(name -> Utils.getNamespacedKey(name)) + .map(name -> name != null ? manager.loadStructure(name, register) : null) + .toArray(Structure[]::new); + } + Location location1 = this.location1.getSingle(event); + Location location2 = this.location2.getSingle(event); + if (location1 == null || location2 == null) + return new Structure[0]; + + World world1 = location1.getWorld(); + World world2 = location2.getWorld(); + if (world1 != world2) + return new Structure[0]; + + Location lowest = BlockUtils.getLowestBlockLocation(location1, location2); + Location highest = BlockUtils.getHighestBlockLocation(location1, location2); + int x = (highest.getBlockX() + 1) - lowest.getBlockX(); + int y = (highest.getBlockY() + 1) - lowest.getBlockY(); + int z = (highest.getBlockZ() + 1) - lowest.getBlockZ(); + + Structure structure = manager.createStructure(); + structure.fill(lowest, new BlockVector(x, y, z), entities); + return CollectionUtils.array(structure); + } + + @Override + public boolean isSingle() { + return names == null || names.isSingle(); + } + + @Override + public Class getReturnType() { + return Structure.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + if (location1 == null || location2 == null) + return "structures " + names.toString(event, debug); + return "structure from " + location1.toString(event, debug) + " to " + location2.toString(event, debug); + } + +} diff --git a/src/main/java/ch/njol/skript/expressions/ExprStructureInfo.java b/src/main/java/ch/njol/skript/expressions/ExprStructureInfo.java new file mode 100644 index 00000000000..1931badf44b --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprStructureInfo.java @@ -0,0 +1,156 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see . + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; + +import org.bukkit.Bukkit; +import org.bukkit.NamespacedKey; +import org.bukkit.block.BlockState; +import org.bukkit.entity.Entity; +import org.bukkit.event.Event; +import org.bukkit.structure.Structure; +import org.bukkit.util.BlockVector; +import org.jetbrains.annotations.Nullable; + +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.RequiredPlugins; +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.util.BlockStateBlock; +import ch.njol.util.Kleenean; +import ch.njol.util.coll.iterator.ArrayIterator; + +@Name("Structure Info") +@Description("An expression to obtain information about a structure's entities, size, and blocks.") +@Examples({ + "loop all entities of structure {_structure}:", + "\tif loop-entity is a diamond:", + "\t\tmessage \"Race to the diamond at %loop-entity's location%\" to {_players::*}", + "\t\tstop", + "if the length of {_structure}'s vector is greater than 100:", + "\tmessage \"&a+50 coins will be granted for winning on this larger map!\"" +}) +@RequiredPlugins("Minecraft 1.17.1+") +@Since("INSERT VERSION") +public class ExprStructureInfo extends PropertyExpression { + + static { + if (Skript.classExists("org.bukkit.structure.Structure")) + register(ExprStructureInfo.class, Object.class, "[structure] (:blocks|:entities|name:name[s]|size:[vector] size[s])", "structures"); + } + + private enum Property { + BLOCKS(BlockStateBlock.class), + SIZE(BlockVector.class), + ENTITIES(Entity.class), + NAME(String.class); + + private final Class returnType; + + Property(Class returnType) { + this.returnType = returnType; + } + + public Class getReturnType() { + return returnType; + } + } + + @Nullable + private Expression name; + private Property property; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + property = Property.valueOf(parseResult.tags.get(0).toUpperCase(Locale.ENGLISH)); + setExpr((Expression) exprs[0]); + return true; + } + + @Override + protected Object[] get(Event event, Structure[] source) { + switch (property) { + case BLOCKS: + return Arrays.stream(source).flatMap(structure -> { + if (structure.getPaletteCount() > 0) + return structure.getPalettes().get(0).getBlocks().stream() + .map(state -> new BlockStateBlock(state, true)); + return null; + }) + .filter(Objects::nonNull) + .toArray(BlockStateBlock[]::new); + case ENTITIES: + return get(source, structure -> structure.getEntities().toArray(Entity[]::new)); + case SIZE: + return get(source, Structure::getSize); + case NAME: + Map structures = Bukkit.getStructureManager().getStructures(); + return get(source, structure -> structures.entrySet().stream() + .filter(entry -> entry.getValue().equals(structure)) + .map(entry -> "structure " + entry.getKey().asString()) + .findFirst() + .orElse("structure " + structure.toString())); + } + return null; + } + + @Override + @Nullable + public Iterator iterator(Event event) { + if (property != Property.BLOCKS) + return new ArrayIterator(get(event, getExpr().getArray(event))); + List blocks = new ArrayList<>(); + for (Structure structure : getExpr().getArray(event)) { + if (structure.getPaletteCount() > 0) { + for (BlockState state : structure.getPalettes().get(0).getBlocks()) + blocks.add(new BlockStateBlock(state, true)); + } + } + return blocks.iterator(); + } + + @Override + public boolean isSingle() { + return (property == Property.SIZE || property == Property.NAME) && getExpr().isSingle(); + } + + @Override + public Class getReturnType() { + return property.getReturnType(); + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return property.name().toLowerCase(Locale.ENGLISH) + " of " + getExpr().toString(event, debug); + } + +} diff --git a/src/main/java/ch/njol/skript/expressions/ExprStructureSectionInfo.java b/src/main/java/ch/njol/skript/expressions/ExprStructureSectionInfo.java new file mode 100644 index 00000000000..8dd9603f4f5 --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprStructureSectionInfo.java @@ -0,0 +1,180 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see . + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import java.util.Locale; +import java.util.function.BiConsumer; + +import org.bukkit.block.structure.Mirror; +import org.bukkit.block.structure.StructureRotation; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; + +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.RequiredPlugins; +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.util.SimpleExpression; +import ch.njol.skript.sections.EffSecStructurePlace.StructurePlaceEvent; +import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; + +@Name("Structure Place Settings") +@Description({ + "Returns or modifies the settings for placement of a structure.", + "- includes entities will determine if the enitites saved should spawn when placing.", + "- rotation will allow placement of the structure based on the rotation at the location point.", + "- integrity determines how damaged the building should look by randomly skipping blocks to place. " + + "This value can range from decimal 0 to 1. With 0 removing all blocks and 1 spawning the structure in pristine condition.", + "- pallet index is what iteration of the structure to use, starting at 0, or -1 to pick a random palette. Useful for Minecraft structures." + + "If you registered your own structure through Skript, you can use 0 as it won't have multiple pallets, " + + "this is also the default value and this setting is not required in the section.", + "- mirror the mirror setting for the structure on placement.", + "All setting syntaxes are optional and this doesn't even need to be a section, can be an effect. " + + "Default settings are rotation = none, pallet = 0, mirror = none, entities = true, integrity = 1", + "Note that some Minecraft structures have conditions that Mojang has defined, such as an ancient city will spawn near bedrock level." +}) +@Examples({ + "place structure \"Example\" at {_location}", + "place structure \"minecraft:ancient_city\" at {_location} without entities # Places near bedrock level", + "place structure \"minecraft:end_city\" at player's location:", + "\tset pallet to -1 # Will pick a random variation", + "place structure \"minecraft:end_city\" at player's location:", + "\tset includes entities to false", + "\tset integrity to 0.9", + "\tset pallet to 2", + "\tset rotation to clockwise 90", + "\tset mirror to left to right" +}) +@RequiredPlugins("Minecraft 1.17.1+") +@Since("INSERT VERSION") +public class ExprStructureSectionInfo extends SimpleExpression { + + static { + if (Skript.classExists("org.bukkit.structure.Structure")) { + Skript.registerExpression(ExprStructureSectionInfo.class, Object.class, ExpressionType.SIMPLE, + "includes entities", "rotation", "integrity", "pallet [index]", "mirror" + ); + } + } + + private enum Setting { + + INCLUDES_ENTITIES(Boolean.class, StructurePlaceEvent::setIncludesEntities), + ROTATION(StructureRotation.class, StructurePlaceEvent::setRotation), + INTEGRITY(Float.class, (details, number) -> { + float integrity = ((Number) number).floatValue(); + details.setIntegrity(Math.min(1, Math.max(0, integrity))); + }), + PALLET(Integer.class, (details, number) -> { + int pallet = ((Number) number).intValue(); + int max = details.getStructure().getPaletteCount(); + details.setPallet(Math.min(max, Math.max(-1, pallet))); + }), + MIRROR(Mirror.class, StructurePlaceEvent::setMirror); + + private final BiConsumer consumer; + private final Class returnType; + + @SuppressWarnings("unchecked") + Setting(Class returnType, BiConsumer consumer) { + this.returnType = returnType; + this.consumer = (BiConsumer) consumer; + } + + public void accept(StructurePlaceEvent event, Object object) { + consumer.accept(event, object); + } + + public Class getReturnType() { + return returnType; + } + + } + + private Setting setting; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (!getParser().isCurrentEvent(StructurePlaceEvent.class)) { + Skript.error(parseResult.expr + " can only be used in a structure place section!"); + return false; + } + setting = Setting.values()[matchedPattern]; + return true; + } + + @Override + protected Object[] get(Event event) { + if (!(event instanceof StructurePlaceEvent)) + return new Object[0]; + StructurePlaceEvent details = (StructurePlaceEvent) event; + switch (setting) { + case INCLUDES_ENTITIES: + return CollectionUtils.array(details.includeEntities()); + case INTEGRITY: + return CollectionUtils.array(details.includeEntities()); + case PALLET: + return CollectionUtils.array(details.includeEntities()); + case MIRROR: + return CollectionUtils.array(details.getMirror()); + case ROTATION: + return CollectionUtils.array(details.getRotation()); + } + return new Object[0]; + } + + public Class[] acceptChange(ChangeMode mode) { + if (mode != ChangeMode.SET) + return null; + if (setting.getReturnType().isAssignableFrom(Number.class)) + return CollectionUtils.array(Number.class); + return CollectionUtils.array(setting.getReturnType()); + } + + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { + if (delta == null) + return; + if (!(event instanceof StructurePlaceEvent)) + return; + setting.accept((StructurePlaceEvent) event, delta[0]); + } + + @Override + public boolean isSingle() { + return true; + } + + @Override + public Class getReturnType() { + return setting.getReturnType(); + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "structure place setting " + setting.name().toLowerCase(Locale.ENGLISH).replace("_", " "); + } + +} diff --git a/src/main/java/ch/njol/skript/expressions/ExprStructures.java b/src/main/java/ch/njol/skript/expressions/ExprStructures.java new file mode 100644 index 00000000000..14f1b824964 --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprStructures.java @@ -0,0 +1,75 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see . + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import org.bukkit.Bukkit; +import org.bukkit.event.Event; +import org.bukkit.structure.Structure; +import org.jetbrains.annotations.Nullable; + +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.RequiredPlugins; +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.util.SimpleExpression; +import ch.njol.util.Kleenean; + +@Name("Structures") +@Description("Returns all of the structures registered.") +@Examples("set {_structures::*} to all of the structures") +@RequiredPlugins("Minecraft 1.17.1+") +@Since("INSERT VERSION") +public class ExprStructures extends SimpleExpression { + + static { + if (Skript.classExists("org.bukkit.structure.Structure")) + Skript.registerExpression(ExprStructures.class, Structure.class, ExpressionType.SIMPLE, "[(all [[of] the]|the)] [registered] structures"); + } + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + return true; + } + + @Override + protected Structure[] get(Event event) { + return Bukkit.getStructureManager().getStructures().values().toArray(new Structure[0]); + } + + @Override + public boolean isSingle() { + return false; + } + + @Override + public Class getReturnType() { + return Structure.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "all of the structures"; + } + +} diff --git a/src/main/java/ch/njol/skript/sections/EffSecStructurePlace.java b/src/main/java/ch/njol/skript/sections/EffSecStructurePlace.java new file mode 100644 index 00000000000..bf87c3eb5c4 --- /dev/null +++ b/src/main/java/ch/njol/skript/sections/EffSecStructurePlace.java @@ -0,0 +1,191 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see . + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.sections; + +import java.util.List; +import java.util.Random; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.bukkit.Location; +import org.bukkit.block.structure.Mirror; +import org.bukkit.block.structure.StructureRotation; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.bukkit.structure.Structure; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; + +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.lang.EffectSection; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.Trigger; +import ch.njol.skript.lang.TriggerItem; +import ch.njol.skript.variables.Variables; +import ch.njol.util.Kleenean; + +@Name("Structure Place") +@Description({ + "Places a structure. This can be used as an effect and as a section.", + "If it is used as a section, the section is run before the structure is placed in the world.", + "You can modify the place settings like if it should include entities and any rotation/mirror option you want.", + "For more info on the section settings see Structure Place Settings" +}) +@Examples({ + "place structure \"minecraft:end_city\" at player's location without entities:", + "\tset integrity to 0.9", + "\tset pallet to -1 # -1 for random pallet", + "\tset pallet to -1 # random pallet", + "\tset rotation to counter clockwise 90", + "\tset mirror to none # already the default not required" +}) +@Since("INSERT VERSION") +public class EffSecStructurePlace extends EffectSection { + + public class StructurePlaceEvent extends Event { + + private StructureRotation rotation = StructureRotation.NONE; + private Mirror mirror = Mirror.NONE; + private final Structure structure; + private float integrity = 1F; + private boolean entities; + private int pallet = 0; + + public StructurePlaceEvent(Structure structure, boolean entities) { + this.structure = structure; + this.entities = entities; + } + + public Structure getStructure() { + return structure; + } + + public boolean includeEntities() { + return entities; + } + + public void setIncludesEntities(boolean entities) { + this.entities = entities; + } + + public StructureRotation getRotation() { + return rotation; + } + + public void setRotation(StructureRotation rotation) { + this.rotation = rotation; + } + + public Mirror getMirror() { + return mirror; + } + + public void setMirror(Mirror mirror) { + this.mirror = mirror; + } + + public int getPallet() { + return pallet; + } + + public void setPallet(int pallet) { + this.pallet = pallet; + } + + public float getIntegrity() { + return integrity; + } + + public void setIntegrity(float integrity) { + this.integrity = integrity; + } + + @Override + @NotNull + public HandlerList getHandlers() { + throw new IllegalStateException(); + } + } + + static { + if (Skript.classExists("org.bukkit.structure.Structure")) + Skript.registerSection(EffSecStructurePlace.class, "place %structure% at %locations% [without :entities]"); + } + + private Expression structure; + private Expression locations; + private boolean entities; + + @Nullable + private Trigger trigger; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult, @Nullable SectionNode sectionNode, @Nullable List triggerItems) { + structure = (Expression) exprs[0]; + locations = (Expression) exprs[1]; + entities = !parseResult.hasTag("entities"); // Negated + + if (sectionNode != null) { + AtomicBoolean delayed = new AtomicBoolean(false); + Runnable afterLoading = () -> delayed.set(!getParser().getHasDelayBefore().isFalse()); + trigger = loadCode(sectionNode, "structure place", afterLoading, StructurePlaceEvent.class); + if (delayed.get()) { + Skript.error("Delays can't be used within a structure place section!"); + return false; + } + } + return true; + } + + @Override + @Nullable + protected TriggerItem walk(Event event) { + Structure structure = this.structure.getSingle(event); + if (structure == null) { + debug(event, false); + return super.walk(event, false); + } + StructurePlaceEvent details = new StructurePlaceEvent(structure, entities); + if (trigger != null) { + Object localVars = Variables.copyLocalVariables(event); + Variables.setLocalVariables(details, localVars); + TriggerItem.walk(trigger, details); + Variables.setLocalVariables(event, Variables.copyLocalVariables(details)); + Variables.removeLocals(details); + } + + for (Location location : locations.getArray(event)) { + structure.place(location, details.includeEntities(), details.getRotation(), details.getMirror(), details.getPallet(), details.getIntegrity(), new Random()); + } + + return super.walk(event, false); + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "place structure " + structure.toString(event, debug) + " at " + locations.toString(event, debug); + } + +} diff --git a/src/main/java/ch/njol/skript/util/BlockStateBlock.java b/src/main/java/ch/njol/skript/util/BlockStateBlock.java index 364ae09229c..34d132f56e4 100644 --- a/src/main/java/ch/njol/skript/util/BlockStateBlock.java +++ b/src/main/java/ch/njol/skript/util/BlockStateBlock.java @@ -70,7 +70,7 @@ public BlockStateBlock(BlockState state) { public BlockStateBlock(BlockState state, boolean delayChanges) { assert state != null; this.state = state; - if (ISPASSABLE_METHOD_EXISTS) + if (ISPASSABLE_METHOD_EXISTS && state.isPlaced()) this.isPassable = state.getBlock().isPassable(); else this.isPassable = false; diff --git a/src/main/java/ch/njol/skript/util/BlockUtils.java b/src/main/java/ch/njol/skript/util/BlockUtils.java index 1768fd5b4f6..fe98308272c 100644 --- a/src/main/java/ch/njol/skript/util/BlockUtils.java +++ b/src/main/java/ch/njol/skript/util/BlockUtils.java @@ -41,7 +41,7 @@ * TODO !Update with every version [blocks] - also update aliases-*.sk */ public class BlockUtils { - + /** * Sets the given block. * @param block Block to set. @@ -58,25 +58,23 @@ public static boolean set(Block block, Material type, @Nullable BlockValues bloc return true; } - + public static boolean set(Block block, ItemData type, boolean applyPhysics) { return set(block, type.getType(), type.getBlockValues(), applyPhysics); } - + public static void sendBlockChange(Player player, Location location, Material type, @Nullable BlockValues blockValues) { BlockCompat.SETTER.sendBlockChange(player, location, type, blockValues); } - - @SuppressWarnings("null") + public static Iterable getBlocksAround(Block b) { return Arrays.asList(b.getRelative(BlockFace.NORTH), b.getRelative(BlockFace.EAST), b.getRelative(BlockFace.SOUTH), b.getRelative(BlockFace.WEST)); } - - @SuppressWarnings("null") + public static Iterable getFaces() { return Arrays.asList(BlockFace.NORTH, BlockFace.EAST, BlockFace.SOUTH, BlockFace.WEST); } - + /** * @param b A block * @return Location of the block, including its direction @@ -93,7 +91,7 @@ public static Location getLocation(@Nullable Block b) { } return l; } - + @Nullable public static BlockData createBlockData(String dataString) { // Skript uses a comma to separate lists, so we use a semi colon as a delimiter @@ -164,4 +162,36 @@ public static Block extractBlock(Block block) { return block instanceof DelayedChangeBlock ? ((DelayedChangeBlock) block).block : block; } + /** + * Returns the lowest block location between the two locations. + * + * @param location1 The first location. + * @param location2 The second location. + * @return Location of the lowest block location from the two locations. Null if worlds don't match. + */ + public static Location getLowestBlockLocation(Location location1, Location location2) { + if (location1.getWorld() != location2.getWorld()) + return null; + int x = Math.min(location1.getBlockX(), location2.getBlockX()); + int y = Math.min(location1.getBlockY(), location2.getBlockY()); + int z = Math.min(location1.getBlockZ(), location2.getBlockZ()); + return new Location(location1.getWorld(), x, y, z); + } + + /** + * Returns the highest block location between the two locations. + * + * @param location1 The first location. + * @param location2 The second location. + * @return Location of the highest block location from the two locations. Null if worlds don't match. + */ + public static Location getHighestBlockLocation(Location location1, Location location2) { + if (location1.getWorld() != location2.getWorld()) + return null; + int x = Math.max(location1.getBlockX(), location2.getBlockX()); + int y = Math.max(location1.getBlockY(), location2.getBlockY()); + int z = Math.max(location1.getBlockZ(), location2.getBlockZ()); + return new Location(location1.getWorld(), x, y, z); + } + } diff --git a/src/main/java/ch/njol/skript/util/Utils.java b/src/main/java/ch/njol/skript/util/Utils.java index 2cda7d3109b..1a450b3e4f3 100644 --- a/src/main/java/ch/njol/skript/util/Utils.java +++ b/src/main/java/ch/njol/skript/util/Utils.java @@ -32,6 +32,7 @@ import java.util.stream.Stream; import org.bukkit.Bukkit; +import org.bukkit.NamespacedKey; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.java.JavaPlugin; @@ -809,6 +810,21 @@ public static boolean isInteger(Number... numbers) { return true; } + /** + * Returns a namespaced key. If the key contains a collon, it'll lookup a Minecraft key. + * Otherwise will return a key registered under Skript. + * Will return null if the Minecraft key doesn't exist. + * + * @param key The string key to lookup. + * @return The found NamespacedKey or null if not found when using a collon. + */ + @Nullable + public static NamespacedKey getNamespacedKey(String key) { + if (key.contains(":")) + return NamespacedKey.fromString(key); + return NamespacedKey.fromString(key, Skript.getInstance()); + } + protected static class WordEnding { // To be a record in 2.10 private final String singular, plural; diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index fec454c2ac5..5689e88f016 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -2275,6 +2275,19 @@ transform reasons: unknown: unknown infection: infection, villager infection +# -- Structure Rotations -- +structure rotations: + clockwise_180: clockwise 180 + clockwise_90: clockwise 90 + counterclockwise_90: counter clockwise 90 + none: none, default + +# -- Structure Mirrors -- +mirrors: + front_back: front back, front to back + left_right: left right, left to right + none: none, default + # -- Boolean -- boolean: true: @@ -2345,6 +2358,9 @@ types: cattype: cat type¦s @a gamerule: gamerule¦s @a attributetype: attribute type¦s @a + structure: structure¦s @a + structurerotation: structure rotation¦s @a + mirror: mirror¦s @a enchantmentoffer: enchantment offer¦s @an environment: environment¦s @an moonphase: moonphase¦s @a