diff --git a/.github/workflows/checkstyle.yml b/.github/workflows/checkstyle.yml new file mode 100644 index 00000000000..409ca6b4fd7 --- /dev/null +++ b/.github/workflows/checkstyle.yml @@ -0,0 +1,37 @@ +name: checkstyle + +on: + push: + branches: + - master + - 'dev/**' + pull_request: + +jobs: + build: + if: "! contains(toJSON(github.event.commits.*.message), '[ci skip]')" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + - name: validate gradle wrapper + uses: gradle/wrapper-validation-action@v2 + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'adopt' + cache: gradle + - name: Grant execute permission for gradlew + run: chmod +x gradlew + - name: Run checkstyle + run: ./gradlew clean checkstyleMain + - name: Upload checkstyle report + uses: actions/upload-artifact@v4 + if: success() + with: + name: checkstyle-report + path: | + build/reports/checkstyle/*.xml + build/reports/checkstyle/*.html diff --git a/build.gradle b/build.gradle index 88fe6a634a8..6052dc530da 100644 --- a/build.gradle +++ b/build.gradle @@ -7,6 +7,7 @@ plugins { id 'com.github.johnrengelman.shadow' version '8.1.1' id 'maven-publish' id 'java' + id 'checkstyle' } configurations { @@ -42,6 +43,11 @@ dependencies { testShadow group: 'org.easymock', name: 'easymock', version: '5.4.0' } +checkstyle { + configFile = new File("checkstyle.xml") + sourceSets = [] // disables checkstyle after build task +} + task checkAliases { description 'Checks for the existence of the aliases.' doLast { diff --git a/checkstyle.xml b/checkstyle.xml new file mode 100644 index 00000000000..42794bbeccb --- /dev/null +++ b/checkstyle.xml @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/code-conventions.md b/code-conventions.md index c9b70f8d5a4..f47329495a0 100644 --- a/code-conventions.md +++ b/code-conventions.md @@ -97,6 +97,7 @@ If we need to remove or alter contributed code due to a licensing issue we will - The exception to this is breaking up conditional statements (e.g. `if (x || y)`) where the condition starts may be aligned * Each class begins with an empty line +* Each Java file ends with an empty line * No squeezing of multiple lines of code on a single line * Separate method declarations with empty lines - Empty line after last method in a class is *not* required diff --git a/gradle.properties b/gradle.properties index a620b360bd5..8fa041e0878 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,7 @@ org.gradle.parallel=true groupid=ch.njol name=skript -version=2.9.2 +version=2.9.3 jarName=Skript.jar testEnv=java21/paper-1.21.0 testEnvJavaVersion=21 diff --git a/src/main/java/ch/njol/skript/SkriptCommand.java b/src/main/java/ch/njol/skript/SkriptCommand.java index e850820aaca..9dc50018dd5 100644 --- a/src/main/java/ch/njol/skript/SkriptCommand.java +++ b/src/main/java/ch/njol/skript/SkriptCommand.java @@ -215,7 +215,11 @@ public boolean onCommand(CommandSender sender, Command command, String label, St if (scriptInfo.files == 0) { info(sender, "reload.empty folder", fileName); } else { - reloaded(sender, logHandler, timingLogHandler, "x scripts in folder", fileName, scriptInfo.files); + if (logHandler.numErrors() == 0) { + reloaded(sender, logHandler, timingLogHandler, "x scripts in folder success", fileName, scriptInfo.files); + } else { + reloaded(sender, logHandler, timingLogHandler, "x scripts in folder error", fileName, scriptInfo.files); + } } }); } diff --git a/src/main/java/ch/njol/skript/aliases/Aliases.java b/src/main/java/ch/njol/skript/aliases/Aliases.java index 19daed69359..3e67971698a 100644 --- a/src/main/java/ch/njol/skript/aliases/Aliases.java +++ b/src/main/java/ch/njol/skript/aliases/Aliases.java @@ -395,17 +395,17 @@ public static void load() { } /** - * Temporarily create an alias for a material which may not have an alias yet. + * Temporarily create an alias for materials which do not have aliases yet. */ private static void loadMissingAliases() { if (!Skript.methodExists(Material.class, "getKey")) return; for (Material material : Material.values()) { - if (!provider.hasAliasForMaterial(material)) { + if (!material.isLegacy() && !provider.hasAliasForMaterial(material)) { NamespacedKey key = material.getKey(); String name = key.getKey().replace("_", " "); parser.loadAlias(name + "¦s", key.toString()); - Skript.debug(ChatColor.YELLOW + "Creating temporary alias for: " + key.toString()); + Skript.debug(ChatColor.YELLOW + "Creating temporary alias for: " + key); } } } diff --git a/src/main/java/ch/njol/skript/aliases/ItemType.java b/src/main/java/ch/njol/skript/aliases/ItemType.java index 8b78d39229b..aa772d2b646 100644 --- a/src/main/java/ch/njol/skript/aliases/ItemType.java +++ b/src/main/java/ch/njol/skript/aliases/ItemType.java @@ -1,21 +1,3 @@ -/** - * 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.aliases; import ch.njol.skript.aliases.ItemData.OldItemData; @@ -41,6 +23,7 @@ import org.bukkit.Material; import org.bukkit.OfflinePlayer; import org.bukkit.Tag; +import org.bukkit.Bukkit; import org.bukkit.block.Block; import org.bukkit.block.BlockState; import org.bukkit.block.Skull; @@ -391,22 +374,35 @@ public boolean hasType() { */ public boolean setBlock(Block block, boolean applyPhysics) { for (int i = random.nextInt(types.size()); i < types.size(); i++) { - ItemData d = types.get(i); - Material blockType = ItemUtils.asBlock(d.type); + ItemData data = types.get(i); + Material blockType = ItemUtils.asBlock(data.type); + if (blockType == null) // Ignore items which cannot be placed continue; - if (BlockUtils.set(block, blockType, d.getBlockValues(), applyPhysics)) { - ItemMeta itemMeta = getItemMeta(); - if (itemMeta instanceof SkullMeta) { - OfflinePlayer offlinePlayer = ((SkullMeta) itemMeta).getOwningPlayer(); - if (offlinePlayer == null) - continue; - Skull skull = (Skull) block.getState(); + + if (!BlockUtils.set(block, blockType, data.getBlockValues(), applyPhysics)) + continue; + + ItemMeta itemMeta = getItemMeta(); + + if (itemMeta instanceof SkullMeta) { + OfflinePlayer offlinePlayer = ((SkullMeta) itemMeta).getOwningPlayer(); + if (offlinePlayer == null) + continue; + Skull skull = (Skull) block.getState(); + if (offlinePlayer.getName() != null) { skull.setOwningPlayer(offlinePlayer); - skull.update(false, applyPhysics); + } else if (ItemUtils.CAN_CREATE_PLAYER_PROFILE) { + //noinspection deprecation + skull.setOwnerProfile(Bukkit.createPlayerProfile(offlinePlayer.getUniqueId(), "")); + } else { + //noinspection deprecation + skull.setOwner(""); } - return true; + skull.update(false, applyPhysics); } + + return true; } return false; } diff --git a/src/main/java/ch/njol/skript/bukkitutil/BukkitUnsafe.java b/src/main/java/ch/njol/skript/bukkitutil/BukkitUnsafe.java index 5a2912039fb..f47a3f5d8d7 100644 --- a/src/main/java/ch/njol/skript/bukkitutil/BukkitUnsafe.java +++ b/src/main/java/ch/njol/skript/bukkitutil/BukkitUnsafe.java @@ -70,11 +70,7 @@ public class BukkitUnsafe { @Nullable public static Material getMaterialFromMinecraftId(String id) { - // On 1.13, Vanilla and Spigot names are same - if (id.length() > 9) - return Material.matchMaterial(id.substring(10)); // Strip 'minecraft:' out - else // Malformed material name - return null; + return Material.matchMaterial(id); } public static void modifyItemStack(ItemStack stack, String arguments) { diff --git a/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java b/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java index ced9f5a8367..a74a604664d 100644 --- a/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java +++ b/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java @@ -21,7 +21,9 @@ import ch.njol.skript.Skript; import ch.njol.skript.aliases.ItemType; import ch.njol.skript.util.slot.Slot; +import org.bukkit.Bukkit; import org.bukkit.Material; +import org.bukkit.OfflinePlayer; import org.bukkit.Tag; import org.bukkit.TreeType; import org.bukkit.block.Block; @@ -32,9 +34,11 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.Damageable; import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.SkullMeta; import org.jetbrains.annotations.Nullable; import java.util.HashMap; +import java.util.UUID; /** * Miscellaneous static utility methods related to items. @@ -44,6 +48,7 @@ public class ItemUtils { public static final boolean HAS_MAX_DAMAGE = Skript.methodExists(Damageable.class, "getMaxDamage"); // Introduced in Paper 1.21 public static final boolean HAS_RESET = Skript.methodExists(Damageable.class, "resetDamage"); + public static final boolean CAN_CREATE_PLAYER_PROFILE = Skript.methodExists(Bukkit.class, "createPlayerProfile", UUID.class, String.class); /** * Gets damage/durability of an item, or 0 if it does not have damage. @@ -148,6 +153,30 @@ public static void setDamage(ItemType itemType, int damage) { } } + /** + * Sets the owner of a player head. + * @param skull player head item to modify + * @param player owner of the head + */ + public static void setHeadOwner(ItemType skull, OfflinePlayer player) { + ItemMeta meta = skull.getItemMeta(); + if (!(meta instanceof SkullMeta)) + return; + + SkullMeta skullMeta = (SkullMeta) meta; + + if (player.getName() != null) { + skullMeta.setOwningPlayer(player); + } else if (CAN_CREATE_PLAYER_PROFILE) { + //noinspection deprecation + skullMeta.setOwnerProfile(Bukkit.createPlayerProfile(player.getUniqueId(), "")); + } else { + skullMeta.setOwningPlayer(null); + } + + skull.setItemMeta(skullMeta); + } + /** * Gets a block material corresponding to given item material, which might * be the given material. If no block material is found, null is returned. diff --git a/src/main/java/ch/njol/skript/classes/data/DefaultOperations.java b/src/main/java/ch/njol/skript/classes/data/DefaultOperations.java index 860c7ee1030..1588d31ece8 100644 --- a/src/main/java/ch/njol/skript/classes/data/DefaultOperations.java +++ b/src/main/java/ch/njol/skript/classes/data/DefaultOperations.java @@ -47,11 +47,7 @@ public class DefaultOperations { return left.doubleValue() * right.doubleValue(); }); Arithmetics.registerOperation(Operator.DIVISION, Number.class, (left, right) -> left.doubleValue() / right.doubleValue()); - Arithmetics.registerOperation(Operator.EXPONENTIATION, Number.class, (left, right) -> { - if (Utils.isInteger(left, right) && right.longValue() >= 0) - return (long) Math.pow(left.longValue(), right.longValue()); - return Math.pow(left.doubleValue(), right.doubleValue()); - }); + Arithmetics.registerOperation(Operator.EXPONENTIATION, Number.class, (left, right) -> Math.pow(left.doubleValue(), right.doubleValue())); Arithmetics.registerDifference(Number.class, (left, right) -> { if (Utils.isInteger(left, right)) return Math.abs(left.longValue() - right.longValue()); diff --git a/src/main/java/ch/njol/skript/conditions/CondIsWithin.java b/src/main/java/ch/njol/skript/conditions/CondIsWithin.java index e6b88ed710a..7ec25401ae8 100644 --- a/src/main/java/ch/njol/skript/conditions/CondIsWithin.java +++ b/src/main/java/ch/njol/skript/conditions/CondIsWithin.java @@ -103,7 +103,7 @@ public boolean check(Event event) { Location one = loc1.getSingle(event); Location two = loc2.getSingle(event); if (one == null || two == null || one.getWorld() != two.getWorld()) - return false; + return isNegated(); AABB box = new AABB(one, two); return locsToCheck.check(event, box::contains, isNegated()); } @@ -111,7 +111,7 @@ public boolean check(Event event) { // else, within an entity/block/chunk/world Object area = this.area.getSingle(event); if (area == null) - return false; + return isNegated(); // Entities if (area instanceof Entity) { diff --git a/src/main/java/ch/njol/skript/effects/EffCancelEvent.java b/src/main/java/ch/njol/skript/effects/EffCancelEvent.java index 317cdd3a9ee..3343f032cdf 100644 --- a/src/main/java/ch/njol/skript/effects/EffCancelEvent.java +++ b/src/main/java/ch/njol/skript/effects/EffCancelEvent.java @@ -1,34 +1,5 @@ -/** - * 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 org.bukkit.entity.Player; -import org.bukkit.event.Cancellable; -import org.bukkit.event.Event; -import org.bukkit.event.Event.Result; -import org.bukkit.event.block.BlockCanBuildEvent; -import org.bukkit.event.inventory.InventoryInteractEvent; -import org.bukkit.event.player.PlayerDropItemEvent; -import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.event.player.PlayerLoginEvent; -import org.jetbrains.annotations.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.bukkitutil.PlayerUtils; import ch.njol.skript.doc.Description; @@ -42,39 +13,58 @@ import ch.njol.skript.log.ErrorQuality; import ch.njol.skript.util.Utils; import ch.njol.util.Kleenean; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.block.BlockCanBuildEvent; +import org.bukkit.event.entity.EntityToggleSwimEvent; +import org.bukkit.event.inventory.InventoryInteractEvent; +import org.bukkit.event.player.PlayerDropItemEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerLoginEvent; +import org.jetbrains.annotations.Nullable; -/** - * @author Peter Güttinger - */ @Name("Cancel Event") @Description("Cancels the event (e.g. prevent blocks from being placed, or damage being taken).") -@Examples({"on damage:", - " victim is a player", - " victim has the permission \"skript.god\"", - " cancel the event"}) +@Examples({ + "on damage:", + "\tvictim is a player", + "\tvictim has the permission \"skript.god\"", + "\tcancel the event" +}) @Since("1.0") public class EffCancelEvent extends Effect { + static { Skript.registerEffect(EffCancelEvent.class, "cancel [the] event", "uncancel [the] event"); } private boolean cancel; - - @SuppressWarnings("null") + @Override - public boolean init(final Expression[] vars, final int matchedPattern, final Kleenean isDelayed, final ParseResult parser) { + public boolean init(Expression[] expressions, int matchedPattern, + Kleenean isDelayed, ParseResult parseResult) { if (isDelayed == Kleenean.TRUE) { - Skript.error("Can't cancel an event anymore after it has already passed", ErrorQuality.SEMANTIC_ERROR); + Skript.error("An event cannot be cancelled after it has already passed", ErrorQuality.SEMANTIC_ERROR); return false; } + cancel = matchedPattern == 0; - final Class[] es = getParser().getCurrentEvents(); - if (es == null) + Class[] currentEvents = getParser().getCurrentEvents(); + + if (currentEvents == null) + return false; + + if (cancel && getParser().isCurrentEvent(EntityToggleSwimEvent.class)) { + Skript.error("Cancelling a toggle swim event has no effect"); return false; - for (final Class e : es) { - if (Cancellable.class.isAssignableFrom(e) || BlockCanBuildEvent.class.isAssignableFrom(e)) + } + + for (Class event : currentEvents) { + if (Cancellable.class.isAssignableFrom(event) || BlockCanBuildEvent.class.isAssignableFrom(event)) return true; // TODO warning if some event(s) cannot be cancelled even though some can (needs a way to be suppressed) } + if (getParser().isCurrentEvent(PlayerLoginEvent.class)) Skript.error("A connect event cannot be cancelled, but the player may be kicked ('kick player by reason of \"...\"')", ErrorQuality.SEMANTIC_ERROR); else @@ -83,24 +73,24 @@ public boolean init(final Expression[] vars, final int matchedPattern, final } @Override - public void execute(final Event e) { - if (e instanceof Cancellable) - ((Cancellable) e).setCancelled(cancel); - if (e instanceof PlayerInteractEvent) { - EvtClick.interactTracker.eventModified((Cancellable) e); - ((PlayerInteractEvent) e).setUseItemInHand(cancel ? Result.DENY : Result.DEFAULT); - ((PlayerInteractEvent) e).setUseInteractedBlock(cancel ? Result.DENY : Result.DEFAULT); - } else if (e instanceof BlockCanBuildEvent) { - ((BlockCanBuildEvent) e).setBuildable(!cancel); - } else if (e instanceof PlayerDropItemEvent) { - PlayerUtils.updateInventory(((PlayerDropItemEvent) e).getPlayer()); - } else if (e instanceof InventoryInteractEvent) { - PlayerUtils.updateInventory(((Player) ((InventoryInteractEvent) e).getWhoClicked())); + public void execute(Event event) { + if (event instanceof Cancellable) + ((Cancellable) event).setCancelled(cancel); + if (event instanceof PlayerInteractEvent) { + EvtClick.interactTracker.eventModified((Cancellable) event); + ((PlayerInteractEvent) event).setUseItemInHand(cancel ? Event.Result.DENY : Event.Result.DEFAULT); + ((PlayerInteractEvent) event).setUseInteractedBlock(cancel ? Event.Result.DENY : Event.Result.DEFAULT); + } else if (event instanceof BlockCanBuildEvent) { + ((BlockCanBuildEvent) event).setBuildable(!cancel); + } else if (event instanceof PlayerDropItemEvent) { + PlayerUtils.updateInventory(((PlayerDropItemEvent) event).getPlayer()); + } else if (event instanceof InventoryInteractEvent) { + PlayerUtils.updateInventory(((Player) ((InventoryInteractEvent) event).getWhoClicked())); } } @Override - public String toString(final @Nullable Event e, final boolean debug) { + public String toString(@Nullable Event event, boolean debug) { return (cancel ? "" : "un") + "cancel event"; } diff --git a/src/main/java/ch/njol/skript/expressions/ExprHoverList.java b/src/main/java/ch/njol/skript/expressions/ExprHoverList.java index a3b58b6b9d3..835319f3449 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprHoverList.java +++ b/src/main/java/ch/njol/skript/expressions/ExprHoverList.java @@ -104,7 +104,7 @@ public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { if (HAS_NEW_LISTED_PLAYER_INFO) { List values = new ArrayList<>(); - if (mode != ChangeMode.DELETE && mode != ChangeMode.RESET) { + if (mode != ChangeMode.DELETE && mode != ChangeMode.RESET && mode != ChangeMode.REMOVE) { for (Object object : delta) { if (object instanceof Player) { Player player = (Player) object; @@ -124,7 +124,9 @@ public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { sample.addAll(values); break; case REMOVE: - sample.removeAll(values); + for (Object value : delta) { + sample.removeIf(profile -> profile.name().equals(value)); + } break; case DELETE: case RESET: @@ -135,7 +137,7 @@ public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { } List values = new ArrayList<>(); - if (mode != ChangeMode.DELETE && mode != ChangeMode.RESET) { + if (mode != ChangeMode.DELETE && mode != ChangeMode.RESET && mode != ChangeMode.REMOVE) { for (Object object : delta) { if (object instanceof Player) { Player player = (Player) object; @@ -150,13 +152,14 @@ public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { switch (mode) { case SET: sample.clear(); - sample.addAll(values); - break; + // $FALL-THROUGH$ case ADD: sample.addAll(values); break; case REMOVE: - sample.removeAll(values); + for (Object value : delta) { + sample.removeIf(profile -> profile.getName() != null && profile.getName().equals(value)); + } break; case DELETE: case RESET: diff --git a/src/main/java/ch/njol/skript/expressions/ExprItems.java b/src/main/java/ch/njol/skript/expressions/ExprItems.java index 652cb3f6a72..4fb0c11d66a 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprItems.java +++ b/src/main/java/ch/njol/skript/expressions/ExprItems.java @@ -1,21 +1,3 @@ -/** - * 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 ch.njol.skript.Skript; @@ -54,7 +36,7 @@ public class ExprItems extends SimpleExpression { private static final ItemType[] ALL_BLOCKS = Arrays.stream(Material.values()) - .filter(Material::isBlock) + .filter(material -> !material.isLegacy() && material.isBlock()) .map(ItemType::new) .toArray(ItemType[]::new); diff --git a/src/main/java/ch/njol/skript/expressions/ExprSkull.java b/src/main/java/ch/njol/skript/expressions/ExprSkull.java index bce8422a76d..8b8c0e0e19b 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprSkull.java +++ b/src/main/java/ch/njol/skript/expressions/ExprSkull.java @@ -1,86 +1,45 @@ -/** - * 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 ch.njol.skript.bukkitutil.ItemUtils; import org.bukkit.Material; import org.bukkit.OfflinePlayer; -import org.bukkit.inventory.meta.SkullMeta; import org.jetbrains.annotations.Nullable; -import ch.njol.skript.Skript; -import ch.njol.skript.aliases.Aliases; import ch.njol.skript.aliases.ItemType; 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.skript.lang.Expression; -import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.util.Kleenean; -/** - * @author Peter Güttinger - */ @Name("Player Skull") @Description("Gets a skull item representing a player. Skulls for other entities are provided by the aliases.") -@Examples({"give the victim's skull to the attacker", - "set the block at the entity to the entity's skull"}) +@Examples({ + "give the victim's skull to the attacker", + "set the block at the entity to the entity's skull" +}) @Since("2.0") -public class ExprSkull extends SimplePropertyExpression { - +public class ExprSkull extends SimplePropertyExpression { + static { register(ExprSkull.class, ItemType.class, "(head|skull)", "offlineplayers"); } - - /** - * In 2017, SkullMeta finally got a method that takes OfflinePlayer. - */ - private static final boolean newSkullOwner = Skript.methodExists(SkullMeta.class, "setOwningPlayer", OfflinePlayer.class); - - @Override - public boolean init(final Expression[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parseResult) { - return super.init(exprs, matchedPattern, isDelayed, parseResult); - } - - @SuppressWarnings("deprecation") + @Override - @Nullable - public ItemType convert(final Object o) { + public @Nullable ItemType convert(OfflinePlayer player) { ItemType skull = new ItemType(Material.PLAYER_HEAD); - SkullMeta meta = (SkullMeta) skull.getItemMeta(); - if (newSkullOwner) - meta.setOwningPlayer((OfflinePlayer) o); - else - meta.setOwner(((OfflinePlayer) o).getName()); - skull.setItemMeta(meta); + ItemUtils.setHeadOwner(skull, player); return skull; } - + @Override public Class getReturnType() { return ItemType.class; } - + @Override protected String getPropertyName() { return "skull"; } - + } diff --git a/src/main/java/ch/njol/skript/expressions/ExprSkullOwner.java b/src/main/java/ch/njol/skript/expressions/ExprSkullOwner.java index ec80ad70793..bc102126022 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprSkullOwner.java +++ b/src/main/java/ch/njol/skript/expressions/ExprSkullOwner.java @@ -1,23 +1,6 @@ -/** - * 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 ch.njol.skript.bukkitutil.ItemUtils; import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; @@ -25,6 +8,7 @@ import ch.njol.skript.doc.Since; import ch.njol.skript.expressions.base.SimplePropertyExpression; import ch.njol.util.coll.CollectionUtils; +import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; import org.bukkit.block.Block; import org.bukkit.block.BlockState; @@ -66,11 +50,20 @@ public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { OfflinePlayer offlinePlayer = (OfflinePlayer) delta[0]; for (Block block : getExpr().getArray(event)) { BlockState state = block.getState(); - if (state instanceof Skull) { - Skull skull = (Skull) state; + if (!(state instanceof Skull)) + continue; + + Skull skull = (Skull) state; + if (offlinePlayer.getName() != null) { skull.setOwningPlayer(offlinePlayer); - skull.update(true, false); + } else if (ItemUtils.CAN_CREATE_PLAYER_PROFILE) { + //noinspection deprecation + skull.setOwnerProfile(Bukkit.createPlayerProfile(offlinePlayer.getUniqueId(), "")); + } else { + //noinspection deprecation + skull.setOwner(""); } + skull.update(true, false); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprTime.java b/src/main/java/ch/njol/skript/expressions/ExprTime.java index 4e769f6656d..e2199e18a80 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprTime.java +++ b/src/main/java/ch/njol/skript/expressions/ExprTime.java @@ -1,27 +1,5 @@ -/** - * 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.World; -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; @@ -32,42 +10,51 @@ import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.skript.registrations.Classes; -import ch.njol.skript.util.Getter; import ch.njol.skript.util.Time; import ch.njol.skript.util.Timeperiod; import ch.njol.skript.util.Timespan; import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; +import org.bukkit.World; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; -/** - * @author Peter Güttinger - */ @Name("Time") -@Description("The time of a world.") -@Examples({"time in world is between 18:00 and 6:00:", - " broadcast \"It's night-time, watch out for monsters!\""}) +@Description({ + "The time of a world.", + "Use the \"minecraft timespan\" syntax to change the time according " + + "to Minecraft's time intervals.", + "Since Minecraft uses discrete intervals for time (ticks), " + + "changing the time by real-world minutes or real-world seconds only changes it approximately.", + "Removing an amount of time from a world's time will move the clock forward a day." +}) +@Examples({ + "set time of world \"world\" to 2:00", + "add 2 minecraft hours to time of world \"world\"", + "add 54 real seconds to time of world \"world\" # approximately 1 minecraft hour" +}) @Since("1.0") public class ExprTime extends PropertyExpression { + + // 18000 is the offset to allow for using "add 2:00" without going to a new day + // and causing unexpected behaviour + private static final int TIME_TO_TIMESPAN_OFFSET = 18000; + static { - Skript.registerExpression(ExprTime.class, Time.class, ExpressionType.PROPERTY, "[the] time[s] [([with]in|of) %worlds%]", "%worlds%'[s] time[s]"); + Skript.registerExpression(ExprTime.class, Time.class, ExpressionType.PROPERTY, + "[the] time[s] [([with]in|of) %worlds%]", "%worlds%'[s] time[s]"); } - - @SuppressWarnings({"unchecked", "null"}) + + @SuppressWarnings("unchecked") @Override - public boolean init(final Expression[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parser) { - setExpr((Expression) exprs[0]); + public boolean init(Expression[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parser) { + setExpr((Expression) expressions[0]); return true; } - + @Override - protected Time[] get(final Event e, final World[] source) { - return get(source, new Getter() { - @Override - public Time get(final World w) { - return new Time((int) w.getTime()); - } - }); + protected Time[] get(Event event, World[] worlds) { + return get(worlds, world -> new Time((int) world.getTime())); } @Override @@ -76,56 +63,62 @@ public Class[] acceptChange(final ChangeMode mode) { switch (mode) { case ADD: case REMOVE: - return CollectionUtils.array(Timespan.class); + // allow time to avoid conversion to timespan, which causes all sorts of headaches + return CollectionUtils.array(Time.class, Timespan.class); case SET: return CollectionUtils.array(Time.class, Timeperiod.class); - case DELETE: - case REMOVE_ALL: - case RESET: default: return null; } } - + @Override - public void change(final Event e, final @Nullable Object[] delta, final ChangeMode mode) { - final World[] worlds = getExpr().getArray(e); - int mod = 1; - switch (mode) { - case SET: - assert delta != null; - final int time = delta[0] instanceof Time ? ((Time) delta[0]).getTicks() : ((Timeperiod) delta[0]).start; - for (final World w : worlds) { - w.setTime(time); - } - break; - case REMOVE: - mod = -1; - //$FALL-THROUGH$ - case ADD: - assert delta != null; - final Timespan ts = (Timespan) delta[0]; - for (final World w : worlds) { - w.setTime(w.getTime() + mod * ts.getTicks()); - } - break; - case DELETE: - case REMOVE_ALL: - case RESET: - assert false; + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + if (getExpr() == null || delta == null) + return; + + Object time = delta[0]; + if (time == null) + return; + + World[] worlds = getExpr().getArray(event); + + long ticks = 0; + if (time instanceof Time) { + if (mode != ChangeMode.SET) { + ticks = ((Time) time).getTicks() - TIME_TO_TIMESPAN_OFFSET; + } else { + ticks = ((Time) time).getTicks(); + } + } else if (time instanceof Timespan) { + ticks = ((Timespan) time).getAs(Timespan.TimePeriod.TICK); + } else if (time instanceof Timeperiod) { + ticks = ((Timeperiod) time).start; + } + + for (World world : worlds) { + switch (mode) { + case ADD: + world.setTime(world.getTime() + ticks); + break; + case REMOVE: + world.setTime(world.getTime() - ticks); + break; + case SET: + world.setTime(ticks); + break; + } } } - + @Override public Class