Merge remote-tracking branch 'upstream/dev/4.0.0'

# Conflicts:
#	proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java
This commit is contained in:
yoyosource
2026-08-24 17:03:57 +02:00
39 changed files with 600 additions and 133 deletions
+1 -1
View File
@@ -18,7 +18,7 @@ java {
}
dependencies {
compileOnlyApi(libs.jspecify)
api(libs.jspecify)
api(libs.gson)
api(libs.guava)
@@ -97,6 +97,16 @@ public class PluginAnnotationProcessor extends AbstractProcessor {
}
}
for (String provided : plugin.provides()) {
if (!SerializedPluginDescription.ID_PATTERN.matcher(provided).matches()) {
environment.getMessager().printMessage(Diagnostic.Kind.ERROR,
"Invalid provided ID '" + provided + "' for plugin " + qualifiedName
+ ". IDs must start alphabetically, have lowercase alphanumeric characters, and "
+ "can contain dashes or underscores.");
return false;
}
}
// All good, generate the velocity-plugin.json.
SerializedPluginDescription description = SerializedPluginDescription
.from(plugin, qualifiedName.toString());
@@ -35,11 +35,12 @@ public final class SerializedPluginDescription {
private final @Nullable String url;
private final @Nullable List<String> authors;
private final @Nullable List<Dependency> dependencies;
private final @Nullable List<String> provides;
private final String main;
private SerializedPluginDescription(String id, String name, String version, String description,
String url,
List<String> authors, List<Dependency> dependencies, String main) {
List<String> authors, List<Dependency> dependencies, List<String> provides, String main) {
Preconditions.checkNotNull(id, "id");
Preconditions.checkArgument(ID_PATTERN.matcher(id).matches(), "id is not valid");
this.id = id;
@@ -50,6 +51,7 @@ public final class SerializedPluginDescription {
this.authors = authors == null || authors.isEmpty() ? ImmutableList.of() : authors;
this.dependencies =
dependencies == null || dependencies.isEmpty() ? ImmutableList.of() : dependencies;
this.provides = provides == null || provides.isEmpty() ? ImmutableList.of() : provides;
this.main = Preconditions.checkNotNull(main, "main");
}
@@ -61,7 +63,9 @@ public final class SerializedPluginDescription {
return new SerializedPluginDescription(plugin.id(), plugin.name(), plugin.version(),
plugin.description(), plugin.url(),
Arrays.stream(plugin.authors()).filter(author -> !author.isEmpty())
.collect(Collectors.toList()), dependencies, qualifiedName);
.collect(Collectors.toList()), dependencies,
Arrays.stream(plugin.provides()).filter(provided -> !provided.isEmpty())
.collect(Collectors.toList()), qualifiedName);
}
public String getId() {
@@ -92,6 +96,10 @@ public final class SerializedPluginDescription {
return dependencies == null ? ImmutableList.of() : dependencies;
}
public List<String> getProvides() {
return provides == null ? ImmutableList.of() : provides;
}
public String getMain() {
return main;
}
@@ -112,12 +120,13 @@ public final class SerializedPluginDescription {
&& Objects.equals(url, that.url)
&& Objects.equals(authors, that.authors)
&& Objects.equals(dependencies, that.dependencies)
&& Objects.equals(provides, that.provides)
&& Objects.equals(main, that.main);
}
@Override
public int hashCode() {
return Objects.hash(id, name, version, description, url, authors, dependencies);
return Objects.hash(id, name, version, description, url, authors, dependencies, provides);
}
@Override
@@ -130,6 +139,7 @@ public final class SerializedPluginDescription {
+ ", url='" + url + '\''
+ ", authors=" + authors
+ ", dependencies=" + dependencies
+ ", provides=" + provides
+ ", main='" + main + '\''
+ '}';
}
@@ -0,0 +1,45 @@
/*
* Copyright (C) 2018-2026 Velocity Contributors
*
* The Velocity API is licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in the api top-level directory.
*/
package com.velocitypowered.api.event.player;
import com.google.common.annotations.Beta;
import com.google.common.base.Preconditions;
import com.velocitypowered.api.proxy.Player;
/**
* Called when a player is marked as loaded by the client.
*
* <p>This event is fired once per {@link com.velocitypowered.api.proxy.ServerConnection}
* when the player explicitly notifies the server after loading the world (closing the downloading terrain screen)
*
* @implNote Unlike Paper this event will <u>not</u> fire due to a timeout nor respawning.
* Though plugins can implement a timeout by scheduling a task in {@link ServerPostConnectEvent}
* and checking {@link com.velocitypowered.api.proxy.ServerConnection#isClientLoaded()}.
* @sinceMinecraft 1.21.4
* @since 4.1.0
*/
@Beta
public final class PlayerClientLoadedWorldEvent {
private final Player player;
public PlayerClientLoadedWorldEvent(Player player) {
this.player = Preconditions.checkNotNull(player, "player");
}
public Player getPlayer() {
return player;
}
@Override
public String toString() {
return "PlayerClientLoadedWorldEvent{"
+ "player=" + player
+ '}';
}
}
@@ -72,4 +72,12 @@ public @interface Plugin {
* @return the plugin dependencies
*/
Dependency[] dependencies() default {};
/**
* The plugin IDs this plugin "provides" for. Each ID must match
* {@link SerializedPluginDescription#ID_PATTERN_STRING}.
*
* @return the provided IDs
*/
String[] provides() default {};
}
@@ -100,6 +100,16 @@ public interface PluginDescription {
return Optional.empty();
}
/**
* Gets a {@link Collection} of the provided IDs of the {@link Plugin} within this container.
*
* @return the provided plugins IDs, can be empty
* @see Plugin#provides()
*/
default Collection<String> getProvidedIds() {
return ImmutableSet.of();
}
/**
* Returns the source the plugin was loaded from.
*
@@ -7,6 +7,7 @@
package com.velocitypowered.api.proxy;
import com.google.common.annotations.Beta;
import com.velocitypowered.api.proxy.messages.ChannelMessageSink;
import com.velocitypowered.api.proxy.messages.ChannelMessageSource;
import com.velocitypowered.api.proxy.server.RegisteredServer;
@@ -40,6 +41,17 @@ public interface ServerConnection extends ChannelMessageSource, ChannelMessageSi
*/
ServerInfo getServerInfo();
/**
* Returns whether the client notified this connection of having loaded the world.
*
* @return true if the client has loaded the world
* @implNote This is purely client-dependent; see {@link com.velocitypowered.api.event.player.PlayerClientLoadedWorldEvent}.
* @sinceMinecraft 1.21.4
* @since 4.1.0
*/
@Beta
boolean isClientLoaded();
/**
* Returns the player that this connection is associated with.
*