Mirror von
https://github.com/PaperMC/Velocity.git
synchronisiert 2024-11-17 05:20:14 +01:00
Title API (#95)
Dieser Commit ist enthalten in:
Ursprung
1e04d27bb7
Commit
ee917682e0
@ -6,6 +6,7 @@ import com.velocitypowered.api.proxy.messages.ChannelMessageSource;
|
||||
import com.velocitypowered.api.proxy.player.PlayerSettings;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import com.velocitypowered.api.util.MessagePosition;
|
||||
import com.velocitypowered.api.util.title.Title;
|
||||
import net.kyori.text.Component;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
@ -83,6 +84,12 @@ public interface Player extends CommandSource, InboundConnection, ChannelMessage
|
||||
*/
|
||||
void disconnect(Component reason);
|
||||
|
||||
/**
|
||||
* Sends the specified title to the client.
|
||||
* @param title the title to send
|
||||
*/
|
||||
void sendTitle(Title title);
|
||||
|
||||
/**
|
||||
* Sends chat input onto the players current server as if they typed it
|
||||
* into the client chat box.
|
||||
|
236
api/src/main/java/com/velocitypowered/api/util/title/TextTitle.java
Normale Datei
236
api/src/main/java/com/velocitypowered/api/util/title/TextTitle.java
Normale Datei
@ -0,0 +1,236 @@
|
||||
package com.velocitypowered.api.util.title;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import net.kyori.text.Component;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Represents a "full" title, including all components. This class is immutable.
|
||||
*/
|
||||
public class TextTitle implements Title {
|
||||
private final Component title;
|
||||
private final Component subtitle;
|
||||
private final int stay;
|
||||
private final int fadeIn;
|
||||
private final int fadeOut;
|
||||
private final boolean resetBeforeSend;
|
||||
|
||||
private TextTitle(Builder builder) {
|
||||
this.title = builder.title;
|
||||
this.subtitle = builder.subtitle;
|
||||
this.stay = builder.stay;
|
||||
this.fadeIn = builder.fadeIn;
|
||||
this.fadeOut = builder.fadeOut;
|
||||
this.resetBeforeSend = builder.resetBeforeSend;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the main title this title has, if any.
|
||||
* @return the main title of this title
|
||||
*/
|
||||
public Optional<Component> getTitle() {
|
||||
return Optional.ofNullable(title);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the subtitle this title has, if any.
|
||||
* @return the subtitle
|
||||
*/
|
||||
public Optional<Component> getSubtitle() {
|
||||
return Optional.ofNullable(subtitle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of ticks this title will stay up.
|
||||
* @return how long the title will stay, in ticks
|
||||
*/
|
||||
public int getStay() {
|
||||
return stay;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of ticks over which this title will fade in.
|
||||
* @return how long the title will fade in, in ticks
|
||||
*/
|
||||
public int getFadeIn() {
|
||||
return fadeIn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of ticks over which this title will fade out.
|
||||
* @return how long the title will fade out, in ticks
|
||||
*/
|
||||
public int getFadeOut() {
|
||||
return fadeOut;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not a reset packet will be sent before this title is sent. By default, unless explicitly
|
||||
* disabled, this is enabled by default.
|
||||
* @return whether or not a reset packet will be sent before this title is sent
|
||||
*/
|
||||
public boolean isResetBeforeSend() {
|
||||
return resetBeforeSend;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether or not this title has times set on it. If none are set, it will update the previous title
|
||||
* set on the client.
|
||||
* @return whether or not this title has times set on it
|
||||
*/
|
||||
public boolean areTimesSet() {
|
||||
return stay != 0 || fadeIn != 0 || fadeOut != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new builder from the contents of this title so that it may be changed.
|
||||
* @return a builder instance with the contents of this title
|
||||
*/
|
||||
public Builder toBuilder() {
|
||||
return new Builder(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
TextTitle textTitle = (TextTitle) o;
|
||||
return stay == textTitle.stay &&
|
||||
fadeIn == textTitle.fadeIn &&
|
||||
fadeOut == textTitle.fadeOut &&
|
||||
resetBeforeSend == textTitle.resetBeforeSend &&
|
||||
Objects.equals(title, textTitle.title) &&
|
||||
Objects.equals(subtitle, textTitle.subtitle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TextTitle{" +
|
||||
"title=" + title +
|
||||
", subtitle=" + subtitle +
|
||||
", stay=" + stay +
|
||||
", fadeIn=" + fadeIn +
|
||||
", fadeOut=" + fadeOut +
|
||||
", resetBeforeSend=" + resetBeforeSend +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(title, subtitle, stay, fadeIn, fadeOut, resetBeforeSend);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new builder for constructing titles.
|
||||
* @return a builder for constructing titles
|
||||
*/
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private @Nullable Component title;
|
||||
private @Nullable Component subtitle;
|
||||
private int stay;
|
||||
private int fadeIn;
|
||||
private int fadeOut;
|
||||
private boolean resetBeforeSend = true;
|
||||
|
||||
private Builder() {}
|
||||
|
||||
private Builder(TextTitle copy) {
|
||||
this.title = copy.title;
|
||||
this.subtitle = copy.subtitle;
|
||||
this.stay = copy.stay;
|
||||
this.fadeIn = copy.fadeIn;
|
||||
this.fadeOut = copy.fadeOut;
|
||||
this.resetBeforeSend = copy.resetBeforeSend;
|
||||
}
|
||||
|
||||
public Builder title(Component title) {
|
||||
this.title = Preconditions.checkNotNull(title, "title");
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder clearTitle() {
|
||||
this.title = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder subtitle(Component subtitle) {
|
||||
this.subtitle = Preconditions.checkNotNull(subtitle, "subtitle");
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder clearSubtitle() {
|
||||
this.subtitle = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder stay(int ticks) {
|
||||
Preconditions.checkArgument(ticks >= 0, "ticks value %s is negative", ticks);
|
||||
this.stay = ticks;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder fadeIn(int ticks) {
|
||||
Preconditions.checkArgument(ticks >= 0, "ticks value %s is negative", ticks);
|
||||
this.fadeIn = ticks;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder fadeOut(int ticks) {
|
||||
Preconditions.checkArgument(ticks >= 0, "ticks value %s is negative", ticks);
|
||||
this.fadeOut = ticks;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder resetBeforeSend(boolean b) {
|
||||
this.resetBeforeSend = b;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Component getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public Component getSubtitle() {
|
||||
return subtitle;
|
||||
}
|
||||
|
||||
public int getStay() {
|
||||
return stay;
|
||||
}
|
||||
|
||||
public int getFadeIn() {
|
||||
return fadeIn;
|
||||
}
|
||||
|
||||
public int getFadeOut() {
|
||||
return fadeOut;
|
||||
}
|
||||
|
||||
public boolean isResetBeforeSend() {
|
||||
return resetBeforeSend;
|
||||
}
|
||||
|
||||
public TextTitle build() {
|
||||
return new TextTitle(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Builder{" +
|
||||
"title=" + title +
|
||||
", subtitle=" + subtitle +
|
||||
", stay=" + stay +
|
||||
", fadeIn=" + fadeIn +
|
||||
", fadeOut=" + fadeOut +
|
||||
", resetBeforeSend=" + resetBeforeSend +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
}
|
7
api/src/main/java/com/velocitypowered/api/util/title/Title.java
Normale Datei
7
api/src/main/java/com/velocitypowered/api/util/title/Title.java
Normale Datei
@ -0,0 +1,7 @@
|
||||
package com.velocitypowered.api.util.title;
|
||||
|
||||
/**
|
||||
* Represents a title that can be sent to a Minecraft client.
|
||||
*/
|
||||
public interface Title {
|
||||
}
|
50
api/src/main/java/com/velocitypowered/api/util/title/Titles.java
Normale Datei
50
api/src/main/java/com/velocitypowered/api/util/title/Titles.java
Normale Datei
@ -0,0 +1,50 @@
|
||||
package com.velocitypowered.api.util.title;
|
||||
|
||||
/**
|
||||
* Provides special-purpose titles.
|
||||
*/
|
||||
public class Titles {
|
||||
private Titles() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
private static final Title RESET = new Title() {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "reset title";
|
||||
}
|
||||
};
|
||||
|
||||
private static final Title HIDE = new Title() {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "hide title";
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a title that, when sent to the client, will cause all title data to be reset and any existing title to be
|
||||
* hidden.
|
||||
* @return the reset title
|
||||
*/
|
||||
public static Title reset() {
|
||||
return RESET;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a title that, when sent to the client, will cause any existing title to be hidden. The title may be
|
||||
* restored by a {@link TextTitle} with no title or subtitle (only a time).
|
||||
* @return the hide title
|
||||
*/
|
||||
public static Title hide() {
|
||||
return HIDE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a builder for {@link TextTitle}s.
|
||||
* @return a builder for text titles
|
||||
*/
|
||||
public static TextTitle.Builder text() {
|
||||
return TextTitle.builder();
|
||||
}
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Provides data structures for creating and manipulating titles.
|
||||
*/
|
||||
package com.velocitypowered.api.util.title;
|
@ -219,6 +219,9 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
player.getConnectedServer().getMinecraftConnection().delayedWrite(pm);
|
||||
}
|
||||
|
||||
// Clear any title from the previous server.
|
||||
player.getConnection().delayedWrite(TitlePacket.resetForProtocolVersion(player.getProtocolVersion()));
|
||||
|
||||
// Flush everything
|
||||
player.getConnection().flush();
|
||||
player.getConnectedServer().getMinecraftConnection().flush();
|
||||
|
@ -15,6 +15,9 @@ import com.velocitypowered.api.proxy.player.PlayerSettings;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import com.velocitypowered.api.util.GameProfile;
|
||||
import com.velocitypowered.api.util.MessagePosition;
|
||||
import com.velocitypowered.api.util.title.TextTitle;
|
||||
import com.velocitypowered.api.util.title.Title;
|
||||
import com.velocitypowered.api.util.title.Titles;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation;
|
||||
@ -22,6 +25,7 @@ import com.velocitypowered.proxy.connection.VelocityConstants;
|
||||
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
|
||||
import com.velocitypowered.proxy.connection.util.ConnectionMessages;
|
||||
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.packet.*;
|
||||
import com.velocitypowered.proxy.server.VelocityRegisteredServer;
|
||||
import com.velocitypowered.proxy.util.ThrowableUtils;
|
||||
@ -139,11 +143,20 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
byte pos = (byte) position.ordinal();
|
||||
String json;
|
||||
if (position == MessagePosition.ACTION_BAR) {
|
||||
if (getProtocolVersion() >= ProtocolConstants.MINECRAFT_1_11) {
|
||||
// We can use the title packet instead.
|
||||
TitlePacket pkt = new TitlePacket();
|
||||
pkt.setAction(TitlePacket.SET_ACTION_BAR);
|
||||
pkt.setComponent(ComponentSerializers.JSON.serialize(component));
|
||||
connection.write(pkt);
|
||||
return;
|
||||
} else {
|
||||
// Due to issues with action bar packets, we'll need to convert the text message into a legacy message
|
||||
// and then inject the legacy text into a component... yuck!
|
||||
JsonObject object = new JsonObject();
|
||||
object.addProperty("text", ComponentSerializers.LEGACY.serialize(component));
|
||||
json = VelocityServer.GSON.toJson(object);
|
||||
}
|
||||
} else {
|
||||
json = ComponentSerializers.JSON.serialize(component);
|
||||
}
|
||||
@ -176,6 +189,48 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
connection.closeWith(Disconnect.create(reason));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendTitle(Title title) {
|
||||
Preconditions.checkNotNull(title, "title");
|
||||
|
||||
if (title.equals(Titles.reset())) {
|
||||
connection.write(TitlePacket.resetForProtocolVersion(connection.getProtocolVersion()));
|
||||
} else if (title.equals(Titles.hide())) {
|
||||
connection.write(TitlePacket.hideForProtocolVersion(connection.getProtocolVersion()));
|
||||
} else if (title instanceof TextTitle) {
|
||||
TextTitle tt = (TextTitle) title;
|
||||
|
||||
if (tt.isResetBeforeSend()) {
|
||||
connection.delayedWrite(TitlePacket.resetForProtocolVersion(connection.getProtocolVersion()));
|
||||
}
|
||||
|
||||
if (tt.getTitle().isPresent()) {
|
||||
TitlePacket titlePkt = new TitlePacket();
|
||||
titlePkt.setAction(TitlePacket.SET_TITLE);
|
||||
titlePkt.setComponent(ComponentSerializers.JSON.serialize(tt.getTitle().get()));
|
||||
connection.delayedWrite(titlePkt);
|
||||
}
|
||||
if (tt.getSubtitle().isPresent()) {
|
||||
TitlePacket titlePkt = new TitlePacket();
|
||||
titlePkt.setAction(TitlePacket.SET_SUBTITLE);
|
||||
titlePkt.setComponent(ComponentSerializers.JSON.serialize(tt.getSubtitle().get()));
|
||||
connection.delayedWrite(titlePkt);
|
||||
}
|
||||
|
||||
if (tt.areTimesSet()) {
|
||||
TitlePacket timesPkt = TitlePacket.timesForProtocolVersion(connection.getProtocolVersion());
|
||||
timesPkt.setFadeIn(tt.getFadeIn());
|
||||
timesPkt.setStay(tt.getStay());
|
||||
timesPkt.setFadeOut(tt.getFadeOut());
|
||||
connection.delayedWrite(timesPkt);
|
||||
}
|
||||
connection.flush();
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unknown title class " + title.getClass().getName());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public VelocityServerConnection getConnectedServer() {
|
||||
return connectedServer;
|
||||
}
|
||||
|
@ -136,6 +136,12 @@ public enum StateRegistry {
|
||||
map(0x44, MINECRAFT_1_12, true),
|
||||
map(0x45, MINECRAFT_1_12_1, true),
|
||||
map(0x48, MINECRAFT_1_13, true));
|
||||
CLIENTBOUND.register(TitlePacket.class, TitlePacket::new,
|
||||
map(0x45, MINECRAFT_1_8, true),
|
||||
map(0x45, MINECRAFT_1_9, true),
|
||||
map(0x47, MINECRAFT_1_12, true),
|
||||
map(0x48, MINECRAFT_1_12_1, true),
|
||||
map(0x4B, MINECRAFT_1_13, true));
|
||||
}
|
||||
},
|
||||
LOGIN {
|
||||
|
@ -0,0 +1,137 @@
|
||||
package com.velocitypowered.proxy.protocol.packet;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public class TitlePacket implements MinecraftPacket {
|
||||
public static final int SET_TITLE = 0;
|
||||
public static final int SET_SUBTITLE = 1;
|
||||
public static final int SET_ACTION_BAR = 2;
|
||||
public static final int SET_TIMES = 3;
|
||||
public static final int SET_TIMES_OLD = 2;
|
||||
public static final int HIDE = 4;
|
||||
public static final int HIDE_OLD = 3;
|
||||
public static final int RESET = 5;
|
||||
public static final int RESET_OLD = 4;
|
||||
|
||||
private int action;
|
||||
private String component;
|
||||
private int fadeIn;
|
||||
private int stay;
|
||||
private int fadeOut;
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
throw new UnsupportedOperationException(); // encode only
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
ProtocolUtils.writeVarInt(buf, action);
|
||||
if (protocolVersion >= ProtocolConstants.MINECRAFT_1_11) {
|
||||
// 1.11+ shifted the action enum by 1 to handle the action bar
|
||||
switch (action) {
|
||||
case SET_TITLE:
|
||||
case SET_SUBTITLE:
|
||||
case SET_ACTION_BAR:
|
||||
ProtocolUtils.writeString(buf, component);
|
||||
break;
|
||||
case SET_TIMES:
|
||||
buf.writeInt(fadeIn);
|
||||
buf.writeInt(stay);
|
||||
buf.writeInt(fadeOut);
|
||||
break;
|
||||
case HIDE:
|
||||
case RESET:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (action) {
|
||||
case SET_TITLE:
|
||||
case SET_SUBTITLE:
|
||||
ProtocolUtils.writeString(buf, component);
|
||||
break;
|
||||
case SET_TIMES_OLD:
|
||||
buf.writeInt(fadeIn);
|
||||
buf.writeInt(stay);
|
||||
buf.writeInt(fadeOut);
|
||||
break;
|
||||
case HIDE_OLD:
|
||||
case RESET_OLD:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
public void setAction(int action) {
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
public String getComponent() {
|
||||
return component;
|
||||
}
|
||||
|
||||
public void setComponent(String component) {
|
||||
this.component = component;
|
||||
}
|
||||
|
||||
public int getFadeIn() {
|
||||
return fadeIn;
|
||||
}
|
||||
|
||||
public void setFadeIn(int fadeIn) {
|
||||
this.fadeIn = fadeIn;
|
||||
}
|
||||
|
||||
public int getStay() {
|
||||
return stay;
|
||||
}
|
||||
|
||||
public void setStay(int stay) {
|
||||
this.stay = stay;
|
||||
}
|
||||
|
||||
public int getFadeOut() {
|
||||
return fadeOut;
|
||||
}
|
||||
|
||||
public void setFadeOut(int fadeOut) {
|
||||
this.fadeOut = fadeOut;
|
||||
}
|
||||
|
||||
public static TitlePacket hideForProtocolVersion(int protocolVersion) {
|
||||
TitlePacket packet = new TitlePacket();
|
||||
packet.setAction(protocolVersion >= ProtocolConstants.MINECRAFT_1_11 ? TitlePacket.HIDE : TitlePacket.HIDE_OLD);
|
||||
return packet;
|
||||
}
|
||||
|
||||
public static TitlePacket resetForProtocolVersion(int protocolVersion) {
|
||||
TitlePacket packet = new TitlePacket();
|
||||
packet.setAction(protocolVersion >= ProtocolConstants.MINECRAFT_1_11 ? TitlePacket.RESET : TitlePacket.RESET_OLD);
|
||||
return packet;
|
||||
}
|
||||
|
||||
public static TitlePacket timesForProtocolVersion(int protocolVersion) {
|
||||
TitlePacket packet = new TitlePacket();
|
||||
packet.setAction(protocolVersion >= ProtocolConstants.MINECRAFT_1_11 ? TitlePacket.SET_TIMES : TitlePacket.SET_TIMES_OLD);
|
||||
return packet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TitlePacket{" +
|
||||
"action=" + action +
|
||||
", component='" + component + '\'' +
|
||||
", fadeIn=" + fadeIn +
|
||||
", stay=" + stay +
|
||||
", fadeOut=" + fadeOut +
|
||||
'}';
|
||||
}
|
||||
}
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren