Skip to content

Commit 1e43779

Browse files
Phill310sovdeethEfniliteAbsolutionismModerocky
authored
Add worldborders (#7006)
* Initial setup * Fix imports * Use pattern variables for instanceof * Cleaner switch statements * Specify paper version * Use newer timespan method * Require "world border" when editing a world border's attributes * Move IsInsideWorldBorder to IsWithin condition * Make variable name readable * Clean up conditions * Make radius/diameter optional * Make damage have a minimum of 0 * Make warning distance have a minimum of 0 * Make damage buffer have a minimum of 0 * Make warning time have a minimum of 0 * Force the center location to stay inside the limits defined by WorldBorder#getMaxCenterCoordinate * Tests! * End files with a new line * Apply suggestions from code review Co-authored-by: sovdee <[email protected]> * Correct annotation placement * Apply suggestions from code review Co-authored-by: Efnilite <[email protected]> Co-authored-by: SirSmurfy2 <[email protected]> * Phil's stuff. * More of Phil's stuff. * Whoops I imported again * Apply suggestions from code review Co-authored-by: SirSmurfy2 <[email protected]> * register properties as defaults * Add docs * Clean up toString * Update tests * Apply suggestions from code review Co-authored-by: Efnilite <[email protected]> * Fix imports * Remove duplicate event registration * Update src/main/java/ch/njol/skript/expressions/ExprSecCreateWorldBorder.java Co-authored-by: SirSmurfy2 <[email protected]> * Fix typo * Validate inputs before setting the value * Update src/main/java/ch/njol/skript/effects/EffWorldBorderExpand.java Co-authored-by: sovdee <[email protected]> * Paper's getMaxSize method did not do what I expected * Improve tests * Add check for NaN * Add check for NaN and infinite values * Add check for NaN and overflow during integer math * Apply suggestions from code review Co-authored-by: SirSmurfy2 <[email protected]> * Move clamp outside loop * Remove extra event-value registration * Improve example for World Border Bounds Change Event * Clean up imports * Set the Warning Distance to 0 rather than MIN_VALUE * Add more tests with large numbers * Account for integer overflow issues * Remove debug code * Use signum to clean up conditions --------- Co-authored-by: sovdee <[email protected]> Co-authored-by: Efnilite <[email protected]> Co-authored-by: SirSmurfy2 <[email protected]> Co-authored-by: Moderocky <[email protected]>
1 parent c0f6ba4 commit 1e43779

23 files changed

+1320
-6
lines changed

src/main/java/ch/njol/skript/classes/data/BukkitClasses.java

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
import ch.njol.skript.util.PotionEffectUtils;
2323
import ch.njol.skript.util.StringMode;
2424
import ch.njol.skript.util.Utils;
25-
import ch.njol.util.StringUtils;
2625
import ch.njol.yggdrasil.Fields;
2726
import io.papermc.paper.world.MoonPhase;
2827
import org.bukkit.*;
@@ -1477,6 +1476,31 @@ public String toVariableNameString(EntitySnapshot snapshot) {
14771476
);
14781477
}
14791478

1479+
Classes.registerClass(new ClassInfo<>(WorldBorder.class, "worldborder")
1480+
.user("world ?borders?")
1481+
.name("World Border")
1482+
.description("Represents the border of a world or player.")
1483+
.since("INSERT VERSION")
1484+
.parser(new Parser<WorldBorder>() {
1485+
@Override
1486+
public boolean canParse(ParseContext context) {
1487+
return false;
1488+
}
1489+
1490+
@Override
1491+
public String toString(WorldBorder border, int flags) {
1492+
if (border.getWorld() == null)
1493+
return "virtual world border";
1494+
return "world border of world named '" + border.getWorld().getName() + "'";
1495+
}
1496+
1497+
@Override
1498+
public String toVariableNameString(WorldBorder border) {
1499+
return toString(border, 0);
1500+
}
1501+
})
1502+
.defaultExpression(new EventValueExpression<>(WorldBorder.class)));
1503+
14801504
Classes.registerClass(new ClassInfo<>(org.bukkit.block.banner.Pattern.class, "bannerpattern")
14811505
.user("banner ?patterns?")
14821506
.name("Banner Pattern")

src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@
2121
import com.destroystokyo.paper.event.player.PlayerElytraBoostEvent;
2222
import io.papermc.paper.event.entity.EntityMoveEvent;
2323
import io.papermc.paper.event.player.*;
24+
import io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent;
25+
import io.papermc.paper.event.world.border.WorldBorderBoundsChangeFinishEvent;
26+
import io.papermc.paper.event.world.border.WorldBorderCenterChangeEvent;
27+
import io.papermc.paper.event.world.border.WorldBorderEvent;
2428
import org.bukkit.*;
2529
import org.bukkit.block.Block;
2630
import org.bukkit.block.BlockFace;
@@ -746,6 +750,25 @@ else if (hand == EquipmentSlot.OFF_HAND)
746750
EventValues.registerEventValue(PlayerElytraBoostEvent.class, Entity.class, PlayerElytraBoostEvent::getFirework);
747751
}
748752

