Revision of the Event system

- new direct Event system inspired by FabricMC
- all current events adapted to the new system
- removed EventManager, Listener, Cancellable and EventHandler in favor of the new thing
- Changed the event invocations in ClientSession and Player
This commit is contained in:
GrizzlT 2021-08-14 13:26:23 +02:00
parent b8ea419dfc
commit 0e25e4e960
No known key found for this signature in database
GPG Key ID: 82A1B63E9F5F6A50
24 changed files with 595 additions and 345 deletions

1
.gitignore vendored
View File

@ -1,4 +1,5 @@
.idea/
target/
*.iml

View File

@ -39,7 +39,6 @@ import org.json.simple.parser.ParseException;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.loohp.limbo.events.EventsManager;
import com.loohp.limbo.server.ServerConnection;
import com.loohp.limbo.server.packets.Packet;
import com.loohp.limbo.server.packets.PacketIn;
@ -124,7 +123,6 @@ public class Limbo {
private ServerProperties properties;
private PluginManager pluginManager;
private EventsManager eventsManager;
private PermissionsManager permissionManager;
private File pluginFolder;
@ -289,8 +287,6 @@ public class Limbo {
permissionManager = new PermissionsManager();
permissionManager.loadDefaultPermissionFile(permissionFile);
eventsManager = new EventsManager();
pluginFolder = new File("plugins");
pluginFolder.mkdirs();
@ -353,10 +349,6 @@ public class Limbo {
return internalDataFolder;
}
public EventsManager getEventsManager() {
return eventsManager;
}
public File getPluginFolder() {
return pluginFolder;
}
@ -496,7 +488,7 @@ public class Limbo {
}
public String buildLegacyPingResponse(String version, BaseComponent[] motd, int maxPlayers, int playersOnline) {
String begin = "§1";
String begin = "<EFBFBD>1";
return String.join("\00", begin, "127", version, String.join("", Arrays.asList(motd).stream().map(each -> each.toLegacyText()).collect(Collectors.toList())), String.valueOf(playersOnline), String.valueOf(maxPlayers));
}

View File

@ -1,9 +0,0 @@
package com.loohp.limbo.events;
public interface Cancellable {
public void setCancelled(boolean cancelled);
public boolean isCancelled();
}

View File

@ -1,5 +0,0 @@
package com.loohp.limbo.events;
public abstract class Event {
}

View File

@ -1,14 +0,0 @@
package com.loohp.limbo.events;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EventHandler {
EventPriority priority() default EventPriority.NORMAL;
}

View File

@ -1,37 +0,0 @@
package com.loohp.limbo.events;
public enum EventPriority {
LOWEST(0),
LOW(1),
NORMAL(2),
HIGH(3),
HIGHEST(4),
MONITOR(5);
int order;
EventPriority(int order) {
this.order = order;
}
public int getOrder() {
return order;
}
public static EventPriority getByOrder(int order) {
for (EventPriority each : EventPriority.values()) {
if (each.getOrder() == order) {
return each;
}
}
return null;
}
public static EventPriority[] getPrioritiesInOrder() {
EventPriority[] array = new EventPriority[EventPriority.values().length];
for (int i = 0; i < array.length; i++) {
array[i] = EventPriority.getByOrder(i);
}
return array;
}
}

View File

@ -1,58 +0,0 @@
package com.loohp.limbo.events;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import com.loohp.limbo.plugins.LimboPlugin;
public class EventsManager {
private List<ListenerPair> listeners;
public EventsManager() {
listeners = new ArrayList<>();
}
public <T extends Event> T callEvent(T event) {
for (EventPriority priority : EventPriority.getPrioritiesInOrder()) {
for (ListenerPair entry : listeners) {
Listener listener = entry.listener;
for (Method method : listener.getClass().getMethods()) {
if (method.isAnnotationPresent(EventHandler.class)) {
if (method.getAnnotation(EventHandler.class).priority().equals(priority)) {
if (method.getParameterCount() == 1 && method.getParameterTypes()[0].equals(event.getClass())) {
try {
method.invoke(listener, event);
} catch (Exception e) {
System.err.println("Error while passing " + event.getClass().getCanonicalName() + " to the plugin \"" + entry.plugin.getName() + "\"");
e.printStackTrace();
}
}
}
}
}
}
}
return event;
}
public void registerEvents(LimboPlugin plugin, Listener listener) {
listeners.add(new ListenerPair(plugin, listener));
}
public void unregisterAllListeners(LimboPlugin plugin) {
listeners.removeIf(each -> each.plugin.equals(plugin));
}
protected static class ListenerPair {
public LimboPlugin plugin;
public Listener listener;
public ListenerPair(LimboPlugin plugin, Listener listener) {
this.plugin = plugin;
this.listener = listener;
}
}
}

View File

@ -1,5 +0,0 @@
package com.loohp.limbo.events;
public interface Listener {
}

View File

@ -0,0 +1,56 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.loohp.limbo.events.api;
/**
* Base class for Event implementations.
*
* @param <T> The listener type.
* @see EventFactory
*/
public abstract class Event<T> {
/**
* The invoker field. This should be updated by the implementation to
* always refer to an instance containing all code that should be
* executed upon event emission.
*/
protected volatile T invoker;
/**
* Returns the invoker instance.
*
* <p>An "invoker" is an object which hides multiple registered
* listeners of type T under one instance of type T, executing
* them and leaving early as necessary.
*
* @return The invoker instance.
*/
public final T invoker() {
return invoker;
}
/**
* Register a listener to the event.
*
* @param listener The desired listener.
*/
public abstract void register(EventPriority eventPriority, T listener);
public void register(T listener) {
this.register(EventPriority.NORMAL, listener);
}
}

View File

@ -0,0 +1,107 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.loohp.limbo.events.api;
import com.loohp.limbo.events.impl.EventFactoryImpl;
import java.util.function.Function;
/**
* Helper for creating {@link Event} classes.
*/
public final class EventFactory {
private static boolean profilingEnabled = true;
private EventFactory() { }
/**
* @return True if events are supposed to be profiled.
*/
public static boolean isProfilingEnabled() {
return profilingEnabled;
}
/**
* Invalidate and re-create all existing "invoker" instances across
* events created by this EventFactory. Use this if, for instance,
* the profilingEnabled field changes.
*/
// TODO: Turn this into an event?
public static void invalidate() {
EventFactoryImpl.invalidate();
}
/**
* Create an "array-backed" Event instance.
*
* <p>If your factory simply delegates to the listeners without adding custom behavior,
* consider using {@linkplain #createArrayBacked(Class, Object, Function) the other overload}
* if performance of this event is critical.
*
* @param type The listener class type.
* @param invokerFactory The invoker factory, combining multiple listeners into one instance.
* @param <T> The listener type.
* @return The Event instance.
*/
public static <T> Event<T> createArrayBacked(Class<? super T> type, Function<T[], T> invokerFactory) {
return EventFactoryImpl.createArrayBacked(type, invokerFactory);
}
/**
* Create an "array-backed" Event instance with a custom empty invoker,
* for an event whose {@code invokerFactory} only delegates to the listeners.
* <ul>
* <li>If there is no listener, the custom empty invoker will be used.</li>
* <li><b>If there is only one listener, that one will be used as the invoker
* and the factory will not be called.</b></li>
* <li>Only when there are at least two listeners will the factory be used.</li>
* </ul>
*
* <p>Having a custom empty invoker (of type (...) -&gt; {}) increases performance
* relative to iterating over an empty array; however, it only really matters
* if the event is executed thousands of times a second.
*
* @param type The listener class type.
* @param emptyInvoker The custom empty invoker.
* @param invokerFactory The invoker factory, combining multiple listeners into one instance.
* @param <T> The listener type.
* @return The Event instance.
*/
public static <T> Event<T> createArrayBacked(Class<T> type, T emptyInvoker, Function<T[], T> invokerFactory) {
return createArrayBacked(type, listeners -> {
if (listeners.length == 0) {
return emptyInvoker;
} else if (listeners.length == 1) {
return listeners[0];
} else {
return invokerFactory.apply(listeners);
}
});
}
/**
* Get the listener object name. This can be used in debugging/profiling
* scenarios.
*
* @param handler The listener object.
* @return The listener name.
*/
public static String getHandlerName(Object handler) {
return handler.getClass().getName();
}
}

View File

@ -0,0 +1,10 @@
package com.loohp.limbo.events.api;
public enum EventPriority {
LOWEST,
LOW,
NORMAL,
HIGH,
HIGHEST,
MONITOR
}

View File

@ -0,0 +1,85 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.loohp.limbo.events.impl;
import com.loohp.limbo.events.api.EventPriority;
import com.loohp.limbo.events.api.Event;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Objects;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Function;
class ArrayBackedEvent<T> extends Event<T>
{
private final Function<T[], T> invokerFactory;
private final Lock lock = new ReentrantLock();
private T[] handlers;
private final Class<? super T> invokerType;
private InvokerWrapper<T>[] handlersWrapped;
@SuppressWarnings("unchecked")
ArrayBackedEvent(Class<? super T> type, Function<T[], T> invokerFactory) {
this.invokerType = type;
this.invokerFactory = invokerFactory;
this.handlers = (T[]) Array.newInstance(type, 0);
this.handlersWrapped = new InvokerWrapper[0];
update();
}
void update() {
this.invoker = invokerFactory.apply(handlers);
}
@SuppressWarnings("unchecked")
@Override
public void register(EventPriority eventPriority, T listener) {
Objects.requireNonNull(listener, "Tried to register a null listener!");
lock.lock();
try {
handlersWrapped = Arrays.copyOf(handlersWrapped, handlersWrapped.length + 1);
handlersWrapped[handlersWrapped.length - 1] = new InvokerWrapper<>(eventPriority, listener);
Arrays.sort(handlersWrapped, Comparator.comparing(wrapper -> wrapper.priority));
handlers = Arrays.stream(handlersWrapped).map(InvokerWrapper::getInvoker).toArray(size -> (T[]) Array.newInstance(this.invokerType, size));
update();
} finally {
lock.unlock();
}
}
static class InvokerWrapper<T> {
public EventPriority priority;
public T invoker;
public InvokerWrapper(EventPriority priority, T invoker) {
this.priority = priority;
this.invoker = invoker;
}
public T getInvoker() {
return this.invoker;
}
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.loohp.limbo.events.impl;
import com.loohp.limbo.events.api.Event;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
// As required by the Apache License, Version 2.0 we are required to
// mention that we decided to delete the second method in this class.
// It was considered unnecessary code by us and thus remove, (this also influences the imports)
public final class EventFactoryImpl {
private static final List<ArrayBackedEvent<?>> ARRAY_BACKED_EVENTS = new ArrayList<>();
private EventFactoryImpl() { }
public static void invalidate() {
ARRAY_BACKED_EVENTS.forEach(ArrayBackedEvent::update);
}
public static <T> Event<T> createArrayBacked(Class<? super T> type, Function<T[], T> invokerFactory) {
ArrayBackedEvent<T> event = new ArrayBackedEvent<>(type, invokerFactory);
ARRAY_BACKED_EVENTS.add(event);
return event;
}
}

View File

@ -1,19 +1,51 @@
package com.loohp.limbo.events.player;
import com.loohp.limbo.events.Cancellable;
import com.loohp.limbo.events.api.Event;
import com.loohp.limbo.events.api.EventFactory;
import com.loohp.limbo.player.Player;
public class PlayerChatEvent extends PlayerEvent implements Cancellable {
public class PlayerChatEvent extends PlayerEvent {
/**
* This callback will be invoked every time the server receives a chat message
*
* Cancelling this event will prevent the chat message from being sent to all connected clients
*/
public static final Event<PlayerChatEventCallback> PLAYER_CHAT_EVENT = EventFactory.createArrayBacked(PlayerChatEventCallback.class, (_1, cancel) -> cancel, callbacks -> (event, _isCancelled) -> {
boolean isCancelled = _isCancelled;
for (PlayerChatEventCallback callback : callbacks) {
isCancelled = callback.onPlayerChat(event, isCancelled);
}
return isCancelled;
});
public interface PlayerChatEventCallback {
/**
* Callback for the {@link PlayerChatEvent}
* This will initiate the event as non-cancelled
* @param event the chat event
* @return true to cancel the event, otherwise return false
*/
default boolean onPlayerChat(PlayerChatEvent event) {
return this.onPlayerChat(event, false);
}
/**
* Callback for the {@link PlayerChatEvent}
* @param event the chat event
* @param isCancelled whether the event was cancelled before reaching this callback
* @return true to cancel the event, otherwise return false
*/
boolean onPlayerChat(PlayerChatEvent event, boolean isCancelled);
}
private String format;
private String message;
private boolean cancelled;
public PlayerChatEvent(Player player, String format, String message, boolean cancelled) {
public PlayerChatEvent(Player player, String format, String message) {
super(player);
this.format = format;
this.message = message;
this.cancelled = cancelled;
}
public String getFormat() {
@ -32,14 +64,4 @@ public class PlayerChatEvent extends PlayerEvent implements Cancellable {
this.message = message;
}
@Override
public void setCancelled(boolean cancelled) {
this.cancelled = cancelled;
}
@Override
public boolean isCancelled() {
return cancelled;
}
}

View File

@ -1,11 +1,10 @@
package com.loohp.limbo.events.player;
import com.loohp.limbo.events.Event;
import com.loohp.limbo.player.Player;
public class PlayerEvent extends Event {
public class PlayerEvent {
private Player player;
private final Player player;
public PlayerEvent(Player player) {
this.player = player;

View File

@ -1,22 +1,24 @@
package com.loohp.limbo.events.player;
import com.loohp.limbo.events.api.Event;
import com.loohp.limbo.events.api.EventFactory;
import com.loohp.limbo.location.Location;
import com.loohp.limbo.player.Player;
public class PlayerJoinEvent extends PlayerEvent {
public interface PlayerJoinEvent {
private Location spawnLocation;
public PlayerJoinEvent(Player player, Location spawnLoc) {
super(player);
spawnLocation = spawnLoc;
}
public Location getSpawnLocation() {
/**
* Called whenever a player joins the server
*
* This event can be used to change the spawn location of the player
*/
Event<PlayerJoinEvent> PLAYER_JOIN_EVENT = EventFactory.createArrayBacked(PlayerJoinEvent.class, (_1, location) -> location, callbacks -> (player, _spawnLocation) -> {
Location spawnLocation = _spawnLocation;
for (PlayerJoinEvent callback : callbacks) {
spawnLocation = callback.onPlayerJoin(player, spawnLocation);
}
return spawnLocation;
}
});
public void setSpawnLocation(Location spawnLocation) {
this.spawnLocation = spawnLocation;
}
Location onPlayerJoin(Player player, Location spawnLocation);
}

View File

@ -1,20 +1,50 @@
package com.loohp.limbo.events.player;
import com.loohp.limbo.events.Cancellable;
import com.loohp.limbo.events.Event;
import com.loohp.limbo.events.api.Event;
import com.loohp.limbo.events.api.EventFactory;
import com.loohp.limbo.server.ClientConnection;
import net.md_5.bungee.api.chat.BaseComponent;
public class PlayerLoginEvent extends Event implements Cancellable {
public class PlayerLoginEvent {
private ClientConnection connection;
private boolean cancelled;
/**
* Called when a player logs into the server (protocol-wise right before the Login Success Packet would be sent)
*
* Cancelling this event will prevent the player from joining the server
*/
public static Event<PlayerLoginEventCallback> PLAYER_LOGIN_EVENT = EventFactory.createArrayBacked(PlayerLoginEventCallback.class, (_1, cancel) -> cancel, callbacks -> (event, _isCancelled) -> {
boolean isCancelled = _isCancelled;
for (PlayerLoginEventCallback callback : callbacks) {
isCancelled = callback.onPlayerLoginEvent(event, isCancelled);
}
return isCancelled;
});
public interface PlayerLoginEventCallback {
/**
* Callback for the {@link PlayerLoginEvent}
* This will initiate the event as non-cancelled
* @param event the chat event
* @return true to cancel the event, otherwise return false
*/
default boolean onPlayerLoginEvent(PlayerLoginEvent event) {
return this.onPlayerLoginEvent(event, false);
}
/**
* Callback for the {@link PlayerLoginEvent}
* @param event the login event
* @param isCancelled whether the event was cancelled before reaching this callback
* @return true to cancel the event, otherwise return false
*/
boolean onPlayerLoginEvent(PlayerLoginEvent event, boolean isCancelled);
}
private final ClientConnection connection;
private BaseComponent[] cancelReason;
public PlayerLoginEvent(ClientConnection connection, boolean cancelled, BaseComponent... cancelReason) {
public PlayerLoginEvent(ClientConnection connection, BaseComponent... cancelReason) {
this.connection = connection;
this.cancelled = cancelled;
this.cancelReason = cancelReason;
}
@ -30,14 +60,4 @@ public class PlayerLoginEvent extends Event implements Cancellable {
this.cancelReason = cancelReason;
}
@Override
public void setCancelled(boolean cancelled) {
this.cancelled = cancelled;
}
@Override
public boolean isCancelled() {
return cancelled;
}
}

View File

@ -1,15 +1,48 @@
package com.loohp.limbo.events.player;
import com.loohp.limbo.events.Cancellable;
import com.loohp.limbo.events.api.Event;
import com.loohp.limbo.events.api.EventFactory;
import com.loohp.limbo.location.Location;
import com.loohp.limbo.player.Player;
/**
* Holds information for player movement events
*/
public class PlayerMoveEvent extends PlayerEvent implements Cancellable {
public class PlayerMoveEvent extends PlayerEvent {
/**
* Called when the player sends a movement packet
*
* Cancelling this event will cause the player to stay in place
*/
public static final Event<PlayerMoveEventCallback> PLAYER_MOVE_EVENT = EventFactory.createArrayBacked(PlayerMoveEventCallback.class, (_1, cancel) -> cancel, callbacks -> (event, _isCancelled) -> {
boolean isCancelled = _isCancelled;
for (PlayerMoveEventCallback callback : callbacks) {
isCancelled = callback.onPlayerMove(event, isCancelled);
}
return isCancelled;
});
public interface PlayerMoveEventCallback {
/**
* Callback for the {@link PlayerMoveEvent}
* This will initiate the event as non-cancelled
* @param event the move event
* @return true to cancel the event, otherwise return false
*/
default boolean onPlayerMove(PlayerMoveEvent event) {
return this.onPlayerMove(event, false);
}
/**
* Callback for the {@link PlayerMoveEvent}
* @param event the move event
* @param isCancelled whether the event was cancelled before reaching this callback
* @return true to cancel the event, otherwise return false
*/
boolean onPlayerMove(PlayerMoveEvent event, boolean isCancelled);
}
private boolean cancel = false;
private Location from;
private Location to;
@ -19,36 +52,6 @@ public class PlayerMoveEvent extends PlayerEvent implements Cancellable {
this.to = to;
}
/**
* Gets the cancellation state of this event. A cancelled event will not
* be executed in the server, but will still pass to other plugins
* <p>
* If a move or teleport event is cancelled, the player will be moved or
* teleported back to the Location as defined by getFrom(). This will not
* fire an event
*
* @return true if this event is cancelled
*/
@Override
public boolean isCancelled() {
return cancel;
}
/**
* Sets the cancellation state of this event. A cancelled event will not
* be executed in the server, but will still pass to other plugins
* <p>
* If a move or teleport event is cancelled, the player will be moved or
* teleported back to the Location as defined by getFrom(). This will not
* fire an event
*
* @param cancel true if you wish to cancel this event
*/
@Override
public void setCancelled(boolean cancel) {
this.cancel = cancel;
}
/**
* Gets the location this player moved from
*

View File

@ -1,11 +1,20 @@
package com.loohp.limbo.events.player;
import com.loohp.limbo.events.api.Event;
import com.loohp.limbo.events.api.EventFactory;
import com.loohp.limbo.player.Player;
public class PlayerQuitEvent extends PlayerEvent {
public interface PlayerQuitEvent {
public PlayerQuitEvent(Player player) {
super(player);
}
/**
* Called whenever a player leaves the server
*/
Event<PlayerQuitEvent> PLAYER_QUIT_EVENT = EventFactory.createArrayBacked(PlayerQuitEvent.class, _player -> {}, callbacks -> player -> {
for (PlayerQuitEvent callback : callbacks) {
callback.onPlayerQuit(player);
}
});
void onPlayerQuit(Player player);
}

View File

@ -1,11 +1,43 @@
package com.loohp.limbo.events.player;
import com.loohp.limbo.events.Cancellable;
import com.loohp.limbo.events.api.Event;
import com.loohp.limbo.events.api.EventFactory;
import com.loohp.limbo.player.Player;
public class PlayerSelectedSlotChangeEvent extends PlayerEvent implements Cancellable {
public class PlayerSelectedSlotChangeEvent extends PlayerEvent {
/**
* Called when the client sends a Held Item Change Packet
*
* Cancelling this event will force the player to keep the same slot selected
*/
public static final Event<PlayerSelectedSlotChangeEventCallback> PLAYER_SELECTED_SLOT_CHANGE_EVENT = EventFactory.createArrayBacked(PlayerSelectedSlotChangeEventCallback.class, (_1, cancel) -> cancel, callbacks -> (event, _isCancelled) -> {
boolean isCancelled = _isCancelled;
for (PlayerSelectedSlotChangeEventCallback callback : callbacks) {
isCancelled = callback.onPlayerSelectedSlotChange(event, isCancelled);
}
return isCancelled;
});
public interface PlayerSelectedSlotChangeEventCallback {
/**
* Callback for the {@link PlayerSelectedSlotChangeEvent}
* This will initiate the event as non-cancelled
* @return true to cancel the event, otherwise return false
*/
default boolean onPlayerSelectedSlotChange(PlayerSelectedSlotChangeEvent event) {
return this.onPlayerSelectedSlotChange(event, false);
}
/**
* Callback for the {@link PlayerSelectedSlotChangeEvent}
* @param isCancelled whether the event was cancelled before reaching this callback
* @return true to cancel the event, otherwise return false
*/
boolean onPlayerSelectedSlotChange(PlayerSelectedSlotChangeEvent event, boolean isCancelled);
}
private boolean cancel = false;
private byte slot;
public PlayerSelectedSlotChangeEvent(Player player, byte slot) {
@ -13,16 +45,6 @@ public class PlayerSelectedSlotChangeEvent extends PlayerEvent implements Cancel
this.slot = slot;
}
@Override
public void setCancelled(boolean cancelled) {
this.cancel = cancelled;
}
@Override
public boolean isCancelled() {
return cancel;
}
public byte getSlot() {
return slot;
}

View File

@ -1,10 +1,43 @@
package com.loohp.limbo.events.player;
import com.loohp.limbo.events.api.Event;
import com.loohp.limbo.events.api.EventFactory;
import com.loohp.limbo.location.Location;
import com.loohp.limbo.player.Player;
public class PlayerTeleportEvent extends PlayerMoveEvent {
/**
* Called when the player is about to teleport
*
* Cancelling this event will prevent the player from being teleported
*/
public static final Event<PlayerTeleportEventCallback> PLAYER_TELEPORT_EVENT = EventFactory.createArrayBacked(PlayerTeleportEventCallback.class, (_1, cancel) -> cancel, callbacks -> (event, _isCancelled) -> {
boolean isCancelled = _isCancelled;
for (PlayerTeleportEventCallback callback : callbacks) {
isCancelled = callback.onPlayerTeleport(event, isCancelled);
}
return isCancelled;
});
public interface PlayerTeleportEventCallback {
/**
* Callback for {@link PlayerTeleportEvent}
* This will initiate the event as non-cancelled
* @return true to cancel the event, otherwise return false
*/
default boolean onPlayerTeleport(PlayerTeleportEvent event) {
return this.onPlayerTeleport(event, false);
}
/**
* Callback for {@link PlayerTeleportEvent}
* @param isCancelled whether the event was cancelled before reaching this callback
* @return true to cancel the event, otherwise return false
*/
boolean onPlayerTeleport(PlayerTeleportEvent event, boolean isCancelled);
}
public PlayerTeleportEvent(Player player, Location from, Location to) {
super(player, from, to);
}

View File

@ -1,15 +1,35 @@
package com.loohp.limbo.events.status;
import com.loohp.limbo.events.api.Event;
import com.loohp.limbo.events.api.EventFactory;
import com.loohp.limbo.server.ClientConnection;
import net.md_5.bungee.api.chat.BaseComponent;
import java.awt.image.BufferedImage;
import com.loohp.limbo.events.Event;
import com.loohp.limbo.server.ClientConnection;
public class StatusPingEvent {
import net.md_5.bungee.api.chat.BaseComponent;
/**
* Called when the ping request is being constructed
*
* Can be used to alter the status information
*/
public static final Event<StatusPingEventCallback> STATUS_PING_EVENT = EventFactory.createArrayBacked(StatusPingEventCallback.class, event -> {}, callbacks -> statusPingEvent -> {
for (StatusPingEventCallback callback : callbacks) {
callback.onStatusPing(statusPingEvent);
}
});
public class StatusPingEvent extends Event {
public interface StatusPingEventCallback {
private ClientConnection connection;
/**
* Callback for the {@link StatusPingEvent}
* @return the (modified) event that will be used to construct a response
*/
void onStatusPing(StatusPingEvent statusData);
}
private final ClientConnection connection;
private String version;
private int protocol;
private BaseComponent[] motd;

View File

@ -1,30 +1,25 @@
package com.loohp.limbo.player;
import java.io.IOException;
import java.util.UUID;
import com.loohp.limbo.Limbo;
import com.loohp.limbo.events.player.PlayerChatEvent;
import com.loohp.limbo.events.player.PlayerTeleportEvent;
import com.loohp.limbo.server.ClientConnection;
import com.loohp.limbo.server.packets.PacketPlayOutChat;
import com.loohp.limbo.server.packets.PacketPlayOutGameState;
import com.loohp.limbo.server.packets.PacketPlayOutHeldItemChange;
import com.loohp.limbo.server.packets.PacketPlayOutPositionAndLook;
import com.loohp.limbo.server.packets.PacketPlayOutRespawn;
import com.loohp.limbo.commands.CommandSender;
import com.loohp.limbo.entity.DataWatcher;
import com.loohp.limbo.entity.EntityType;
import com.loohp.limbo.entity.LivingEntity;
import com.loohp.limbo.entity.DataWatcher.WatchableField;
import com.loohp.limbo.entity.DataWatcher.WatchableObjectType;
import com.loohp.limbo.entity.EntityType;
import com.loohp.limbo.entity.LivingEntity;
import com.loohp.limbo.events.player.PlayerChatEvent;
import com.loohp.limbo.events.player.PlayerTeleportEvent;
import com.loohp.limbo.location.Location;
import com.loohp.limbo.server.ClientConnection;
import com.loohp.limbo.server.packets.*;
import com.loohp.limbo.utils.GameMode;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.chat.ComponentSerializer;
import java.io.IOException;
import java.util.UUID;
public class Player extends LivingEntity implements CommandSender {
public final ClientConnection clientConnection;
@ -173,8 +168,8 @@ public class Player extends LivingEntity implements CommandSender {
@Override
public void teleport(Location location) {
PlayerTeleportEvent event = Limbo.getInstance().getEventsManager().callEvent(new PlayerTeleportEvent(this, getLocation(), location));
if (!event.isCancelled()) {
PlayerTeleportEvent event = new PlayerTeleportEvent(this, getLocation(), location);
if (!PlayerTeleportEvent.PLAYER_TELEPORT_EVENT.invoker().onPlayerTeleport(event)) {
location = event.getTo();
super.teleport(location);
try {
@ -248,8 +243,8 @@ public class Player extends LivingEntity implements CommandSender {
public void chat(String message) {
String format = "<%name%> %message%";
PlayerChatEvent event = (PlayerChatEvent) Limbo.getInstance().getEventsManager().callEvent(new PlayerChatEvent(this, format, message, false));
if (!event.isCancelled()) {
PlayerChatEvent event = new PlayerChatEvent(this, format, message);
if (!PlayerChatEvent.PLAYER_CHAT_EVENT.invoker().onPlayerChat(event)) {
String chat = event.getFormat().replace("%name%", username).replace("%message%", event.getMessage());
Limbo.getInstance().getConsole().sendMessage(chat);
for (Player each : Limbo.getInstance().getPlayers()) {

View File

@ -1,81 +1,38 @@
package com.loohp.limbo.server;
import com.loohp.limbo.Limbo;
import com.loohp.limbo.events.player.*;
import com.loohp.limbo.events.status.StatusPingEvent;
import com.loohp.limbo.file.ServerProperties;
import com.loohp.limbo.location.Location;
import com.loohp.limbo.player.Player;
import com.loohp.limbo.player.PlayerInteractManager;
import com.loohp.limbo.server.packets.*;
import com.loohp.limbo.server.packets.PacketPlayOutPlayerAbilities.PlayerAbilityFlags;
import com.loohp.limbo.server.packets.PacketPlayOutPlayerInfo.PlayerInfoAction;
import com.loohp.limbo.server.packets.PacketPlayOutPlayerInfo.PlayerInfoData;
import com.loohp.limbo.server.packets.PacketPlayOutPlayerInfo.PlayerInfoData.PlayerInfoDataAddPlayer.PlayerSkinProperty;
import com.loohp.limbo.server.packets.PacketPlayOutTabComplete.TabCompleteMatches;
import com.loohp.limbo.utils.*;
import com.loohp.limbo.utils.MojangAPIUtils.SkinResponse;
import com.loohp.limbo.world.BlockPosition;
import com.loohp.limbo.world.World;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.chat.ComponentSerializer;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;
import com.loohp.limbo.Limbo;
import com.loohp.limbo.events.player.PlayerJoinEvent;
import com.loohp.limbo.events.player.PlayerLoginEvent;
import com.loohp.limbo.events.player.PlayerMoveEvent;
import com.loohp.limbo.events.player.PlayerQuitEvent;
import com.loohp.limbo.events.player.PlayerSelectedSlotChangeEvent;
import com.loohp.limbo.events.status.StatusPingEvent;
import com.loohp.limbo.file.ServerProperties;
import com.loohp.limbo.location.Location;
import com.loohp.limbo.player.Player;
import com.loohp.limbo.player.PlayerInteractManager;
import com.loohp.limbo.server.packets.Packet;
import com.loohp.limbo.server.packets.PacketHandshakingIn;
import com.loohp.limbo.server.packets.PacketLoginInLoginStart;
import com.loohp.limbo.server.packets.PacketLoginOutDisconnect;
import com.loohp.limbo.server.packets.PacketLoginOutLoginSuccess;
import com.loohp.limbo.server.packets.PacketOut;
import com.loohp.limbo.server.packets.PacketPlayInChat;
import com.loohp.limbo.server.packets.PacketPlayInHeldItemChange;
import com.loohp.limbo.server.packets.PacketPlayInKeepAlive;
import com.loohp.limbo.server.packets.PacketPlayInPosition;
import com.loohp.limbo.server.packets.PacketPlayInPositionAndLook;
import com.loohp.limbo.server.packets.PacketPlayInRotation;
import com.loohp.limbo.server.packets.PacketPlayInTabComplete;
import com.loohp.limbo.server.packets.PacketPlayOutDeclareCommands;
import com.loohp.limbo.server.packets.PacketPlayOutDisconnect;
import com.loohp.limbo.server.packets.PacketPlayOutEntityMetadata;
import com.loohp.limbo.server.packets.PacketPlayOutGameState;
import com.loohp.limbo.server.packets.PacketPlayOutHeldItemChange;
import com.loohp.limbo.server.packets.PacketPlayOutLogin;
import com.loohp.limbo.server.packets.PacketPlayOutPlayerAbilities;
import com.loohp.limbo.server.packets.PacketPlayOutPlayerAbilities.PlayerAbilityFlags;
import com.loohp.limbo.server.packets.PacketPlayOutPlayerInfo;
import com.loohp.limbo.server.packets.PacketPlayOutPlayerInfo.PlayerInfoAction;
import com.loohp.limbo.server.packets.PacketPlayOutPlayerInfo.PlayerInfoData;
import com.loohp.limbo.server.packets.PacketPlayOutPlayerInfo.PlayerInfoData.PlayerInfoDataAddPlayer.PlayerSkinProperty;
import com.loohp.limbo.server.packets.PacketPlayOutPositionAndLook;
import com.loohp.limbo.server.packets.PacketPlayOutSpawnPosition;
import com.loohp.limbo.server.packets.PacketPlayOutTabComplete;
import com.loohp.limbo.server.packets.PacketPlayOutTabComplete.TabCompleteMatches;
import com.loohp.limbo.server.packets.PacketPlayOutUpdateViewPosition;
import com.loohp.limbo.server.packets.PacketStatusInPing;
import com.loohp.limbo.server.packets.PacketStatusInRequest;
import com.loohp.limbo.server.packets.PacketStatusOutPong;
import com.loohp.limbo.server.packets.PacketStatusOutResponse;
import com.loohp.limbo.utils.CustomStringUtils;
import com.loohp.limbo.utils.DataTypeIO;
import com.loohp.limbo.utils.DeclareCommands;
import com.loohp.limbo.utils.GameMode;
import com.loohp.limbo.utils.MojangAPIUtils;
import com.loohp.limbo.utils.MojangAPIUtils.SkinResponse;
import com.loohp.limbo.utils.NamespacedKey;
import com.loohp.limbo.world.BlockPosition;
import com.loohp.limbo.world.World;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.chat.ComponentSerializer;
public class ClientConnection extends Thread {
public static enum ClientState {
@ -186,7 +143,8 @@ public class ClientConnection extends Thread {
String str = inetAddress.getHostName() + ":" + client_socket.getPort();
Limbo.getInstance().getConsole().sendMessage("[/" + str + "] <-> Legacy Status has pinged");
ServerProperties p = Limbo.getInstance().getServerProperties();
StatusPingEvent event = Limbo.getInstance().getEventsManager().callEvent(new StatusPingEvent(this, p.getVersionString(), p.getProtocol(), ComponentSerializer.parse(p.getMotdJson()), p.getMaxPlayers(), Limbo.getInstance().getPlayers().size(), p.getFavicon().orElse(null)));
StatusPingEvent event = new StatusPingEvent(this, p.getVersionString(), p.getProtocol(), ComponentSerializer.parse(p.getMotdJson()), p.getMaxPlayers(), Limbo.getInstance().getPlayers().size(), p.getFavicon().orElse(null));
StatusPingEvent.STATUS_PING_EVENT.invoker().onStatusPing(event);
String response = Limbo.getInstance().buildLegacyPingResponse(event.getVersion(), event.getMotd(), event.getMaxPlayers(), event.getPlayersOnline());
byte[] bytes = response.getBytes(StandardCharsets.UTF_16BE);
output.writeShort(response.length());
@ -222,7 +180,8 @@ public class ClientConnection extends Thread {
Limbo.getInstance().getConsole().sendMessage("[/" + str + "] <-> Handshake Status has pinged");
}
ServerProperties p = Limbo.getInstance().getServerProperties();
StatusPingEvent event = Limbo.getInstance().getEventsManager().callEvent(new StatusPingEvent(this, p.getVersionString(), p.getProtocol(), ComponentSerializer.parse(p.getMotdJson()), p.getMaxPlayers(), Limbo.getInstance().getPlayers().size(), p.getFavicon().orElse(null)));
StatusPingEvent event = new StatusPingEvent(this, p.getVersionString(), p.getProtocol(), ComponentSerializer.parse(p.getMotdJson()), p.getMaxPlayers(), Limbo.getInstance().getPlayers().size(), p.getFavicon().orElse(null));
StatusPingEvent.STATUS_PING_EVENT.invoker().onStatusPing(event);
PacketStatusOutResponse packet = new PacketStatusOutResponse(Limbo.getInstance().buildServerListResponseJson(event.getVersion(), event.getProtocol(), event.getMotd(), event.getMaxPlayers(), event.getPlayersOnline(), event.getFavicon()));
sendPacket(packet);
} else if (packetType.equals(PacketStatusInPing.class)) {
@ -270,6 +229,12 @@ public class ClientConnection extends Thread {
String username = start.getUsername();
UUID uuid = isBungeecord ? bungeeUUID : UUID.nameUUIDFromBytes(("OfflinePlayer:" + username).getBytes(StandardCharsets.UTF_8));
PlayerLoginEvent event = new PlayerLoginEvent(this);
if (PlayerLoginEvent.PLAYER_LOGIN_EVENT.invoker().onPlayerLoginEvent(event)) {
disconnectDuringLogin(event.getCancelReason());
break;
}
PacketLoginOutLoginSuccess success = new PacketLoginOutLoginSuccess(uuid, username);
sendPacket(success);
@ -285,11 +250,6 @@ public class ClientConnection extends Thread {
}
}
PlayerLoginEvent event = Limbo.getInstance().getEventsManager().callEvent(new PlayerLoginEvent(this, false));
if (event.isCancelled()) {
disconnectDuringLogin(event.getCancelReason());
}
break;
}
} catch (Exception e) {
@ -304,8 +264,7 @@ public class ClientConnection extends Thread {
ServerProperties properties = Limbo.getInstance().getServerProperties();
Location worldSpawn = properties.getWorldSpawn();
PlayerJoinEvent joinEvent = Limbo.getInstance().getEventsManager().callEvent(new PlayerJoinEvent(player, worldSpawn));
worldSpawn = joinEvent.getSpawnLocation();
worldSpawn = PlayerJoinEvent.PLAYER_JOIN_EVENT.invoker().onPlayerJoin(player, worldSpawn);
World world = worldSpawn.getWorld();
PacketPlayOutLogin join = new PacketPlayOutLogin(player.getEntityId(), false, properties.getDefaultGamemode(), Limbo.getInstance().getWorlds().stream().map(each -> new NamespacedKey(each.getName()).toString()).collect(Collectors.toList()).toArray(new String[Limbo.getInstance().getWorlds().size()]), Limbo.getInstance().getDimensionRegistry().getCodec(), world, 0, (byte) properties.getMaxPlayers(), 8, properties.isReducedDebugInfo(), true, false, true);
@ -363,7 +322,7 @@ public class ClientConnection extends Thread {
//Limbo.getInstance().getConsole().sendMessage(packetId + " -> " + packetType);
CheckedConsumer<PlayerMoveEvent, IOException> processMoveEvent = event -> {
Location originalTo = event.getTo().clone();
if (event.isCancelled()) {
if (PlayerMoveEvent.PLAYER_MOVE_EVENT.invoker().onPlayerMove(event)) {
Location returnTo = event.getFrom();
PacketPlayOutPositionAndLook cancel = new PacketPlayOutPositionAndLook(returnTo.getX(), returnTo.getY(), returnTo.getZ(), returnTo.getYaw(), returnTo.getPitch(), 1, false);
sendPacket(cancel);
@ -386,21 +345,21 @@ public class ClientConnection extends Thread {
Location from = player.getLocation();
Location to = new Location(player.getWorld(), pos.getX(), pos.getY(), pos.getZ(), pos.getYaw(), pos.getPitch());
PlayerMoveEvent event = Limbo.getInstance().getEventsManager().callEvent(new PlayerMoveEvent(player, from, to));
PlayerMoveEvent event = new PlayerMoveEvent(player, from, to);
processMoveEvent.consume(event);
} else if (packetType.equals(PacketPlayInPosition.class)) {
PacketPlayInPosition pos = new PacketPlayInPosition(input);
Location from = player.getLocation();
Location to = new Location(player.getWorld(), pos.getX(), pos.getY(), pos.getZ(), player.getLocation().getYaw(), player.getLocation().getPitch());
PlayerMoveEvent event = Limbo.getInstance().getEventsManager().callEvent(new PlayerMoveEvent(player, from, to));
PlayerMoveEvent event = new PlayerMoveEvent(player, from, to);
processMoveEvent.consume(event);
} else if (packetType.equals(PacketPlayInRotation.class)) {
PacketPlayInRotation pos = new PacketPlayInRotation(input);
Location from = player.getLocation();
Location to = new Location(player.getWorld(), player.getLocation().getX(), player.getLocation().getY(), player.getLocation().getZ(), pos.getYaw(), pos.getPitch());
PlayerMoveEvent event = Limbo.getInstance().getEventsManager().callEvent(new PlayerMoveEvent(player, from, to));
PlayerMoveEvent event = new PlayerMoveEvent(player, from, to);
processMoveEvent.consume(event);
} else if (packetType.equals(PacketPlayInKeepAlive.class)) {
PacketPlayInKeepAlive alive = new PacketPlayInKeepAlive(input);
@ -430,8 +389,8 @@ public class ClientConnection extends Thread {
}
} else if (packetType.equals(PacketPlayInHeldItemChange.class)) {
PacketPlayInHeldItemChange change = new PacketPlayInHeldItemChange(input);
PlayerSelectedSlotChangeEvent event = Limbo.getInstance().getEventsManager().callEvent(new PlayerSelectedSlotChangeEvent(player, (byte) change.getSlot()));
if (event.isCancelled()) {
PlayerSelectedSlotChangeEvent event = new PlayerSelectedSlotChangeEvent(player, (byte) change.getSlot());
if (PlayerSelectedSlotChangeEvent.PLAYER_SELECTED_SLOT_CHANGE_EVENT.invoker().onPlayerSelectedSlotChange(event)) {
PacketPlayOutHeldItemChange cancelPacket = new PacketPlayOutHeldItemChange(player.getSelectedSlot());
sendPacket(cancelPacket);
} else if (change.getSlot() != event.getSlot()) {
@ -450,7 +409,7 @@ public class ClientConnection extends Thread {
}
}
Limbo.getInstance().getEventsManager().callEvent(new PlayerQuitEvent(player));
PlayerQuitEvent.PLAYER_QUIT_EVENT.invoker().onPlayerQuit(player);
str = inetAddress.getHostName() + ":" + client_socket.getPort() + "|" + player.getName();
Limbo.getInstance().getConsole().sendMessage("[/" + str + "] <-> Player had disconnected!");