Skip to content

Commit 860999f

Browse files
0utplayderklaro
andauthored
feat: allow to include disconnect reason in bridge messages (#1399)
### Motivation When players are kicked from e.g. a Lobby service, the message the Lobby kicked them with is lost. This is a problem when having issues with version mismatches or plugins kicking someone from a fallback as the kick message is lost there too. ### Modification When getting kicked from a service and no other fallback is available the player will be kicked with the kick message of the downstream service. If there is no kick message, the newly introduced "server-kick-no-other-hub" message will be used. ### Result Kick reasons are properly usable in disconnect messages. --------- Co-authored-by: Pasqual Koschmieder <git@derklaro.dev>
1 parent 3840b15 commit 860999f

7 files changed

Lines changed: 306 additions & 130 deletions

File tree

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/*
2+
* Copyright 2019-2024 CloudNetService team & contributors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package eu.cloudnetservice.ext.bungee;
18+
19+
import java.util.ArrayList;
20+
import java.util.regex.Pattern;
21+
import lombok.NonNull;
22+
import net.md_5.bungee.api.chat.BaseComponent;
23+
import net.md_5.bungee.api.chat.TextComponent;
24+
25+
public final class BungeeComponentUtil {
26+
27+
private BungeeComponentUtil() {
28+
throw new UnsupportedOperationException();
29+
}
30+
31+
/**
32+
* Replaces all occurrences of the given pattern in the given component and its children with the given replacement.
33+
*
34+
* @param component the component to replace the occurrences of the given pattern in.
35+
* @param pattern the pattern to find and replace in the given component.
36+
* @param replacement the replacement to use for the pattern in the component.
37+
* @return a component with all occurrences of the given pattern replaced.
38+
* @throws NullPointerException if the given component, matcher or replacement is null.
39+
*/
40+
public static @NonNull BaseComponent replaceText(
41+
@NonNull BaseComponent component,
42+
@NonNull Pattern pattern,
43+
@NonNull BaseComponent replacement
44+
) {
45+
var modifiedComponent = component;
46+
var oldExtra = component.getExtra();
47+
var newExtra = new ArrayList<BaseComponent>();
48+
49+
if (component instanceof TextComponent textComponent) {
50+
var lastReplacedIndex = 0;
51+
var text = textComponent.getText();
52+
var matcher = pattern.matcher(text);
53+
while (matcher.find()) {
54+
if (matcher.start() == 0) {
55+
if (matcher.end() == text.length()) {
56+
// the component is a full match against the given pattern, we can
57+
// just replace the current component with the replacement component
58+
modifiedComponent = replacement.duplicate();
59+
modifiedComponent.copyFormatting(component);
60+
61+
var replacementExtra = replacement.getExtra();
62+
if (replacementExtra != null && !replacementExtra.isEmpty()) {
63+
newExtra.addAll(replacement.getExtra());
64+
}
65+
} else {
66+
// not a full match, but because the match is at the first index, we
67+
// just use an empty component as the replacement and add the replacement
68+
// component as its first extra component
69+
modifiedComponent = new TextComponent();
70+
newExtra.add(replacement);
71+
}
72+
} else {
73+
if (modifiedComponent == component) {
74+
// first match, initialize the target component & add
75+
// the text up until the first match into the component
76+
modifiedComponent = component.duplicate();
77+
var prefixText = text.substring(0, matcher.start());
78+
((TextComponent) modifiedComponent).setText(prefixText);
79+
} else if (lastReplacedIndex < matcher.start()) {
80+
// not the first match, add the literal text between the last match
81+
// and the current match as an extra of the new component
82+
var middleText = text.substring(lastReplacedIndex, matcher.start());
83+
newExtra.add(new TextComponent(middleText));
84+
}
85+
86+
// add the replacement for the current matched token as an
87+
// extra of the new component we're building
88+
newExtra.add(replacement.duplicate());
89+
}
90+
91+
lastReplacedIndex = matcher.end();
92+
}
93+
94+
if (lastReplacedIndex > 0 && lastReplacedIndex < text.length()) {
95+
// copy over the trailing text from the original component
96+
var trailingText = text.substring(lastReplacedIndex);
97+
newExtra.add(new TextComponent(trailingText));
98+
}
99+
}
100+
101+
// replace the text in all extra components of the current component
102+
if (oldExtra != null && !oldExtra.isEmpty()) {
103+
for (var extra : oldExtra) {
104+
var replaced = replaceText(extra, pattern, replacement);
105+
newExtra.add(replaced);
106+
}
107+
}
108+
109+
// only set extra components if we really constructed some, bungee only
110+
// checks if extra is null during serialisation, not if the list is empty
111+
// which results in a decoding error on the client later on
112+
if (!newExtra.isEmpty()) {
113+
modifiedComponent.setExtra(newExtra);
114+
}
115+
116+
return modifiedComponent;
117+
}
118+
}

gradle/libs.versions.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ logback = "1.5.16"
5959

6060
# platform api versions
6161
sponge = "9.0.0"
62-
velocity = "3.3.0-SNAPSHOT"
62+
velocity = "3.4.0-SNAPSHOT"
6363
waterdogpe = "1.2.4"
6464
nukkitX = "1.0-SNAPSHOT"
6565
minestom = "ccea53ac44"

modules/bridge/api/src/main/java/eu/cloudnetservice/modules/bridge/config/BridgeConfiguration.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ public final class BridgeConfiguration {
4747
.put("proxy-join-cancel-because-permission", "§7You do not have the required permissions to join this proxy.")
4848
.put("proxy-join-cancel-because-maintenance", "§7This proxy is currently in maintenance mode.")
4949
.put("proxy-join-disconnect-because-no-hub", "§cThere is currently no hub server you can connect to.")
50+
.put("server-kick-no-other-hub", "§cThere is currently no hub server you can connect to.")
5051
.put("command-cloud-sub-command-no-permission", "§7You are not allowed to use §b%command%.")
5152
.put("already-connected", "§cYou are already connected to this network!")
5253
.put("error-connecting-to-server", "§cUnable to connect to %server%: %reason%")

modules/bridge/impl/src/main/java/eu/cloudnetservice/modules/bridge/impl/node/CloudNetBridgeModule.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,30 @@ public void initModule(
200200
.build());
201201
}
202202

203+
// todo: remove in a release after 4.0.0-RC11
204+
@ModuleTask(lifecycle = ModuleLifeCycle.STARTED)
205+
public void updateBridgeConfiguration() {
206+
var config = DocumentFactory.json().parse(this.configPath());
207+
var localizedMessages = config.readMutableDocument("localizedMessages");
208+
var defaultMessages = localizedMessages.readMutableDocument("default");
209+
210+
// if there is no entry in the default messages with the key -> add a new entry
211+
// it is important to check on json level here, as we want to distinguish between the key being present
212+
// with a null value and the key not being present at all
213+
if (!defaultMessages.contains("server-kick-no-other-hub")) {
214+
defaultMessages.append(
215+
"server-kick-no-other-hub",
216+
BridgeConfiguration.DEFAULT_MESSAGES.get("default").get("server-kick-no-other-hub"));
217+
218+
// we need to update from the inside out
219+
localizedMessages.append("default", defaultMessages);
220+
config.append("localizedMessages", localizedMessages);
221+
222+
// write the changes to the configuration
223+
this.writeConfig(config);
224+
}
225+
}
226+
203227
@ModuleTask(lifecycle = ModuleLifeCycle.STARTED)
204228
public void registerCommand(@NonNull CommandProvider commandProvider) {
205229
// register the bridge command

modules/bridge/impl/src/main/java/eu/cloudnetservice/modules/bridge/impl/platform/PlatformBridgeManagement.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,10 @@ public void postInit() {
358358
this.selfTask = this.taskProvider.serviceTask(this.wrapperConfig.serviceConfiguration().serviceId().taskName());
359359
}
360360

361+
public @Nullable ProxyFallbackConfiguration currentFallbackConfiguration() {
362+
return this.currentFallbackConfiguration;
363+
}
364+
361365
public @NonNull NetworkServiceInfo ownNetworkServiceInfo() {
362366
return this.ownNetworkServiceInfo;
363367
}

0 commit comments

Comments
 (0)