753+
// === WorldBorderEvents ===
754+
if (Skript.classExists("io.papermc.paper.event.world.border.WorldBorderEvent")) {
755+
// WorldBorderEvent
756+
EventValues.registerEventValue(WorldBorderEvent.class, WorldBorder.class, WorldBorderEvent::getWorldBorder);
757+
758+
// WorldBorderBoundsChangeEvent
759+
EventValues.registerEventValue(WorldBorderBoundsChangeEvent.class, Number.class, WorldBorderBoundsChangeEvent::getNewSize);
760+
EventValues.registerEventValue(WorldBorderBoundsChangeEvent.class, Number.class, WorldBorderBoundsChangeEvent::getOldSize, EventValues.TIME_PAST);
761+
EventValues.registerEventValue(WorldBorderBoundsChangeEvent.class, Timespan.class, event -> new Timespan(event.getDuration()));
762+
763+
// WorldBorderBoundsChangeFinishEvent
764+
EventValues.registerEventValue(WorldBorderBoundsChangeFinishEvent.class, Number.class, WorldBorderBoundsChangeFinishEvent::getNewSize);
765+
EventValues.registerEventValue(WorldBorderBoundsChangeFinishEvent.class, Number.class, WorldBorderBoundsChangeFinishEvent::getOldSize, EventValues.TIME_PAST);
766+
EventValues.registerEventValue(WorldBorderBoundsChangeFinishEvent.class, Timespan.class, event -> new Timespan((long) event.getDuration()));
767+
768+
// WorldBorderCenterChangeEvent
769+
EventValues.registerEventValue(WorldBorderCenterChangeEvent.class, Location.class, WorldBorderCenterChangeEvent::getNewCenter);
770+
EventValues.registerEventValue(WorldBorderCenterChangeEvent.class, Location.class, WorldBorderCenterChangeEvent::getOldCenter, EventValues.TIME_PAST);
771+
}
749772
}
750773

751774
}

