Velocity queueing solution
1/*
2 * ProxyQueues, a Velocity queueing solution
3 * Copyright (c) 2021 James Lyne
4 *
5 * Some portions of this file were taken from https://github.com/darbyjack/DeluxeQueues
6 * These portions are Copyright (c) 2019 Glare
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining a copy
9 * of this software and associated documentation files (the "Software"), to deal
10 * in the Software without restriction, including without limitation the rights
11 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 * copies of the Software, and to permit persons to whom the Software is
13 * furnished to do so, subject to the following conditions:
14 *
15 * The above copyright notice and this permission notice shall be included in all
16 * copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 */
25
26package uk.co.notnull.proxyqueues;
27
28import cloud.commandframework.CommandManager;
29import cloud.commandframework.annotations.AnnotationParser;
30import cloud.commandframework.execution.CommandExecutionCoordinator;
31import cloud.commandframework.meta.SimpleCommandMeta;
32import cloud.commandframework.minecraft.extras.MinecraftExceptionHandler;
33import cloud.commandframework.velocity.VelocityCommandManager;
34import com.velocitypowered.api.command.CommandSource;
35import com.velocitypowered.api.event.ResultedEvent;
36import com.velocitypowered.api.event.Subscribe;
37import com.velocitypowered.api.event.connection.LoginEvent;
38import com.velocitypowered.api.event.connection.PluginMessageEvent;
39import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
40import com.velocitypowered.api.plugin.annotation.DataDirectory;
41import com.velocitypowered.api.proxy.ProxyServer;
42import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
43import com.velocitypowered.api.proxy.server.RegisteredServer;
44import net.kyori.adventure.text.ComponentLike;
45import net.kyori.adventure.text.minimessage.MiniMessage;
46import org.spongepowered.configurate.ConfigurationNode;
47import org.spongepowered.configurate.yaml.YamlConfigurationLoader;
48import uk.co.notnull.proxyqueues.api.MessageType;
49import uk.co.notnull.proxyqueues.api.ProxyQueues;
50import uk.co.notnull.proxyqueues.api.queues.QueueHandler;
51import uk.co.notnull.proxyqueues.configuration.SettingsHandler;
52import uk.co.notnull.proxyqueues.configuration.sections.ConfigOptions;
53import uk.co.notnull.proxyqueues.metrics.Metrics;
54import uk.co.notnull.proxyqueues.queues.QueueHandlerImpl;
55import org.slf4j.Logger;
56import uk.co.notnull.vanishbridge.helper.CloudHelper;
57import uk.co.notnull.vanishbridge.helper.VanishBridgeHelper;
58
59import javax.inject.Inject;
60import java.io.File;
61import java.io.IOException;
62import java.io.InputStream;
63import java.nio.file.Files;
64import java.nio.file.Path;
65import java.util.Collections;
66import java.util.Map;
67import java.util.Optional;
68import java.util.function.Function;
69
70public final class ProxyQueuesImpl implements ProxyQueues {
71 public static final MinecraftChannelIdentifier playerWaitingIdentifier =
72 MinecraftChannelIdentifier.create("proxyqueues", "player_waiting");
73
74 private static ProxyQueuesImpl instance;
75 private final SettingsHandler settingsHandler;
76 private QueueHandlerImpl queueHandler;
77 private static final MiniMessage miniMessage = MiniMessage.miniMessage();
78 private int playerLimit;
79
80 private final ProxyServer proxyServer;
81 private final Logger logger;
82 private final Path dataFolder;
83
84 @Inject
85 public ProxyQueuesImpl(ProxyServer proxy, Logger logger, @DataDirectory Path dataFolder) {
86 this.proxyServer = proxy;
87 this.logger = logger;
88 this.dataFolder = dataFolder;
89 instance = this;
90
91 createFile("config.yml");
92 createFile("messages.yml");
93
94 loadMessagesConfig();
95
96 settingsHandler = new SettingsHandler(this);
97 playerLimit = settingsHandler.getSettingsManager().getProperty(ConfigOptions.PLAYER_LIMIT);
98 }
99
100 public void loadMessagesConfig() {
101 //Message config
102 ConfigurationNode messagesConfiguration;
103
104 try {
105 messagesConfiguration = YamlConfigurationLoader.builder().file(
106 new File(dataFolder.toAbsolutePath().toString(), "messages.yml")).build().load();
107 Messages.set(messagesConfiguration);
108 } catch (IOException e) {
109 logger.error("Error loading messages.yml");
110 e.printStackTrace();
111 }
112 }
113
114 @Subscribe
115 public void onProxyInitialize(ProxyInitializeEvent event) {
116 startQueues();
117 initCommands();
118
119 if(this.getProxyServer().getPluginManager().isLoaded("prometheus-exporter")) {
120 try {
121 new Metrics(this);
122 } catch(IllegalArgumentException e) {
123 logger.warn("Failed to register prometheus metrics", e);
124 }
125 }
126
127 if(this.getProxyServer().getPluginManager().isLoaded("proxydiscord")) {
128 new ProxyDiscordHandler(this);
129 }
130
131 proxyServer.getChannelRegistrar().register(playerWaitingIdentifier);
132 new VanishBridgeHelper(proxyServer);
133 }
134
135 @Subscribe
136 public void onLogin(LoginEvent event) {
137 if(playerLimit > -1 && proxyServer.getPlayerCount() >= playerLimit &&
138 !event.getPlayer().hasPermission("proxyqueues.bypass-limit")) {
139 event.setResult(ResultedEvent.ComponentResult.denied(Messages.getComponent("errors.player-limit")));
140 }
141 }
142
143 @Subscribe
144 public void onPluginMessageFromPlayer(PluginMessageEvent event) {
145 // Don't allow spoofing of plugin messages
146 if (playerWaitingIdentifier.equals(event.getIdentifier())) {
147 event.setResult(PluginMessageEvent.ForwardResult.handled());
148 }
149 }
150
151 public void initCommands() {
152 CommandManager<CommandSource> manager = new VelocityCommandManager<>(
153 proxyServer.getPluginManager().fromInstance(this).orElseThrow(),
154 proxyServer,
155 CommandExecutionCoordinator.simpleCoordinator(),
156 Function.identity(),
157 Function.identity());
158
159 new MinecraftExceptionHandler<CommandSource>()
160 .withArgumentParsingHandler()
161 .withInvalidSenderHandler()
162 .withInvalidSyntaxHandler()
163 .withNoPermissionHandler()
164 .withCommandExecutionHandler()
165 .withDecorator(message -> message)
166 .apply(manager, p -> p);
167
168 AnnotationParser<CommandSource> annotationParser = new AnnotationParser<>(
169 manager,
170 CommandSource.class,
171 parameters -> SimpleCommandMeta.empty()
172 );
173
174 CloudHelper.registerVisiblePlayerArgument(manager);
175 annotationParser.parse(new Commands(this, manager));
176 }
177
178 /**
179 * Create a file to be used in the plugin
180 * @param name the name of the file
181 */
182 private void createFile(String name) {
183 File folder = dataFolder.toFile();
184
185 if (!folder.exists()) {
186 folder.mkdir();
187 }
188 File languageFolder = new File(folder, "languages");
189 if (!languageFolder.exists()) {
190 languageFolder.mkdirs();
191 }
192
193 File file = new File(folder, name);
194
195 if (!file.exists()) {
196 try (InputStream in = getClass().getClassLoader().getResourceAsStream(name)) {
197 Files.copy(in, file.toPath());
198 } catch (IOException ex) {
199 ex.printStackTrace();
200 }
201 }
202 }
203
204 public SettingsHandler getSettingsHandler() {
205 return this.settingsHandler;
206 }
207
208 public QueueHandler getQueueHandler() {
209 return this.queueHandler;
210 }
211
212 public void startQueues() {
213 queueHandler = new QueueHandlerImpl(settingsHandler.getSettingsManager(), this);
214 }
215
216 public ProxyServer getProxyServer() {
217 return proxyServer;
218 }
219
220 public Logger getLogger() {
221 return logger;
222 }
223
224 public File getDataFolder() {
225 return dataFolder.toFile();
226 }
227
228 public Optional<RegisteredServer> getWaitingServer() {
229 String waitingServerName = getSettingsHandler().getSettingsManager().getProperty(ConfigOptions.WAITING_SERVER);
230 return getProxyServer().getServer(waitingServerName);
231 }
232
233 public static ProxyQueuesImpl getInstance() {
234 return instance;
235 }
236
237 public void sendMessage(CommandSource player, String message) {
238 player.sendMessage(miniMessage.deserialize(message));
239 }
240
241 public void sendMessage(CommandSource player, ComponentLike message) {
242 player.sendMessage(message);
243 }
244
245 public void sendMessage(CommandSource player, MessageType messageType, String message) {
246 sendMessage(player, messageType, message, Collections.emptyMap());
247 }
248
249 public void sendMessage(CommandSource player, MessageType messageType, String message, Map<String, String> replacements) {
250 player.sendMessage(
251 miniMessage.deserialize(Messages.getPrefix(messageType) + Messages.get(message, replacements)));
252 }
253
254 public void setPlayerLimit(int playerLimit) {
255 this.playerLimit = Math.max(playerLimit, -1);
256 }
257}