diff --git a/Spigot-API-Patches/Add-worldborder-events.patch b/Spigot-API-Patches/Add-worldborder-events.patch
new file mode 100644
index 0000000000..d657ce7283
--- /dev/null
+++ b/Spigot-API-Patches/Add-worldborder-events.patch
@@ -0,0 +1,308 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <jake.m.potrebic@gmail.com>
+Date: Mon, 4 Jan 2021 22:40:26 -0800
+Subject: [PATCH] Add worldborder events
+
+
+diff --git a/src/main/java/io/papermc/paper/event/world/border/WorldBorderBoundsChangeEvent.java b/src/main/java/io/papermc/paper/event/world/border/WorldBorderBoundsChangeEvent.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/event/world/border/WorldBorderBoundsChangeEvent.java
+@@ -0,0 +0,0 @@
++package io.papermc.paper.event.world.border;
++
++import org.bukkit.World;
++import org.bukkit.WorldBorder;
++import org.bukkit.event.Cancellable;
++import org.bukkit.event.HandlerList;
++import org.jetbrains.annotations.NotNull;
++
++/**
++ * Called when a world border changes its bounds, either over time, or instantly.
++ */
++public class WorldBorderBoundsChangeEvent extends WorldBorderEvent implements Cancellable {
++
++    private static final HandlerList HANDLER_LIST = new HandlerList();
++
++    private Type type;
++    private final double oldSize;
++    private double newSize;
++    private long duration;
++    private boolean cancelled;
++
++    public WorldBorderBoundsChangeEvent(@NotNull World world, @NotNull WorldBorder worldBorder, @NotNull Type type, double oldSize, double newSize, long duration) {
++        super(world, worldBorder);
++        this.type = type;
++        this.oldSize = oldSize;
++        this.newSize = newSize;
++        this.duration = duration;
++    }
++
++    /**
++     * Gets if this change is an instant change or over-time change.
++     *
++     * @return the change type
++     */
++    @NotNull
++    public Type getType() {
++        return type;
++    }
++
++    /**
++     * Gets the old size or the world border.
++     *
++     * @return the old size
++     */
++    public double getOldSize() {
++        return oldSize;
++    }
++
++    /**
++     * Gets the new size of the world border.
++     *
++     * @return the new size
++     */
++    public double getNewSize() {
++        return newSize;
++    }
++
++    /**
++     * Sets the new size of the world border.
++     *
++     * @param newSize the new size
++     */
++    public void setNewSize(double newSize) {
++        // PAIL: TODO: Magic Values
++        this.newSize = Math.min(6.0E7D, Math.max(1.0D, newSize));
++    }
++
++    /**
++     * Gets the time in milliseconds for the change. Will be 0 if instant.
++     *
++     * @return the time in milliseconds for the change
++     */
++    public long getDuration() {
++        return duration;
++    }
++
++    /**
++     * Sets the time in milliseconds for the change. Will change {@link #getType()} to return
++     * {@link Type#STARTED_MOVE}.
++     *
++     * @param duration the time in milliseconds for the change
++     */
++    public void setDuration(long duration) {
++        // PAIL: TODO: Magic Values
++        this.duration = Math.min(9223372036854775L, Math.max(0L, duration));
++        if (duration >= 0 && type == Type.INSTANT_MOVE) type = Type.STARTED_MOVE;
++    }
++
++    @Override
++    public boolean isCancelled() {
++        return cancelled;
++    }
++
++    @Override
++    public void setCancelled(boolean cancel) {
++        this.cancelled = cancel;
++    }
++
++    @NotNull
++    @Override
++    public HandlerList getHandlers() {
++        return HANDLER_LIST;
++    }
++
++    @NotNull
++    public static HandlerList getHandlerList() {
++        return HANDLER_LIST;
++    }
++
++    public enum Type {
++        STARTED_MOVE,
++        INSTANT_MOVE
++    }
++}
+diff --git a/src/main/java/io/papermc/paper/event/world/border/WorldBorderBoundsChangeFinishEvent.java b/src/main/java/io/papermc/paper/event/world/border/WorldBorderBoundsChangeFinishEvent.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/event/world/border/WorldBorderBoundsChangeFinishEvent.java
+@@ -0,0 +0,0 @@
++package io.papermc.paper.event.world.border;
++
++import org.bukkit.World;
++import org.bukkit.WorldBorder;
++import org.bukkit.event.HandlerList;
++import org.jetbrains.annotations.NotNull;
++
++/**
++ * Called when a moving world border has finished it's move.
++ */
++public class WorldBorderBoundsChangeFinishEvent extends WorldBorderEvent {
++
++    private static final HandlerList HANDLER_LIST = new HandlerList();
++
++    private final double oldSize;
++    private final double newSize;
++    private final double duration;
++
++    public WorldBorderBoundsChangeFinishEvent(@NotNull World world, @NotNull WorldBorder worldBorder, double oldSize, double newSize, double duration) {
++        super(world, worldBorder);
++        this.oldSize = oldSize;
++        this.newSize = newSize;
++        this.duration = duration;
++    }
++
++    /**
++     * Gets the old size of the worldborder.
++     *
++     * @return the old size
++     */
++    public double getOldSize() {
++        return oldSize;
++    }
++
++    /**
++     * Gets the new size of the worldborder.
++     *
++     * @return the new size
++     */
++    public double getNewSize() {
++        return newSize;
++    }
++
++    /**
++     * Gets the duration this worldborder took to make the change.
++     * <p>
++     * Can be 0 if handlers for {@link io.papermc.paper.event.world.border.WorldBorderCenterChangeEvent} set the duration to 0.
++     *
++     * @return the duration of the transition
++     */
++    public double getDuration() {
++        return duration;
++    }
++
++    @NotNull
++    @Override
++    public HandlerList getHandlers() {
++        return HANDLER_LIST;
++    }
++
++    @NotNull
++    public static HandlerList getHandlerList() {
++        return HANDLER_LIST;
++    }
++}
+diff --git a/src/main/java/io/papermc/paper/event/world/border/WorldBorderCenterChangeEvent.java b/src/main/java/io/papermc/paper/event/world/border/WorldBorderCenterChangeEvent.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/event/world/border/WorldBorderCenterChangeEvent.java
+@@ -0,0 +0,0 @@
++package io.papermc.paper.event.world.border;
++
++import org.bukkit.Location;
++import org.bukkit.World;
++import org.bukkit.WorldBorder;
++import org.bukkit.event.Cancellable;
++import org.bukkit.event.HandlerList;
++import org.bukkit.event.world.WorldEvent;
++import org.jetbrains.annotations.NotNull;
++
++/**
++ * Called when a world border's center is changed.
++ */
++public class WorldBorderCenterChangeEvent extends WorldBorderEvent implements Cancellable {
++
++    private static final HandlerList HANDLER_LIST = new HandlerList();
++
++    private final Location oldCenter;
++    private Location newCenter;
++    private boolean cancelled;
++
++    public WorldBorderCenterChangeEvent(@NotNull World world, @NotNull WorldBorder worldBorder, @NotNull Location oldCenter, @NotNull Location newCenter) {
++        super(world, worldBorder);
++        this.oldCenter = oldCenter;
++        this.newCenter = newCenter;
++    }
++
++    /**
++     * Gets the original center location of the world border.
++     *
++     * @return the old center
++     */
++    @NotNull
++    public Location getOldCenter() {
++        return oldCenter;
++    }
++
++    /**
++     * Gets the new center location for the world border.
++     *
++     * @return the new center
++     */
++    @NotNull
++    public Location getNewCenter() {
++        return newCenter;
++    }
++
++    /**
++     * Sets the new center location for the world border. Y coordinate is ignored.
++     *
++     * @param newCenter the new center
++     */
++    public void setNewCenter(@NotNull Location newCenter) {
++        this.newCenter = newCenter;
++    }
++
++    @Override
++    public boolean isCancelled() {
++        return cancelled;
++    }
++
++    @Override
++    public void setCancelled(boolean cancel) {
++        this.cancelled = cancel;
++    }
++
++    @NotNull
++    @Override
++    public HandlerList getHandlers() {
++        return HANDLER_LIST;
++    }
++
++    @NotNull
++    public static HandlerList getHandlerList() {
++        return HANDLER_LIST;
++    }
++}
+diff --git a/src/main/java/io/papermc/paper/event/world/border/WorldBorderEvent.java b/src/main/java/io/papermc/paper/event/world/border/WorldBorderEvent.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/event/world/border/WorldBorderEvent.java
+@@ -0,0 +0,0 @@
++package io.papermc.paper.event.world.border;
++
++import org.bukkit.World;
++import org.bukkit.WorldBorder;
++import org.bukkit.event.Cancellable;
++import org.bukkit.event.world.WorldEvent;
++import org.jetbrains.annotations.NotNull;
++
++public abstract class WorldBorderEvent extends WorldEvent {
++
++    private final WorldBorder worldBorder;
++
++    public WorldBorderEvent(@NotNull World world, @NotNull WorldBorder worldBorder) {
++        super(world);
++        this.worldBorder = worldBorder;
++    }
++
++    @NotNull
++    public WorldBorder getWorldBorder() {
++        return worldBorder;
++    }
++}
diff --git a/Spigot-Server-Patches/Add-worldborder-events.patch b/Spigot-Server-Patches/Add-worldborder-events.patch
new file mode 100644
index 0000000000..1e788a80f9
--- /dev/null
+++ b/Spigot-Server-Patches/Add-worldborder-events.patch
@@ -0,0 +1,122 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <jake.m.potrebic@gmail.com>
+Date: Mon, 4 Jan 2021 22:40:34 -0800
+Subject: [PATCH] Add worldborder events
+
+
+diff --git a/src/main/java/net/minecraft/server/WorldBorder.java b/src/main/java/net/minecraft/server/WorldBorder.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/net/minecraft/server/WorldBorder.java
++++ b/src/main/java/net/minecraft/server/WorldBorder.java
+@@ -0,0 +0,0 @@ import com.google.common.collect.Lists;
+ import com.mojang.serialization.DynamicLike;
+ import java.util.Iterator;
+ import java.util.List;
++import io.papermc.paper.event.world.border.WorldBorderBoundsChangeFinishEvent; // Paper
++import io.papermc.paper.event.world.border.WorldBorderCenterChangeEvent; // Paper
++import io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent; // Paper
+ 
+ public class WorldBorder {
+ 
+@@ -0,0 +0,0 @@ public class WorldBorder {
+     }
+ 
+     public void setCenter(double d0, double d1) {
+-        this.g = d0;
+-        this.h = d1;
++        // Paper start
++        WorldBorderCenterChangeEvent event = new WorldBorderCenterChangeEvent(world.getWorld(), world.getWorld().getWorldBorder(), new org.bukkit.Location(world.getWorld(), this.getCenterX(), 0, this.getCenterZ()), new org.bukkit.Location(world.getWorld(), d0, 0, d1));
++        if (!event.callEvent()) return;
++        this.g = event.getNewCenter().getX();
++        this.h = event.getNewCenter().getZ();
++        // Paper end
+         this.j.k();
+         Iterator iterator = this.l().iterator();
+ 
+         while (iterator.hasNext()) {
+             IWorldBorderListener iworldborderlistener = (IWorldBorderListener) iterator.next();
+ 
+-            iworldborderlistener.a(this, d0, d1);
++            iworldborderlistener.a(this, event.getNewCenter().getX(), event.getNewCenter().getZ()); // Paper
+         }
+ 
+     }
+@@ -0,0 +0,0 @@ public class WorldBorder {
+     }
+ 
+     public void setSize(double d0) {
+-        this.j = new WorldBorder.d(d0);
++        // Paper start
++        WorldBorderBoundsChangeEvent event = new WorldBorderBoundsChangeEvent(world.getWorld(), world.getWorld().getWorldBorder(), WorldBorderBoundsChangeEvent.Type.INSTANT_MOVE, getSize(), d0, 0);
++        if (!event.callEvent()) return;
++        if (event.getType() == WorldBorderBoundsChangeEvent.Type.STARTED_MOVE && event.getDuration() > 0) { // If changed to a timed transition
++            transitionSizeBetween(event.getOldSize(), event.getNewSize(), event.getDuration());
++            return;
++        }
++        this.j = new WorldBorder.d(event.getNewSize());
++        // Paper end
+         Iterator iterator = this.l().iterator();
+ 
+         while (iterator.hasNext()) {
+             IWorldBorderListener iworldborderlistener = (IWorldBorderListener) iterator.next();
+ 
+-            iworldborderlistener.a(this, d0);
++            iworldborderlistener.a(this, event.getNewSize()); // Paper
+         }
+ 
+     }
+ 
+     public void transitionSizeBetween(double d0, double d1, long i) {
+-        this.j = (WorldBorder.a) (d0 == d1 ? new WorldBorder.d(d1) : new WorldBorder.b(d0, d1, i));
++        // Paper start
++        WorldBorderBoundsChangeEvent.Type type;
++        if (d0 == d1) { // new size = old size
++            type = WorldBorderBoundsChangeEvent.Type.INSTANT_MOVE; // Use INSTANT_MOVE because below it creates a Static border if they are equal.
++        } else {
++            type = WorldBorderBoundsChangeEvent.Type.STARTED_MOVE;
++        }
++        WorldBorderBoundsChangeEvent event = new WorldBorderBoundsChangeEvent(world.getWorld(), world.getWorld().getWorldBorder(), type, d0, d1, i);
++        if (!event.callEvent()) return;
++        this.j = (WorldBorder.a) (d0 == event.getNewSize() ? new WorldBorder.d(event.getNewSize()) : new WorldBorder.b(d0, event.getNewSize(), event.getDuration()));
++        // Paper end
+         Iterator iterator = this.l().iterator();
+ 
+         while (iterator.hasNext()) {
+             IWorldBorderListener iworldborderlistener = (IWorldBorderListener) iterator.next();
+ 
+-            iworldborderlistener.a(this, d0, d1, i);
++            iworldborderlistener.a(this, d0, event.getNewSize(), event.getDuration()); // Paper
+         }
+ 
+     }
+@@ -0,0 +0,0 @@ public class WorldBorder {
+ 
+     class b implements WorldBorder.a {
+ 
+-        private final double b;
+-        private final double c;
++        private final double b; public final double getOldSize() { return this.b; } // Paper - OBFHELPER
++        private final double c; public final double getNewSize() { return this.c; } // Paper - OBFHELPER
+         private final long d;
+         private final long e;
+-        private final double f;
++        private final double f; public final double getDuration() { return this.f; } // Paper - OBFHELPER
+ 
+         private b(double d0, double d1, long i) {
+             this.b = d0;
+@@ -0,0 +0,0 @@ public class WorldBorder {
+ 
+         @Override
+         public WorldBorder.a l() {
++            if (this.getLerpTimeRemaining() <= 0L) new WorldBorderBoundsChangeFinishEvent(world.getWorld(), world.getWorld().getWorldBorder(), getOldSize(), getNewSize(), getDuration()).callEvent(); // Paper
+             return (WorldBorder.a) (this.g() <= 0L ? WorldBorder.this.new d(this.c) : this);
+         }
+ 
+@@ -0,0 +0,0 @@ public class WorldBorder {
+ 
+         double e();
+ 
++        default long getLerpTimeRemaining() { return g(); } // Paper - OBFHELPER
+         long g();
+ 
+         double h();