src/main/java/ch/njol/skript/conditions/CondIsWithin.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import org.bukkit.Chunk;
1717
import org.bukkit.Location;
1818
import org.bukkit.World;
19+
import org.bukkit.WorldBorder;
1920
import org.bukkit.block.Block;
2021
import org.bukkit.entity.Entity;
2122
import org.bukkit.event.Event;
@@ -47,12 +48,12 @@
4748
"if player is in world \"world\" and chunk at location(0, 0, 0):",
4849
"\tgive player 1 diamond"
4950
})
50-
@Since("2.7, INSERT VERSION (multiple)")
51+
@Since("2.7, INSERT VERSION (world borders)")
5152
@RequiredPlugins("MC 1.17+ (within block)")
5253
public class CondIsWithin extends Condition {
5354

5455
static {
55-
String validTypes = "entities/chunks/worlds";
56+
String validTypes = "entities/chunks/worlds/worldborders";
5657
if (Skript.methodExists(Block.class, "getCollisionShape"))
5758
validTypes += "/blocks";
5859

@@ -79,7 +80,7 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye
7980
loc1 = (Expression<Location>) exprs[1];
8081
loc2 = (Expression<Location>) exprs[2];
8182
} else {
82-
// within an entity/block/chunk/world
83+
// within an entity/block/chunk/world/worldborder
8384
withinLocations = false;
8485
area = exprs[1];
8586
}
@@ -118,7 +119,9 @@ public boolean check(Event event) {
118119
return location.getChunk().equals(chunk);
119120
} else if (object instanceof World world) {
120121
return location.getWorld().equals(world);
121-
}
122+
} else if (object instanceof WorldBorder worldBorder) {
123+
return worldBorder.isInside(location);
124+
}
122125
return false;
123126
}, false, area.getAnd()),
124127
isNegated());
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package ch.njol.skript.effects;
2+
3+
import ch.njol.skript.Skript;
4+
import ch.njol.skript.config.Node;
5+
import ch.njol.skript.doc.Description;
6+
import ch.njol.skript.doc.Examples;
7+
import ch.njol.skript.doc.Name;
8+
import ch.njol.skript.doc.Since;
9+
import ch.njol.skript.lang.Effect;
10+
import ch.njol.skript.lang.Expression;
11+
import ch.njol.skript.lang.SkriptParser.ParseResult;
12+
import ch.njol.skript.lang.SyntaxStringBuilder;
13+
import ch.njol.skript.util.Timespan;
14+
import ch.njol.skript.util.Timespan.TimePeriod;
15+
import ch.njol.util.Kleenean;
16+
import ch.njol.util.Math2;
17+
import org.bukkit.WorldBorder;
18+
import org.bukkit.event.Event;
19+
import org.jetbrains.annotations.Nullable;
20+
import org.skriptlang.skript.log.runtime.SyntaxRuntimeErrorProducer;
21+
22+
@Name("Expand/Shrink World Border")
23+
@Description({
24+
"Expand or shrink the size of a world border.",
25+
"Using `by` adds/subtracts from the current size of the world border.",
26+
"Using `to` sets to the specified size."
27+
})
28+
@Examples({
29+
"expand world border of player by 100 in 5 seconds",
30+
"shrink world border of world \"world\" to 100 in 10 seconds"
31+
})
32+
@Since("INSERT VERSION")
33+
public class EffWorldBorderExpand extends Effect implements SyntaxRuntimeErrorProducer {
34+
35+
static {
36+
Skript.registerEffect(EffWorldBorderExpand.class,
37+
"(expand|grow) [[the] (diameter|:radius) of] %worldborders% (by|:to) %number% [over [a period of] %-timespan%]",
38+
"(expand|grow) %worldborders%['s (diameter|:radius)] (by|:to) %number% [over [a period of] %-timespan%]",
39+
"(contract|shrink) [[the] (diameter|:radius) of] %worldborders% (by|:to) %number% [over [a period of] %-timespan%]",
40+
"(contract|shrink) %worldborders%['s (diameter|:radius)] (by|:to) %number% [over [a period of] %-timespan%]"
41+
);
42+
}
43+
44+
private boolean shrink;
45+
private boolean radius;
46+
private boolean to;
47+
private Expression<WorldBorder> worldBorders;
48+
private Expression<Number> numberExpr;
49+
private @Nullable Expression<Timespan> timespan;
50+
private static final double MAX_WORLDBORDER_SIZE = 59999968;
51+
private Node node;
52+
53+
@Override
54+
@SuppressWarnings("unchecked")
55+
public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) {
56+
worldBorders = (Expression<WorldBorder>) exprs[0];
57+
numberExpr = (Expression<Number>) exprs[1];
58+
timespan = (Expression<Timespan>) exprs[2];
59+
shrink = matchedPattern > 1;
60+
radius = parseResult.hasTag("radius");
61+
to = parseResult.hasTag("to");
62+
node = getParser().getNode();
63+
return true;
64+
}
65+
66+
@Override
67+
protected void execute(Event event) {
68+
Number number = numberExpr.getSingle(event);
69+
if (number == null)
70+
return;
71+
double input = number.doubleValue();
72+
if (Double.isNaN(input)) {
73+
error("You can't " + (shrink ? "shrink" : "grow") + " a world border " + (to ? "to" : "by") + " NaN.");
74+
return;
75+
}
76+
if (radius)
77+
input *= 2;
78+
long speed = 0;
79+
if (timespan != null) {
80+
Timespan timespan = this.timespan.getSingle(event);
81+
if (timespan != null)
82+
speed = timespan.getAs(TimePeriod.SECOND);
83+
}
84+
WorldBorder[] worldBorders = this.worldBorders.getArray(event);
85+
if (to) {
86+
input = Math2.fit(1, input, MAX_WORLDBORDER_SIZE);
87+
for (WorldBorder worldBorder : worldBorders)
88+
worldBorder.setSize(input, speed);
89+
} else {
90+
if (shrink)
91+
input = -input;
92+
for (WorldBorder worldBorder : worldBorders) {
93+
double size = worldBorder.getSize();
94+
size = Math2.fit(1, size + input, MAX_WORLDBORDER_SIZE);
95+
worldBorder.setSize(size, speed);
96+
}
97+
}
98+
}
99+
100+
@Override
101+
public Node getNode() {
102+
return node;
103+
}
104+
105+
@Override
106+
public String toString(@Nullable Event event, boolean debug) {
107+
SyntaxStringBuilder builder = new SyntaxStringBuilder(event, debug);
108+
builder.append(shrink ? "shrink" : "expand");
109+
builder.append(radius ? "radius" : "diameter");
110+
builder.append("of", worldBorders);
111+
builder.append(to ? "to" : "by");
112+
builder.append(numberExpr);
113+
if (timespan != null)
114+
builder.append("over", timespan);
115+
return builder.toString();
116+
}
117+
118+
}

