packs = ProtocolUtils.newList(packCount);
for (int i = 0; i < packCount; i++) {
- packs[i] = KnownPack.read(buf);
+ packs.add(KnownPack.read(buf));
}
this.packs = packs;
@@ -52,7 +53,7 @@ public class KnownPacksPacket implements MinecraftPacket {
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
- ProtocolUtils.writeVarInt(buf, packs.length);
+ ProtocolUtils.writeVarInt(buf, packs.size());
for (KnownPack pack : packs) {
pack.write(buf);
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/GenericTitlePacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/GenericTitlePacket.java
index d6f5e0e5..8641173e 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/GenericTitlePacket.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/GenericTitlePacket.java
@@ -105,26 +105,14 @@ public abstract class GenericTitlePacket implements MinecraftPacket {
public static GenericTitlePacket constructTitlePacket(ActionType type, ProtocolVersion version) {
GenericTitlePacket packet = null;
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_17)) {
- switch (type) {
- case SET_ACTION_BAR:
- packet = new TitleActionbarPacket();
- break;
- case SET_SUBTITLE:
- packet = new TitleSubtitlePacket();
- break;
- case SET_TIMES:
- packet = new TitleTimesPacket();
- break;
- case SET_TITLE:
- packet = new TitleTextPacket();
- break;
- case HIDE:
- case RESET:
- packet = new TitleClearPacket();
- break;
- default:
- throw new IllegalArgumentException("Invalid ActionType");
- }
+ packet = switch (type) {
+ case SET_ACTION_BAR -> new TitleActionbarPacket();
+ case SET_SUBTITLE -> new TitleSubtitlePacket();
+ case SET_TIMES -> new TitleTimesPacket();
+ case SET_TITLE -> new TitleTextPacket();
+ case HIDE, RESET -> new TitleClearPacket();
+ default -> throw new IllegalArgumentException("Invalid ActionType");
+ };
} else {
packet = new LegacyTitlePacket();
}
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/LegacyTitlePacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/LegacyTitlePacket.java
index 0425f2d3..ebae8a9e 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/LegacyTitlePacket.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/LegacyTitlePacket.java
@@ -40,24 +40,19 @@ public class LegacyTitlePacket extends GenericTitlePacket {
ProtocolUtils.writeVarInt(buf, getAction().getAction(version));
switch (getAction()) {
- case SET_TITLE:
- case SET_SUBTITLE:
- case SET_ACTION_BAR:
+ case SET_TITLE, SET_SUBTITLE, SET_ACTION_BAR -> {
if (component == null) {
throw new IllegalStateException("No component found for " + getAction());
}
component.write(buf);
- break;
- case SET_TIMES:
+ }
+ case SET_TIMES -> {
buf.writeInt(fadeIn);
buf.writeInt(stay);
buf.writeInt(fadeOut);
- break;
- case HIDE:
- case RESET:
- break;
- default:
- throw new UnsupportedOperationException("Unknown action " + getAction());
+ }
+ case HIDE, RESET -> {}
+ default -> throw new UnsupportedOperationException("Unknown action " + getAction());
}
}
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/PluginMessageUtil.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/PluginMessageUtil.java
index 1936eb3f..298e74d4 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/PluginMessageUtil.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/PluginMessageUtil.java
@@ -230,23 +230,20 @@ public final class PluginMessageUtil {
}
// Before falling into the fallback, explicitly rewrite certain messages.
- switch (name) {
- case REGISTER_CHANNEL_LEGACY:
- return REGISTER_CHANNEL;
- case UNREGISTER_CHANNEL_LEGACY:
- return UNREGISTER_CHANNEL;
- case BRAND_CHANNEL_LEGACY:
- return BRAND_CHANNEL;
- case "BungeeCord":
- // This is a special historical case we are compelled to support for the benefit of
- // BungeeQuack.
- return "bungeecord:main";
- default:
+ return switch (name) {
+ case REGISTER_CHANNEL_LEGACY -> REGISTER_CHANNEL;
+ case UNREGISTER_CHANNEL_LEGACY -> UNREGISTER_CHANNEL;
+ case BRAND_CHANNEL_LEGACY -> BRAND_CHANNEL;
+ // This is a special historical case we are compelled to support for the benefit of
+ // BungeeQuack.
+ case "BungeeCord" -> "bungeecord:main";
+ default -> {
// This is very likely a legacy name, so transform it. Velocity uses the same scheme as
// BungeeCord does to transform channels, but also removes clearly invalid characters as
// well.
- String lower = name.toLowerCase(Locale.ROOT);
- return "legacy:" + INVALID_IDENTIFIER_REGEX.matcher(lower).replaceAll("");
- }
+ final String lower = name.toLowerCase(Locale.ROOT);
+ yield "legacy:" + INVALID_IDENTIFIER_REGEX.matcher(lower).replaceAll("");
+ }
+ };
}
}
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/scheduler/ExecutorSchedulerBackend.java b/proxy/src/main/java/com/velocitypowered/proxy/scheduler/ExecutorSchedulerBackend.java
new file mode 100644
index 00000000..d10eab6f
--- /dev/null
+++ b/proxy/src/main/java/com/velocitypowered/proxy/scheduler/ExecutorSchedulerBackend.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2018-2026 Velocity Contributors
+ *
+ * This program 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.
+ *
+ * This program 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 this program. If not, see .
+ */
+
+package com.velocitypowered.proxy.scheduler;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A {@link SchedulerBackend} backed by a real {@link ScheduledExecutorService}.
+ */
+public class ExecutorSchedulerBackend implements SchedulerBackend {
+
+ private final ScheduledExecutorService executor;
+
+ /**
+ * Creates a ExecutorSchedulerBackend with a default executor.
+ */
+ public ExecutorSchedulerBackend() {
+ this(Executors.newSingleThreadScheduledExecutor(
+ new ThreadFactoryBuilder()
+ .setDaemon(true)
+ .setNameFormat("Velocity Task Scheduler Timer")
+ .build()
+ ));
+ }
+
+ /**
+ * Creates a ExecutorSchedulerBackend with a given executor.
+ *
+ * @param executor The executor to use.
+ */
+ public ExecutorSchedulerBackend(ScheduledExecutorService executor) {
+ this.executor = checkNotNull(executor, "executor");
+ }
+
+ @Override
+ public ScheduledFuture> schedule(Runnable task, long delay, TimeUnit unit) {
+ return executor.schedule(task, delay, unit);
+ }
+
+ @Override
+ public ScheduledFuture> scheduleAtFixedRate(Runnable task, long initialDelay, long period, TimeUnit unit) {
+ return executor.scheduleAtFixedRate(task, initialDelay, period, unit);
+ }
+
+ @Override
+ public void shutdown() {
+ executor.shutdown();
+ }
+}
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/scheduler/SchedulerBackend.java b/proxy/src/main/java/com/velocitypowered/proxy/scheduler/SchedulerBackend.java
new file mode 100644
index 00000000..ab251ca2
--- /dev/null
+++ b/proxy/src/main/java/com/velocitypowered/proxy/scheduler/SchedulerBackend.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2018-2026 Velocity Contributors
+ *
+ * This program 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.
+ *
+ * This program 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 this program. If not, see .
+ */
+
+package com.velocitypowered.proxy.scheduler;
+
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Backend interface used by {@link VelocityScheduler} to schedule timer callbacks.
+ *
+ * This is an internal abstraction that allows tests to replace the real-time scheduler
+ * with a deterministic implementation.
+ */
+interface SchedulerBackend {
+
+ /**
+ * Schedules a task to run once after the given delay.
+ *
+ * @param task the task to run
+ * @param delay the delay
+ * @param unit the delay unit
+ * @return a future representing the scheduled task
+ */
+ ScheduledFuture> schedule(Runnable task, long delay, TimeUnit unit);
+
+ /**
+ * Schedules a task to run at a fixed rate.
+ *
+ * @param task the task to run
+ * @param initialDelay the initial delay
+ * @param period the period between runs
+ * @param unit the time unit
+ * @return a future representing the scheduled task
+ */
+ ScheduledFuture> scheduleAtFixedRate(Runnable task, long initialDelay, long period, TimeUnit unit);
+
+ /**
+ * Shuts down the backend.
+ */
+ void shutdown();
+}
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/scheduler/VelocityScheduler.java b/proxy/src/main/java/com/velocitypowered/proxy/scheduler/VelocityScheduler.java
index 727832bd..0a38582a 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/scheduler/VelocityScheduler.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/scheduler/VelocityScheduler.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018-2023 Velocity Contributors
+ * Copyright (C) 2018-2026 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -23,7 +23,6 @@ import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
-import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.velocitypowered.api.plugin.PluginContainer;
import com.velocitypowered.api.plugin.PluginManager;
import com.velocitypowered.api.scheduler.ScheduledTask;
@@ -40,7 +39,6 @@ import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
@@ -60,20 +58,23 @@ import org.jetbrains.annotations.VisibleForTesting;
public class VelocityScheduler implements Scheduler {
private final PluginManager pluginManager;
- private final ScheduledExecutorService timerExecutionService;
+ private final SchedulerBackend backend;
private final Multimap