Skip to content

Commit 8b0cd21

Browse files
committed
WIP Configuration API
1 parent f517267 commit 8b0cd21

26 files changed

+1008
-26
lines changed

notes.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
- Cookie handling is now async on login + works better, able to be requested at any stage.
2+
3+
Previously, the cookie handling for login occurred after the PlayerLoginEvent only. This meant that cookies could not be sent before the player was created.
4+
New Use Cases:
5+
- Instantly terminate the connection if a cookie is not present.
6+
7+
Behavior changes:
8+
- PlayerLoginEvent no longer blocks cookies
9+
- PlayerLoginEvent now triggers twice due to reasons
10+
- Disconnecting a duplicate player no longer causes a forced save. Check to make sure that still occurs
11+
12+
TODO:
13+
- PlayerLinksSendEvent
14+
- Figure out connection hierarchy
15+
- Figure out naming
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package io.papermc.paper.connection;
2+
3+
import org.bukkit.NamespacedKey;
4+
import org.jspecify.annotations.NullMarked;
5+
import java.util.concurrent.CompletableFuture;
6+
7+
@NullMarked
8+
public interface CookieConnection extends PlayerConnection {
9+
10+
/**
11+
* Retrieves a cookie from this connection.
12+
*
13+
* @param key the key identifying the cookie
14+
* @return a {@link CompletableFuture} that will be completed when the
15+
* Cookie response is received or otherwise available. If the cookie is not
16+
* set in the client, the {@link CompletableFuture} will complete with a
17+
* null value.
18+
*/
19+
CompletableFuture<byte[]> retrieveCookie(NamespacedKey key);
20+
21+
/**
22+
* Stores a cookie in this player's client.
23+
*
24+
* @param key the key identifying the cookie
25+
* @param value the data to store in the cookie
26+
* @throws IllegalStateException if a cookie cannot be stored at this time
27+
*/
28+
void storeCookie(NamespacedKey key, byte[] value);
29+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package io.papermc.paper.connection;
2+
3+
import com.destroystokyo.paper.profile.PlayerProfile;
4+
import org.checkerframework.checker.nullness.qual.Nullable;
5+
import org.jetbrains.annotations.NotNull;
6+
import org.jspecify.annotations.NullMarked;
7+
import java.net.InetAddress;
8+
import java.net.InetSocketAddress;
9+
10+
@NullMarked
11+
public interface PlayerConfigurationConnection extends CookieConnection {
12+
13+
/**
14+
* Gets the profile for this connection.
15+
* @return profile
16+
*/
17+
PlayerProfile getProfile();
18+
19+
/**
20+
* Gets the player IP address.
21+
*
22+
* @return The IP address
23+
*/
24+
@NotNull
25+
InetAddress getAddress();
26+
/**
27+
* Gets the raw address of the player logging in
28+
* @return The address
29+
*/
30+
@NotNull
31+
InetAddress getRawAddress();
32+
33+
/**
34+
* Gets the hostname that the player used to connect to the server, or
35+
* blank if unknown
36+
*
37+
* @return The hostname
38+
*/
39+
@NotNull
40+
String getHostname();
41+
42+
boolean isTransferred();
43+
44+
/**
45+
* Clears the players chat history and their local chat.
46+
*/
47+
void clearChat();
48+
49+
/**
50+
* Note, this should be only be called if you are reconfiguring the player.
51+
* Calling this on initial configuration will caused undefined behavior.
52+
*/
53+
void completeConfiguration();
54+
55+
<T> T getClientOption(com.destroystokyo.paper.ClientOption<T> type);
56+
57+
void transfer(String host, int port);
58+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package io.papermc.paper.connection;
2+
3+
import net.kyori.adventure.text.Component;
4+
import org.bukkit.entity.Player;
5+
6+
public interface PlayerConnection {
7+
8+
void disconnect(Component component);
9+
10+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package io.papermc.paper.connection;
2+
3+
import org.bukkit.entity.Player;
4+
5+
public interface PlayerGameConnection extends CookieConnection {
6+
7+
8+
/**
9+
* Bumps the player to the configuration stage.
10+
* <p>
11+
* This will, by default, cause the player to stay until their connection is released by
12+
* {@link PlayerConfigurationConnection#completeConfiguration()}
13+
*/
14+
void configurate();
15+
16+
Player getPlayer();
17+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package io.papermc.paper.connection;
2+
3+
import com.destroystokyo.paper.profile.PlayerProfile;
4+
import org.bukkit.NamespacedKey;
5+
import org.checkerframework.checker.nullness.qual.Nullable;
6+
import org.jetbrains.annotations.NotNull;
7+
import org.jspecify.annotations.NullMarked;
8+
import java.io.IOException;
9+
import java.net.InetAddress;
10+
import java.net.InetSocketAddress;
11+
12+
@NullMarked
13+
public interface PlayerLoginConnection extends CookieConnection {
14+
15+
/**
16+
* Gets the authenticated profile for this connection.
17+
* This may return null depending on what stage this connection is at.
18+
* @return authenticated profile, or null if not currently present
19+
*/
20+
@Nullable
21+
PlayerProfile getAuthenticatedProfile();
22+
23+
/**
24+
* Gets the player IP address.
25+
*
26+
* @return The IP address
27+
*/
28+
@NotNull
29+
InetAddress getAddress();
30+
/**
31+
* Gets the raw address of the player logging in
32+
* @return The address
33+
*/
34+
@NotNull
35+
InetAddress getRawAddress();
36+
37+
/**
38+
* Gets the hostname that the player used to connect to the server, or
39+
* blank if unknown
40+
*
41+
* @return The hostname
42+
*/
43+
@NotNull
44+
String getHostname();
45+
46+
boolean isTransferred();
47+
48+
// TODO: Should we have a read-only interface?
49+
@Deprecated
50+
@Override
51+
void storeCookie(NamespacedKey key, byte[] value) throws UnsupportedOperationException;
52+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package io.papermc.paper.event.connection.common;
2+
3+
import io.papermc.paper.connection.PlayerConnection;
4+
import net.kyori.adventure.text.Component;
5+
import org.bukkit.event.Event;
6+
import org.bukkit.event.HandlerList;
7+
import org.jetbrains.annotations.ApiStatus;
8+
import org.jetbrains.annotations.NotNull;
9+
10+
/**
11+
* Validates whether a player connection would be able to login at this event.
12+
* <p>
13+
* Currently, this occurs when the player connection is attempting to log in for the first time, or is finishing up
14+
* being configured.
15+
*/
16+
// TODO: I am not sure about this event however. I have seen the most common reasons for needing a LoginEvent is to prevent the fullness check. Should we add a separate event for each case? Like WhitelistVerifyEvent?
17+
public class PlayerConnectionValidateLoginEvent extends Event {
18+
private static final HandlerList handlers = new HandlerList();
19+
20+
private final PlayerConnection connection;
21+
private Result result;
22+
private Component message;
23+
24+
@ApiStatus.Internal
25+
public PlayerConnectionValidateLoginEvent(PlayerConnection connection, final Result result, final Component message) {
26+
super(false);
27+
this.connection = connection;
28+
this.result = result;
29+
this.message = message;
30+
}
31+
32+
public PlayerConnection getConnection() {
33+
return connection;
34+
}
35+
36+
public Component getMessage() {
37+
return message;
38+
}
39+
40+
public Result getResult() {
41+
return result;
42+
}
43+
44+
/**
45+
* Allows the player to log in
46+
*/
47+
public void allow() {
48+
result = Result.ALLOWED;
49+
message = net.kyori.adventure.text.Component.empty();
50+
}
51+
52+
/**
53+
* Disallows the player from logging in, with the given reason
54+
*
55+
* @param result New result for disallowing the player
56+
* @param message Kick message to display to the user
57+
*/
58+
public void disallow(@NotNull final Result result, @NotNull final net.kyori.adventure.text.Component message) {
59+
this.result = result;
60+
this.message = message;
61+
}
62+
63+
/**
64+
* Basic kick reasons for communicating to plugins
65+
*/
66+
public enum Result {
67+
68+
/**
69+
* The player is allowed to log in
70+
*/
71+
ALLOWED,
72+
/**
73+
* The player is not allowed to log in, due to the server being full
74+
*/
75+
KICK_FULL,
76+
/**
77+
* The player is not allowed to log in, due to them being banned
78+
*/
79+
KICK_BANNED,
80+
/**
81+
* The player is not allowed to log in, due to them not being on the
82+
* white list
83+
*/
84+
KICK_WHITELIST,
85+
/**
86+
* The player is not allowed to log in, for reasons undefined
87+
*/
88+
KICK_OTHER
89+
}
90+
91+
@NotNull
92+
@Override
93+
public HandlerList getHandlers() {
94+
return handlers;
95+
}
96+
97+
@NotNull
98+
public static HandlerList getHandlerList() {
99+
return handlers;
100+
}
101+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package io.papermc.paper.event.connection.configuration;
2+
3+
import io.papermc.paper.connection.PlayerConfigurationConnection;
4+
import org.bukkit.event.HandlerList;
5+
import org.jetbrains.annotations.ApiStatus;
6+
import org.jetbrains.annotations.NotNull;
7+
8+
/**
9+
* A task that allows you to configurate the player.
10+
* This is async and allows you to run configuration code on the player.
11+
* Once this event has finished execution, the player connection will continue.
12+
* <p>
13+
* This occurs after configuration, but before the player has entered the world.
14+
*/
15+
public class AsyncPlayerConnectionConfigurateEvent extends PlayerConfigurationConnectionEvent {
16+
private static final HandlerList handlers = new HandlerList();
17+
18+
@ApiStatus.Internal
19+
public AsyncPlayerConnectionConfigurateEvent(PlayerConfigurationConnection connection) {
20+
super(true, connection);
21+
}
22+
23+
@NotNull
24+
@Override
25+
public HandlerList getHandlers() {
26+
return handlers;
27+
}
28+
29+
@NotNull
30+
public static HandlerList getHandlerList() {
31+
return handlers;
32+
}
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package io.papermc.paper.event.connection.configuration;
2+
3+
import io.papermc.paper.connection.PlayerConfigurationConnection;
4+
import io.papermc.paper.connection.PlayerLoginConnection;
5+
import org.bukkit.event.Event;
6+
7+
abstract class PlayerConfigurationConnectionEvent extends Event {
8+
private final PlayerConfigurationConnection loginConnection;
9+
10+
protected PlayerConfigurationConnectionEvent(final PlayerConfigurationConnection loginConnection) {
11+
this.loginConnection = loginConnection;
12+
}
13+
14+
protected PlayerConfigurationConnectionEvent(final boolean async, final PlayerConfigurationConnection loginConnection) {
15+
super(async);
16+
this.loginConnection = loginConnection;
17+
}
18+
19+
public PlayerConfigurationConnection getConfigurationConnection() {
20+
return this.loginConnection;
21+
}
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package io.papermc.paper.event.connection.configuration;
2+
3+
import io.papermc.paper.connection.PlayerConfigurationConnection;
4+
import org.bukkit.Bukkit;
5+
import org.bukkit.event.HandlerList;
6+
import org.jetbrains.annotations.ApiStatus;
7+
import org.jetbrains.annotations.NotNull;
8+
9+
public class PlayerConnectionInitialConfigurateEvent extends PlayerConfigurationConnectionEvent {
10+
private static final HandlerList handlers = new HandlerList();
11+
12+
@ApiStatus.Internal
13+
public PlayerConnectionInitialConfigurateEvent(PlayerConfigurationConnection connection) {
14+
super(!Bukkit.isPrimaryThread(), connection);
15+
}
16+
17+
@NotNull
18+
@Override
19+
public HandlerList getHandlers() {
20+
return handlers;
21+
}
22+
23+
@NotNull
24+
public static HandlerList getHandlerList() {
25+
return handlers;
26+
}
27+
}

0 commit comments

Comments
 (0)