src/main/java/ch/njol/skript/events/SimpleEvents.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
import io.papermc.paper.event.player.PlayerInventorySlotChangeEvent;
1616
import io.papermc.paper.event.player.PlayerStopUsingItemEvent;
1717
import io.papermc.paper.event.player.PlayerTradeEvent;
18+
import io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent;
19+
import io.papermc.paper.event.world.border.WorldBorderBoundsChangeFinishEvent;
20+
import io.papermc.paper.event.world.border.WorldBorderCenterChangeEvent;
1821
import org.bukkit.event.Event;
1922
import org.bukkit.event.block.*;
2023
import org.bukkit.event.enchantment.EnchantItemEvent;
@@ -763,6 +766,44 @@ public class SimpleEvents {
763766
.since("2.10");
764767
}
765768

769+
// WorldBorder Events
770+
if (Skript.classExists("io.papermc.paper.event.world.border.WorldBorderEvent")) {
771+
Skript.registerEvent("World Border Bounds Change", SimpleEvent.class, WorldBorderBoundsChangeEvent.class, "world[ ]border [bounds] chang(e|ing)")
772+
.description(
773+
"Called when a world border changes its bounds, either over time, or instantly.",
774+
"This event does not get called for virtual borders."
775+
)
776+
.requiredPlugins("Paper 1.16+")
777+
.examples(
778+
"on worldborder bounds change:",
779+
"\tbroadcast \"The diameter of %event-worldborder% is changing from %past event-number% to %event-number% over the next %event-timespan%\""
780+
)
781+
.since("INSERT VERSION");
782+
783+
Skript.registerEvent("World Border Bounds Finish Change", SimpleEvent.class, WorldBorderBoundsChangeFinishEvent.class, "world[ ]border [bounds] finish chang(e|ing)")
784+
.description(
785+
"Called when a moving world border has finished its move.",
786+
"This event does not get called for virtual borders."
787+
)
788+
.requiredPlugins("Paper 1.16+")
789+
.examples(
790+
"on worldborder bounds finish change:",
791+
"\tbroadcast \"Over the past %event-timespan%, the diameter of %event-worldborder% went from %past event-number% to %event-number%\""
792+
)
793+
.since("INSERT VERSION");
794+
795+
Skript.registerEvent("World Border Center Change", SimpleEvent.class, WorldBorderCenterChangeEvent.class, "world[ ]border center chang(e|ing)")
796+
.description(
797+
"Called when a world border's center has changed.",
798+
"This event does not get called for virtual borders."
799+
)
800+
.requiredPlugins("Paper 1.16+")
801+
.examples(
802+
"on worldborder center change:",
803+
"\tbroadcast \"The center of %event-worldborder% has moved from %past event-location% to %event-location%\""
804+
)
805+
.since("INSERT VERSION");
806+
}
766807
}
767808

