diff --git a/src/main/java/ch/njol/skript/bukkitutil/ServerUtils.java b/src/main/java/ch/njol/skript/bukkitutil/ServerUtils.java new file mode 100644 index 00000000000..cb56f1ab5f1 --- /dev/null +++ b/src/main/java/ch/njol/skript/bukkitutil/ServerUtils.java @@ -0,0 +1,46 @@ +/** + * 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.bukkitutil; + +import ch.njol.skript.Skript; +import org.bukkit.Bukkit; +import org.bukkit.ServerTickManager; + +public class ServerUtils { + + private static final ServerTickManager SERVER_TICK_MANAGER; + + static { + ServerTickManager serverTickManager = null; + if (Skript.methodExists(Bukkit.class, "getServerTickManager")) { + serverTickManager = Bukkit.getServerTickManager(); + } + SERVER_TICK_MANAGER = serverTickManager; + } + + public static ServerTickManager getServerTickManager() { + return SERVER_TICK_MANAGER; + } + + public static boolean isServerTickManagerPresent() { + return SERVER_TICK_MANAGER != null; + } + +} + diff --git a/src/main/java/ch/njol/skript/conditions/CondIsTickFrozen.java b/src/main/java/ch/njol/skript/conditions/CondIsTickFrozen.java new file mode 100644 index 00000000000..41064f8f939 --- /dev/null +++ b/src/main/java/ch/njol/skript/conditions/CondIsTickFrozen.java @@ -0,0 +1,56 @@ +/** + * 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 ch.njol.skript.Skript; +import ch.njol.skript.bukkitutil.ServerUtils; +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.RequiredPlugins; +import ch.njol.skript.doc.Since; +import org.bukkit.Bukkit; +import org.bukkit.entity.Entity; + +@Name("Is Entity Tick Frozen") +@Description("Checks if the specified entities are frozen due to the server's ticking state.") +@Examples("if target entity is tick frozen:") +@Since("INSERT VERSION") +@RequiredPlugins("Minecraft 1.20.4+") +public class CondIsTickFrozen extends PropertyCondition { + + + static { + if (ServerUtils.isServerTickManagerPresent()) + register(CondIsTickFrozen.class, "tick frozen", "entities"); + } + + @Override + public boolean check(Entity entity) { + return ServerUtils.getServerTickManager().isFrozen(entity); + } + + @Override + protected String getPropertyName() { + return "tick frozen"; + } + +} + diff --git a/src/main/java/ch/njol/skript/conditions/CondServerTickState.java b/src/main/java/ch/njol/skript/conditions/CondServerTickState.java new file mode 100644 index 00000000000..cc41e829b20 --- /dev/null +++ b/src/main/java/ch/njol/skript/conditions/CondServerTickState.java @@ -0,0 +1,73 @@ +package ch.njol.skript.conditions; + +import ch.njol.skript.Skript; +import ch.njol.skript.bukkitutil.ServerUtils; +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.util.Kleenean; +import org.bukkit.ServerTickManager; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; + +import java.util.Locale; + +@Name("Server Tick State") +@Description("Represents the ticking state of the server, for example, if the server is frozen, or running normally.") +@Examples({ + "if server's tick state is currently frozen:", + "if the server is sprinting:" +}) +@Since("INSERT VERSION") +@RequiredPlugins("Minecraft 1.20.4+") +public class CondServerTickState extends Condition { + + static { + if (ServerUtils.isServerTickManagerPresent()) + Skript.registerCondition(CondServerTickState.class, + "[the] server's tick[ing] state is [currently] (:frozen|:stepping|:sprinting|:normal)", + "[the] server's tick[ing] state (isn't|is not) [currently] (:frozen|:stepping|:sprinting|:normal)"); + } + + private ServerState state; + + public enum ServerState { + FROZEN, STEPPING, SPRINTING, NORMAL + } + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + String tag = parseResult.tags.get(0).toUpperCase(Locale.ENGLISH); + state = ServerState.valueOf(tag); + setNegated(matchedPattern == 1); + return true; + } + @Override + public boolean check(Event event) { + ServerTickManager serverTickManager = ServerUtils.getServerTickManager(); + boolean result = switch (state) { + case FROZEN -> serverTickManager.isFrozen(); + case STEPPING -> serverTickManager.isStepping(); + case SPRINTING -> serverTickManager.isSprinting(); + case NORMAL -> serverTickManager.isRunningNormally(); + }; + return isNegated() != result; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + String stateStr; + if (isNegated()) { + stateStr = "the server's tick state isn't "; + } else { + stateStr = "the server's tick state is "; + } + return stateStr + state.toString().toLowerCase(Locale.ENGLISH); + } + +} diff --git a/src/main/java/ch/njol/skript/effects/EffFreezeServer.java b/src/main/java/ch/njol/skript/effects/EffFreezeServer.java new file mode 100644 index 00000000000..8713707b870 --- /dev/null +++ b/src/main/java/ch/njol/skript/effects/EffFreezeServer.java @@ -0,0 +1,52 @@ +package ch.njol.skript.effects; + +import ch.njol.skript.Skript; +import ch.njol.skript.bukkitutil.ServerUtils; +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.util.Kleenean; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; + +@Name("Freeze/Unfreeze Server") +@Description("Freezes or unfreezes the server.") +@Examples({ + "freeze server", + "unfreeze server" +}) +@Since("INSERT VERSION") +@RequiredPlugins("Minecraft 1.20.4+") +public class EffFreezeServer extends Effect { + + static { + if (ServerUtils.isServerTickManagerPresent()) + Skript.registerEffect(EffFreezeServer.class, + "freeze [the] server", + "unfreeze [the] server"); + } + + private boolean freeze; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + freeze = matchedPattern == 0; + return true; + } + + @Override + protected void execute(Event event) { + ServerUtils.getServerTickManager().setFrozen(freeze); + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return freeze ? "freeze server" : "unfreeze server"; + } + +} diff --git a/src/main/java/ch/njol/skript/effects/EffSprintServer.java b/src/main/java/ch/njol/skript/effects/EffSprintServer.java new file mode 100644 index 00000000000..2a3a95164db --- /dev/null +++ b/src/main/java/ch/njol/skript/effects/EffSprintServer.java @@ -0,0 +1,65 @@ +package ch.njol.skript.effects; + +import ch.njol.skript.Skript; +import ch.njol.skript.bukkitutil.ServerUtils; +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.Timespan; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; + +@Name("Sprint Server") +@Description({ + "Makes the server sprint for a certain amount of time, or stops the server from sprinting.", + "Sprinting is where the server increases the tick rate depending on the time you input, and resets the tick rate to what it was after the server has finished sprinting." +}) +@Examples({ + "request server to sprint for 10 seconds", + "make server stop sprinting" +}) +@Since("INSERT VERSION") +@RequiredPlugins("Minecraft 1.20.4+") +public class EffSprintServer extends Effect { + + static { + if (ServerUtils.isServerTickManagerPresent()) + Skript.registerEffect(EffSprintServer.class, + "make [the] server sprint for %timespan%", + "make [the] server stop sprinting"); + } + + private Expression timespan; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (matchedPattern == 0) + timespan = (Expression) exprs[0]; + return true; + } + + @Override + protected void execute(Event event) { + if (timespan != null) { + long sprintTicks = timespan.getOptionalSingle(event).map(Timespan::getTicks).orElse(1L); + ServerUtils.getServerTickManager().requestGameToSprint((int) sprintTicks); + } else { + ServerUtils.getServerTickManager().stopSprinting(); + } + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + if (timespan != null) + return "make the server sprint for " + timespan.toString(event, debug); + return "make the server stop sprinting"; + } + +} diff --git a/src/main/java/ch/njol/skript/effects/EffStepServer.java b/src/main/java/ch/njol/skript/effects/EffStepServer.java new file mode 100644 index 00000000000..df5cd8f641e --- /dev/null +++ b/src/main/java/ch/njol/skript/effects/EffStepServer.java @@ -0,0 +1,66 @@ +package ch.njol.skript.effects; + +import ch.njol.skript.Skript; +import ch.njol.skript.bukkitutil.ServerUtils; +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.Timespan; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; + +@Name("Step Server") +@Description({ + "Makes the server \"step\" for a certain amount of time", + "The server can only step when its ticking state is frozen.", + "When stepping, the server goes forward that amount of time in ticks." +}) +@Examples({ + "make server step for 5 seconds", + "make server stop stepping" +}) +@Since("INSERT VERSION") +@RequiredPlugins("Minecraft 1.20.4+") +public class EffStepServer extends Effect { + + static { + if (ServerUtils.isServerTickManagerPresent()) + Skript.registerEffect(EffStepServer.class, + "make [the] server step for %timespan%", + "make [the] server stop stepping"); + } + + private Expression timespan; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (matchedPattern == 0) + timespan = (Expression) exprs[0]; + return true; + } + + @Override + protected void execute(Event event) { + if (timespan != null) { + Timespan timespan = this.timespan.getSingle(event); + if (timespan != null) + ServerUtils.getServerTickManager().stepGameIfFrozen((int) timespan.getAs(Timespan.TimePeriod.TICKS)); + } else { + ServerUtils.getServerTickManager().stopStepping(); + } + } + + + @Override + public String toString(@Nullable Event event, boolean debug) { + return timespan == null ? "make the server stop stepping" : "make the server step for " + timespan.toString(event, debug); + } + +} diff --git a/src/main/java/ch/njol/skript/expressions/ExprFrozenTicksToRun.java b/src/main/java/ch/njol/skript/expressions/ExprFrozenTicksToRun.java new file mode 100644 index 00000000000..cee4884d2c2 --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprFrozenTicksToRun.java @@ -0,0 +1,59 @@ +package ch.njol.skript.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.bukkitutil.ServerUtils; +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; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; + +@Name("Frozen Ticks to Run") +@Description("Gets the amount of ticks that are in queue to run while the server's tick state is frozen.") +@Examples("broadcast \"There are %frozen ticks to run% frozen ticks left!\"") +@Since("INSERT VERSION") +@RequiredPlugins("Minecraft 1.20.4+") +public class ExprFrozenTicksToRun extends SimpleExpression { + + static { + if (ServerUtils.isServerTickManagerPresent()) + Skript.registerExpression(ExprFrozenTicksToRun.class, Integer.class, ExpressionType.SIMPLE, "[the] [amount of] frozen ticks [left] to run"); + } + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + return true; + } + + @Override + protected Integer @Nullable [] get(Event event) { + int frozenTicks = ServerUtils.getServerTickManager().getFrozenTicksToRun(); + if (frozenTicks > 0) { + return new Integer[]{frozenTicks}; + } + return new Integer[0]; + } + + @Override + public boolean isSingle() { + return true; + } + + @Override + public Class getReturnType() { + return Integer.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "the frozen ticks left to run"; + } + +} diff --git a/src/main/java/ch/njol/skript/expressions/ExprServerTickRate.java b/src/main/java/ch/njol/skript/expressions/ExprServerTickRate.java new file mode 100644 index 00000000000..de426fdc333 --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprServerTickRate.java @@ -0,0 +1,100 @@ +package ch.njol.skript.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.bukkitutil.ServerUtils; +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.util.Kleenean; +import ch.njol.util.Math2; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; + +@Name("Server Tick Rate") +@Description({ + "Gets or sets the current tick rate of the server. The tick rate is the number of game ticks that occur in a second. Higher values mean the game runs faster.", + "The server's default tick rate is 20." +}) +@Examples({ + "send \"%server's tick rate%\" to player", + "set server's tick rate to 20 # This is the default tick rate.", + "add 5 to server's tick rate", + "remove 2 from server's tick rate" +}) +@Since("INSERT VERSION") +@RequiredPlugins("Minecraft 1.20.4+") +public class ExprServerTickRate extends SimpleExpression { + + static { + if (ServerUtils.isServerTickManagerPresent()) + Skript.registerExpression(ExprServerTickRate.class, Float.class, ExpressionType.SIMPLE, "[the] server['s] tick rate"); + } + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + return true; + } + + @Nullable + @Override + protected Float[] get(Event event) { + return new Float[] {ServerUtils.getServerTickManager().getTickRate()}; + } + + public Class[] acceptChange(ChangeMode mode) { + switch (mode) { + case SET, ADD, REMOVE, RESET -> return CollectionUtils.array(Number.class); + default -> return null; + } + } + + @Override + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { + float tickRate = ServerUtils.getServerTickManager().getTickRate(); + float change = delta != null ? ((Number) delta[0]).floatValue() : 0; + float newTickRate = tickRate; + + switch (mode) { + case SET: + newTickRate = change; + break; + case ADD: + newTickRate = tickRate + change; + break; + case REMOVE: + newTickRate = tickRate - change; + break; + case RESET: + newTickRate = 20; + break; + } + + newTickRate = Math2.fit(newTickRate, 1.0f, 10000f); + + ServerUtils.getServerTickManager().setTickRate(newTickRate); + } + + @Override + public boolean isSingle() { + return true; + } + + @Override + public Class getReturnType() { + return Float.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "the server's tick rate"; + } + +} diff --git a/src/test/skript/tests/syntaxes/conditions/CondServerTickState.sk b/src/test/skript/tests/syntaxes/conditions/CondServerTickState.sk new file mode 100644 index 00000000000..4a34b54bb8a --- /dev/null +++ b/src/test/skript/tests/syntaxes/conditions/CondServerTickState.sk @@ -0,0 +1,26 @@ +test "Server Tick State Expressions" when running minecraft "1.20.4": + # Test if server is normal + assert server's tick state is normal with "Server tick state is not normal" + + freeze the server + assert server's tick state is frozen with "Server tick state is not frozen" + + # Step the server and test + make the server step for 5 seconds + assert server's tick state is stepping with "Server tick state is not stepping" + make the server stop stepping + unfreeze the server + + # Sprint the server and test + make the server sprint for 10 seconds + assert server's tick state is sprinting with "Server tick state is not sprinting" + make the server stop sprinting + +test "Entity Tick Frozen Expression" when running minecraft "1.20.4": + spawn a cow at spawn of world "world" + freeze the server + wait 1 tick + assert last spawned cow is tick frozen with "Entity is not tick frozen" + unfreeze the server + assert last spawned cow is not tick frozen with "Entity is still tick frozen after unfreezing" + diff --git a/src/test/skript/tests/syntaxes/effects/EffServerTick.sk b/src/test/skript/tests/syntaxes/effects/EffServerTick.sk new file mode 100644 index 00000000000..33c8af2c9e7 --- /dev/null +++ b/src/test/skript/tests/syntaxes/effects/EffServerTick.sk @@ -0,0 +1,51 @@ +test "Server Sprinting Effect" when running minecraft "1.20.4": + make the server sprint for 10 seconds + assert server's tick state is sprinting with "Server did not start sprinting" + + make the server stop sprinting + assert server's tick state is normal with "Server did not stop sprinting" + +test "Server Stepping Effect" when running minecraft "1.20.4": + make the server step for 5 seconds + assert server's tick state is stepping with "Server did not start stepping" + + make the server stop stepping + assert server's tick state is normal with "Server did not stop stepping" + +test "Server Freezing Effect" when running minecraft "1.20.4": + freeze the server + assert server's tick state is frozen with "Server did not freeze" + + unfreeze the server + assert server's tick state is normal with "Server did not unfreeze" + +test "Server Tick Rate Expression" when running minecraft "1.20.4": + # Ensure default tick rate is 20 + set {_tickRate} to server's tick rate + assert {_tickRate} is 20 with "Default server tick rate is not 20" + + set server's tick rate to 25 + set {_tickRate} to server's tick rate + assert {_tickRate} is 25 with "Server tick rate was not set to 25" + + add 5 to server's tick rate + set {_tickRate} to server's tick rate + assert {_tickRate} is 30 with "Server tick rate was not increased to 30" + + remove 10 from server's tick rate + set {_tickRate} to server's tick rate + assert {_tickRate} is 20 with "Server tick rate was not decreased to 20" + + set server's tick rate to -5 + set {_tickRate} to server's tick rate + assert {_tickRate} is 1 with "Setting negative tick rate did not clamp." + + add -3 to server's tick rate + set {_tickRate} to server's tick rate + assert {_tickRate} is 1 with "Adding a negative value clamped the tick rate." + + # Reset the server tick rate to default and verify + reset server's tick rate + set {_tickRate} to server's tick rate + assert {_tickRate} is 20 with "Server tick rate was not reset to 20" +