mirror of
https://gitlab.bixilon.de/bixilon/minosoft.git
synced 2025-09-15 02:15:34 -04:00
Replace all ConnectionCallbacks with Event api, add EventInvokerCallback
This commit is contained in:
parent
28e62f0f86
commit
56d40455d5
@ -20,6 +20,9 @@ import de.bixilon.minosoft.data.locale.Strings;
|
||||
import de.bixilon.minosoft.data.mappings.versions.Version;
|
||||
import de.bixilon.minosoft.data.mappings.versions.Versions;
|
||||
import de.bixilon.minosoft.logging.Log;
|
||||
import de.bixilon.minosoft.modding.event.EventInvokerCallback;
|
||||
import de.bixilon.minosoft.modding.event.events.ConnectionStateChangeEvent;
|
||||
import de.bixilon.minosoft.modding.event.events.ServerListPingArriveEvent;
|
||||
import de.bixilon.minosoft.protocol.network.Connection;
|
||||
import de.bixilon.minosoft.protocol.ping.ForgeModInfo;
|
||||
import de.bixilon.minosoft.protocol.ping.ServerListPing;
|
||||
@ -134,7 +137,8 @@ public class ServerListCell extends ListCell<Server> implements Initializable {
|
||||
if (server.getLastPing() == null) {
|
||||
server.ping();
|
||||
}
|
||||
server.getLastPing().addPingCallback(ping -> Platform.runLater(() -> {
|
||||
server.getLastPing().registerEvent(new EventInvokerCallback<ServerListPingArriveEvent>(ServerListPingArriveEvent.class, event -> Platform.runLater(() -> {
|
||||
ServerListPing ping = event.getServerListPing();
|
||||
if (server != this.server) {
|
||||
// cell does not contains us anymore
|
||||
return;
|
||||
@ -193,7 +197,7 @@ public class ServerListCell extends ListCell<Server> implements Initializable {
|
||||
canConnect = false;
|
||||
setErrorMotd(String.format("%s: %s", server.getLastPing().getLastConnectionException().getClass().getCanonicalName(), server.getLastPing().getLastConnectionException().getLocalizedMessage()));
|
||||
}
|
||||
}));
|
||||
})));
|
||||
}
|
||||
|
||||
private void resetCell() {
|
||||
@ -265,7 +269,7 @@ public class ServerListCell extends ListCell<Server> implements Initializable {
|
||||
}
|
||||
optionsConnect.setDisable(true);
|
||||
connection.connect(server.getLastPing().getAddress(), version);
|
||||
connection.addConnectionChangeCallback(this::handleConnectionCallback);
|
||||
connection.registerEvent(new EventInvokerCallback<>(this::handleConnectionCallback));
|
||||
server.addConnection(connection);
|
||||
|
||||
}
|
||||
@ -334,7 +338,8 @@ public class ServerListCell extends ListCell<Server> implements Initializable {
|
||||
dialog.showAndWait();
|
||||
}
|
||||
|
||||
private void handleConnectionCallback(Connection connection) {
|
||||
private void handleConnectionCallback(ConnectionStateChangeEvent event) {
|
||||
Connection connection = event.getConnection();
|
||||
if (!server.getConnections().contains(connection)) {
|
||||
// the card got recycled
|
||||
return;
|
||||
|
@ -15,6 +15,8 @@ package de.bixilon.minosoft.gui.main;
|
||||
|
||||
import de.bixilon.minosoft.data.locale.LocaleManager;
|
||||
import de.bixilon.minosoft.data.locale.Strings;
|
||||
import de.bixilon.minosoft.modding.event.EventInvokerCallback;
|
||||
import de.bixilon.minosoft.modding.event.events.ConnectionStateChangeEvent;
|
||||
import de.bixilon.minosoft.protocol.network.Connection;
|
||||
import javafx.application.Platform;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
@ -81,12 +83,13 @@ public class SessionListCell extends ListCell<Connection> implements Initializab
|
||||
}
|
||||
setStyle(null);
|
||||
this.connection = connection;
|
||||
connection.addConnectionChangeCallback(this::handleConnectionCallback);
|
||||
connection.registerEvent(new EventInvokerCallback<>(this::handleConnectionCallback));
|
||||
connectionId.setText(String.format("#%d", connection.getConnectionId()));
|
||||
account.setText(connection.getPlayer().getAccount().getPlayerName());
|
||||
}
|
||||
|
||||
private void handleConnectionCallback(Connection connection) {
|
||||
private void handleConnectionCallback(ConnectionStateChangeEvent event) {
|
||||
Connection connection = event.getConnection();
|
||||
if (this.connection != connection) {
|
||||
// the card got recycled
|
||||
return;
|
||||
|
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Minosoft
|
||||
* Copyright (C) 2020 Moritz Zwerger
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program.If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
|
||||
*/
|
||||
|
||||
package de.bixilon.minosoft.modding.event;
|
||||
|
||||
import de.bixilon.minosoft.modding.event.events.ConnectionEvent;
|
||||
import de.bixilon.minosoft.modding.loading.Priorities;
|
||||
|
||||
public abstract class EventInvoker {
|
||||
protected final EventListener listener;
|
||||
protected final boolean ignoreCancelled;
|
||||
protected final Priorities priority;
|
||||
|
||||
public EventInvoker(boolean ignoreCancelled, Priorities priority, EventListener listener) {
|
||||
this.ignoreCancelled = ignoreCancelled;
|
||||
this.listener = listener;
|
||||
this.priority = priority;
|
||||
}
|
||||
|
||||
public boolean isIgnoreCancelled() {
|
||||
return ignoreCancelled;
|
||||
}
|
||||
|
||||
public Priorities getPriority() {
|
||||
return priority;
|
||||
}
|
||||
|
||||
public abstract void invoke(ConnectionEvent event);
|
||||
|
||||
public abstract Class<? extends ConnectionEvent> getEventType();
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Minosoft
|
||||
* Copyright (C) 2020 Moritz Zwerger
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program.If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
|
||||
*/
|
||||
|
||||
package de.bixilon.minosoft.modding.event;
|
||||
|
||||
import de.bixilon.minosoft.modding.event.events.ConnectionEvent;
|
||||
import de.bixilon.minosoft.modding.loading.Priorities;
|
||||
|
||||
public class EventInvokerCallback<V extends ConnectionEvent> extends EventInvoker {
|
||||
private final InvokerCallback<V> callback;
|
||||
|
||||
Class<? extends ConnectionEvent> eventType = ConnectionEvent.class;
|
||||
|
||||
public EventInvokerCallback(boolean ignoreCancelled, InvokerCallback<V> callback) {
|
||||
super(ignoreCancelled, Priorities.NORMAL, null);
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
|
||||
// if you need instant fireing support
|
||||
public EventInvokerCallback(Class<? extends ConnectionEvent> eventType, InvokerCallback<V> callback) {
|
||||
this(false, callback);
|
||||
this.eventType = eventType; // ToDo: how to get the class of V? seems to be impossible
|
||||
}
|
||||
|
||||
public EventInvokerCallback(InvokerCallback<V> callback) {
|
||||
this(false, callback);
|
||||
}
|
||||
|
||||
public void invoke(ConnectionEvent event) {
|
||||
if (eventType != event.getClass()) {
|
||||
return;
|
||||
}
|
||||
callback.handle((V) event);
|
||||
}
|
||||
|
||||
public Class<? extends ConnectionEvent> getEventType() {
|
||||
return eventType;
|
||||
}
|
||||
|
||||
public interface InvokerCallback<V> {
|
||||
void handle(V event);
|
||||
}
|
||||
}
|
@ -16,23 +16,23 @@ package de.bixilon.minosoft.modding.event;
|
||||
import de.bixilon.minosoft.modding.event.events.CancelableEvent;
|
||||
import de.bixilon.minosoft.modding.event.events.ConnectionEvent;
|
||||
import de.bixilon.minosoft.modding.event.events.annotations.EventHandler;
|
||||
import de.bixilon.minosoft.modding.loading.Priorities;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public class EventMethod {
|
||||
private final EventHandler annotation;
|
||||
private final EventListener listener;
|
||||
public class EventInvokerMethod extends EventInvoker {
|
||||
private final Method method;
|
||||
private final Class<? extends ConnectionEvent> eventType;
|
||||
|
||||
public EventMethod(EventHandler annotation, EventListener listener, Method method) {
|
||||
this.annotation = annotation;
|
||||
this.listener = listener;
|
||||
public EventInvokerMethod(boolean ignoreCancelled, Priorities priority, EventListener listener, Method method) {
|
||||
super(ignoreCancelled, priority, listener);
|
||||
this.method = method;
|
||||
eventType = (Class<? extends ConnectionEvent>) method.getParameters()[0].getType();
|
||||
}
|
||||
|
||||
public EventHandler getAnnotation() {
|
||||
return annotation;
|
||||
public EventInvokerMethod(EventHandler annotation, EventListener listener, Method method) {
|
||||
this(annotation.ignoreCancelled(), annotation.priority(), listener, method);
|
||||
}
|
||||
|
||||
public Method getMethod() {
|
||||
@ -43,7 +43,7 @@ public class EventMethod {
|
||||
if (!method.getParameters()[0].getType().isAssignableFrom(event.getClass())) {
|
||||
return;
|
||||
}
|
||||
if (!annotation.ignoreCancelled() && event instanceof CancelableEvent cancelableEvent && cancelableEvent.isCancelled()) {
|
||||
if (!ignoreCancelled && event instanceof CancelableEvent cancelableEvent && cancelableEvent.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@ -52,4 +52,8 @@ public class EventMethod {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public Class<? extends ConnectionEvent> getEventType() {
|
||||
return eventType;
|
||||
}
|
||||
}
|
@ -23,16 +23,16 @@ import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
|
||||
public class EventManager {
|
||||
private final HashSet<EventMethod> globalEventListeners = new HashSet<>();
|
||||
private final HashMap<HashSet<ServerAddressValidator>, HashSet<EventMethod>> specificEventListeners = new HashMap<>();
|
||||
private final HashSet<EventInvoker> globalEventListeners = new HashSet<>();
|
||||
private final HashMap<HashSet<ServerAddressValidator>, HashSet<EventInvoker>> specificEventListeners = new HashMap<>();
|
||||
|
||||
public void registerGlobalListener(EventListener listener) {
|
||||
globalEventListeners.addAll(getEventMethods(listener));
|
||||
}
|
||||
|
||||
private HashSet<EventMethod> getEventMethods(EventListener listener) {
|
||||
private HashSet<EventInvoker> getEventMethods(EventListener listener) {
|
||||
Class<? extends EventListener> clazz = listener.getClass();
|
||||
HashSet<EventMethod> eventMethods = new HashSet<>();
|
||||
HashSet<EventInvoker> eventInvokers = new HashSet<>();
|
||||
for (Method method : clazz.getMethods()) {
|
||||
EventHandler annotation = method.getAnnotation(EventHandler.class);
|
||||
if (annotation == null) {
|
||||
@ -44,12 +44,12 @@ public class EventManager {
|
||||
if (!ConnectionEvent.class.isAssignableFrom(method.getParameters()[0].getType())) {
|
||||
continue;
|
||||
}
|
||||
eventMethods.add(new EventMethod(annotation, listener, method));
|
||||
eventInvokers.add(new EventInvokerMethod(annotation, listener, method));
|
||||
}
|
||||
return eventMethods;
|
||||
return eventInvokers;
|
||||
}
|
||||
|
||||
public HashSet<EventMethod> getGlobalEventListeners() {
|
||||
public HashSet<EventInvoker> getGlobalEventListeners() {
|
||||
return globalEventListeners;
|
||||
}
|
||||
|
||||
@ -61,7 +61,7 @@ public class EventManager {
|
||||
specificEventListeners.put(serverAddresses, getEventMethods(listener));
|
||||
}
|
||||
|
||||
public HashMap<HashSet<ServerAddressValidator>, HashSet<EventMethod>> getSpecificEventListeners() {
|
||||
public HashMap<HashSet<ServerAddressValidator>, HashSet<EventInvoker>> getSpecificEventListeners() {
|
||||
return specificEventListeners;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Minosoft
|
||||
* Copyright (C) 2020 Moritz Zwerger
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program.If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
|
||||
*/
|
||||
|
||||
package de.bixilon.minosoft.modding.event.events;
|
||||
|
||||
import de.bixilon.minosoft.modding.event.events.annotations.Unsafe;
|
||||
import de.bixilon.minosoft.protocol.network.Connection;
|
||||
import de.bixilon.minosoft.protocol.protocol.ConnectionStates;
|
||||
|
||||
/**
|
||||
* Fired when the connection state was just changed
|
||||
*/
|
||||
@Unsafe
|
||||
public class ConnectionStateChangeEvent extends ConnectionEvent {
|
||||
private final ConnectionStates previousState;
|
||||
private final ConnectionStates currentState;
|
||||
|
||||
public ConnectionStateChangeEvent(Connection connection, ConnectionStates previousState, ConnectionStates currentState) {
|
||||
super(connection);
|
||||
this.previousState = previousState;
|
||||
this.currentState = currentState;
|
||||
}
|
||||
|
||||
public ConnectionStates getPreviousState() {
|
||||
return previousState;
|
||||
}
|
||||
|
||||
public ConnectionStates getCurrentState() {
|
||||
return currentState;
|
||||
}
|
||||
}
|
@ -11,10 +11,26 @@
|
||||
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
|
||||
*/
|
||||
|
||||
package de.bixilon.minosoft.gui.main;
|
||||
package de.bixilon.minosoft.modding.event.events;
|
||||
|
||||
import de.bixilon.minosoft.protocol.network.Connection;
|
||||
import de.bixilon.minosoft.protocol.ping.ServerListPing;
|
||||
|
||||
public interface ConnectionChangeCallback {
|
||||
void handle(Connection connection);
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Fired when a ping arrives from the server or the ping already arrived and the event got registered to late
|
||||
*/
|
||||
public class ServerListPingArriveEvent extends ConnectionEvent {
|
||||
private final ServerListPing serverListPing;
|
||||
|
||||
public ServerListPingArriveEvent(Connection connection, ServerListPing serverListPing) {
|
||||
super(connection);
|
||||
this.serverListPing = serverListPing;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ServerListPing getServerListPing() {
|
||||
return serverListPing;
|
||||
}
|
||||
}
|
@ -13,5 +13,9 @@
|
||||
|
||||
package de.bixilon.minosoft.modding.event.events.annotations;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Unsafe {
|
||||
}
|
||||
|
@ -21,21 +21,16 @@ import de.bixilon.minosoft.data.mappings.recipes.Recipes;
|
||||
import de.bixilon.minosoft.data.mappings.versions.Version;
|
||||
import de.bixilon.minosoft.data.mappings.versions.VersionMapping;
|
||||
import de.bixilon.minosoft.data.mappings.versions.Versions;
|
||||
import de.bixilon.minosoft.gui.main.ConnectionChangeCallback;
|
||||
import de.bixilon.minosoft.logging.Log;
|
||||
import de.bixilon.minosoft.logging.LogLevels;
|
||||
import de.bixilon.minosoft.modding.event.EventMethod;
|
||||
import de.bixilon.minosoft.modding.event.events.CancelableEvent;
|
||||
import de.bixilon.minosoft.modding.event.events.ConnectionEvent;
|
||||
import de.bixilon.minosoft.modding.event.events.PacketReceiveEvent;
|
||||
import de.bixilon.minosoft.modding.event.events.PacketSendEvent;
|
||||
import de.bixilon.minosoft.modding.event.EventInvoker;
|
||||
import de.bixilon.minosoft.modding.event.events.*;
|
||||
import de.bixilon.minosoft.protocol.packets.ClientboundPacket;
|
||||
import de.bixilon.minosoft.protocol.packets.ServerboundPacket;
|
||||
import de.bixilon.minosoft.protocol.packets.serverbound.handshaking.PacketHandshake;
|
||||
import de.bixilon.minosoft.protocol.packets.serverbound.login.PacketLoginStart;
|
||||
import de.bixilon.minosoft.protocol.packets.serverbound.status.PacketStatusPing;
|
||||
import de.bixilon.minosoft.protocol.packets.serverbound.status.PacketStatusRequest;
|
||||
import de.bixilon.minosoft.protocol.ping.PingCallback;
|
||||
import de.bixilon.minosoft.protocol.ping.ServerListPing;
|
||||
import de.bixilon.minosoft.protocol.protocol.*;
|
||||
import de.bixilon.minosoft.util.DNSUtil;
|
||||
@ -43,7 +38,6 @@ import de.bixilon.minosoft.util.ServerAddress;
|
||||
import org.xbill.DNS.TextParseException;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
@ -55,9 +49,7 @@ public class Connection {
|
||||
final PacketSender sender = new PacketSender(this);
|
||||
final LinkedBlockingQueue<ClientboundPacket> handlingQueue = new LinkedBlockingQueue<>();
|
||||
final VelocityHandler velocityHandler = new VelocityHandler(this);
|
||||
final HashSet<PingCallback> pingCallbacks = new HashSet<>();
|
||||
final HashSet<ConnectionChangeCallback> connectionChangeCallbacks = new HashSet<>();
|
||||
final LinkedList<EventMethod> eventListeners = new LinkedList<>();
|
||||
final LinkedList<EventInvoker> eventListeners = new LinkedList<>();
|
||||
final int connectionId;
|
||||
final Player player;
|
||||
final String hostname;
|
||||
@ -282,15 +274,6 @@ public class Connection {
|
||||
return connectionId;
|
||||
}
|
||||
|
||||
public void addPingCallback(PingCallback callback) {
|
||||
if (getConnectionState() == ConnectionStates.FAILED || getConnectionState() == ConnectionStates.FAILED_NO_RETRY || lastPing != null) {
|
||||
// ping done
|
||||
callback.handle(lastPing);
|
||||
return;
|
||||
}
|
||||
pingCallbacks.add(callback);
|
||||
}
|
||||
|
||||
public ConnectionStates getConnectionState() {
|
||||
return state;
|
||||
}
|
||||
@ -320,7 +303,7 @@ public class Connection {
|
||||
if (a == null || b == null) {
|
||||
return 0;
|
||||
}
|
||||
return -(b.getAnnotation().priority().ordinal() - a.getAnnotation().priority().ordinal());
|
||||
return -(b.getPriority().ordinal() - a.getPriority().ordinal());
|
||||
});
|
||||
// connection established, starting threads and logging in
|
||||
startHandlingThread();
|
||||
@ -370,11 +353,7 @@ public class Connection {
|
||||
case FAILED_NO_RETRY -> handlePingCallbacks(null);
|
||||
}
|
||||
// handle callbacks
|
||||
connectionChangeCallbacks.forEach((callback -> callback.handle(this)));
|
||||
}
|
||||
|
||||
public HashSet<PingCallback> getPingCallbacks() {
|
||||
return pingCallbacks;
|
||||
fireEvent(new ConnectionStateChangeEvent(this, previousState, state));
|
||||
}
|
||||
|
||||
public int getDesiredVersionNumber() {
|
||||
@ -387,21 +366,23 @@ public class Connection {
|
||||
|
||||
public void handlePingCallbacks(@Nullable ServerListPing ping) {
|
||||
this.lastPing = ping;
|
||||
pingCallbacks.forEach((callback -> callback.handle(ping)));
|
||||
fireEvent(new ServerListPingArriveEvent(this, ping));
|
||||
}
|
||||
|
||||
public void registerEvent(EventInvoker method) {
|
||||
eventListeners.add(method);
|
||||
if (method.getEventType() == ServerListPingArriveEvent.class) {
|
||||
if (getConnectionState() == ConnectionStates.FAILED || getConnectionState() == ConnectionStates.FAILED_NO_RETRY || lastPing != null) {
|
||||
// ping done
|
||||
method.invoke(new ServerListPingArriveEvent(this, lastPing));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Exception getLastConnectionException() {
|
||||
return (lastException != null) ? lastException : network.getLastException();
|
||||
}
|
||||
|
||||
public void addConnectionChangeCallback(ConnectionChangeCallback callback) {
|
||||
connectionChangeCallbacks.add(callback);
|
||||
}
|
||||
|
||||
public HashSet<ConnectionChangeCallback> getConnectionChangeCallbacks() {
|
||||
return connectionChangeCallbacks;
|
||||
}
|
||||
|
||||
public ServerListPing getLastPing() {
|
||||
return lastPing;
|
||||
}
|
||||
|
@ -1,18 +0,0 @@
|
||||
/*
|
||||
* Minosoft
|
||||
* Copyright (C) 2020 Moritz Zwerger
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program.If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
|
||||
*/
|
||||
|
||||
package de.bixilon.minosoft.protocol.ping;
|
||||
|
||||
public interface PingCallback {
|
||||
void handle(ServerListPing ping);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user