768809
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package ch.njol.skript.expressions;
2+
3+
import ch.njol.skript.Skript;
4+
import ch.njol.skript.config.SectionNode;
5+
import ch.njol.skript.doc.Description;
6+
import ch.njol.skript.doc.Examples;
7+
import ch.njol.skript.doc.Name;
8+
import ch.njol.skript.doc.Since;
9+
import ch.njol.skript.expressions.base.SectionExpression;
10+
import ch.njol.skript.lang.*;
11+
import ch.njol.skript.lang.SkriptParser.ParseResult;
12+
import ch.njol.skript.registrations.EventValues;
13+
import ch.njol.skript.variables.Variables;
14+
import ch.njol.util.Kleenean;
15+
import org.bukkit.Bukkit;
16+
import org.bukkit.WorldBorder;
17+
import org.bukkit.event.Event;
18+
import org.bukkit.event.HandlerList;
19+
import org.jetbrains.annotations.NotNull;
20+
import org.jetbrains.annotations.Nullable;
21+
22+
import java.util.List;
23+
import java.util.concurrent.atomic.AtomicBoolean;
24+
25+
@Name("Create WorldBorder")
26+
@Description("Create a virtual worldborder. You can make this a real border by setting a world's worldborder to a virtual border.")
27+
@Examples({
28+
"set player's worldborder to a virtual worldborder",
29+
"",
30+
"on join:",
31+
"\tset {_border} to a worldborder:",
32+
"\t\tset worldborder radius to 25",
33+
"\t\tset worldborder warning distance of event-worldborder to 5",
34+
"\tset worldborder of player to {_border}"
35+
})
36+
@Since("INSERT VERSION")
37+
public class ExprSecCreateWorldBorder extends SectionExpression<WorldBorder> {
38+
39+
static {
40+
Skript.registerExpression(ExprSecCreateWorldBorder.class, WorldBorder.class, ExpressionType.SIMPLE, "a [virtual] world[ ]border");
41+
EventValues.registerEventValue(CreateWorldborderEvent.class, WorldBorder.class, CreateWorldborderEvent::getWorldBorder);
42+
}
43+
44+
private WorldBorder worldBorder;
45+
private Trigger trigger = null;
46+
47+
@Override
48+
public boolean init(Expression[] expressions, int pattern, Kleenean delayed, ParseResult result, @Nullable SectionNode node, @Nullable List list) {
49+
worldBorder = Bukkit.createWorldBorder();
50+
if (node != null) {
51+
AtomicBoolean isDelayed = new AtomicBoolean(false);
52+
Runnable afterLoading = () -> isDelayed.set(!getParser().getHasDelayBefore().isFalse());
53+
trigger = loadCode(node, "create worldborder", afterLoading, CreateWorldborderEvent.class);
54+
if (isDelayed.get()) {
55+
Skript.error("Delays cannot be used within a 'create worldborder' section.");
56+
return false;
57+
}
58+
}
59+
return true;
60+
}
61+
62+
@Override
63+
protected WorldBorder @Nullable [] get(Event event) {
64+
if (trigger == null)
65+
return new WorldBorder[] {worldBorder};
66+
CreateWorldborderEvent worldborderEvent = new CreateWorldborderEvent(worldBorder);
67+
Variables.withLocalVariables(event, worldborderEvent, () -> TriggerItem.walk(trigger, worldborderEvent));
68+
return new WorldBorder[] {worldborderEvent.getWorldBorder()};
69+
}
70+
71+
@Override
72+
public boolean isSingle() {
73+
return true;
74+
}
75+
76+
@Override
77+
public Class<WorldBorder> getReturnType() {
78+
return WorldBorder.class;
79+
}
80+
81+
@Override
82+
public String toString(@Nullable Event event, boolean debug) {
83+
return "a worldborder";
84+
}
85+
86+
public static class CreateWorldborderEvent extends Event {
87+
private final WorldBorder worldborder;
88+
89+
public CreateWorldborderEvent(WorldBorder worldborder) {
90+
this.worldborder = worldborder;
91+
}
92+
93+
public WorldBorder getWorldBorder() {
94+
return worldborder;
95+
}
96+
97+
@Override
98+
public @NotNull HandlerList getHandlers() {
99+
throw new IllegalStateException();
100+
}
101+
102+
}
103+
104+
}

0 commit comments

Comments
 (0)