Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package ru.progrm_jarvis.minecraft.commons.control;

import lombok.*;
import lombok.experimental.FieldDefaults;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;

import javax.annotation.Nullable;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;

/**
* Abstract implementation of {@link PlayerControls} providing its common mechanisms.
*/
@ToString
@EqualsAndHashCode
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PROTECTED, makeFinal = true)
public abstract class AbstractPlayerControls<P extends Plugin, S extends AbstractPlayerControls.Session, E>
implements PlayerControls<P, S, E> {

/**
* Plugin whose player controls those are.
*/
@NonNull P plugin;

/**
* Whether this control session is a <i>global player container</i> or not
*/
@Getter boolean global;

/**
* Map of player's currently managed by this player controls and their current active sessions
*/
@NonNull Map<@NonNull Player, @NonNull S> sessions;

/**
* Reference to the currently set event handler of this player controls
*/
@NonNull AtomicReference<Consumer<E>> eventHandler = new AtomicReference<>();

@Override
public P getBukkitPlugin() {
return plugin;
}

// a more optimal solution without allocating unneeded Optional objects
@Override
public boolean containsPlayer(@NonNull final Player player) {
return sessions.containsKey(player);
}

// a more optimal solution without allocating unneeded Optional objects
@Override
public void removePlayer(@NonNull final Player player) {
val session = sessions.get(player);
if (session != null) session.end();
}

@Override
public Collection<? extends Player> getPlayers() {
return sessions.keySet();
}

@Override
@NonNull public S startSession(@NonNull final Player player) {
val session = createSession(player);
sessions.put(player, session);

return session;
}

@Override
public @NonNull Optional<S> getSession(@NonNull final Player player) {
return Optional.ofNullable(sessions.get(player));
}

/**
* Creates the controls session for the player specified.
*
* @param player player for whom to initialize the controls session
* @return created controls session
*
* @implSpec implementations are not required to add the created session to {@link #sessions}
* as this is done by this method's caller
*
* @see Session#startSession(Player) this method's default caller
*/
@NonNull protected abstract S createSession(Player player);

/**
* Finalizer called in {@link S#end()} in order to cleanup everything needed when the player end his session.
*
* @param session session to release
* @implSpec should <b>not</b> call to {@link S#end()} as this will (most definitely) lead to infinite recursion
*
* @implSpec implementations are not required to remove the session from {@link #sessions}
* as this is done by this method's caller
*
* @see Session#end() this method's default caller
*/
protected void releaseControls(@NonNull final S session) {}

@Override
public void subscribe(@NonNull final Consumer<E> eventHandler) {
this.eventHandler.set(eventHandler);
}

@Nullable
@Override
public Consumer<E> unsubscribe() {
return eventHandler.getAndSet(null);
}

/**
* Default session object to be used with {@link AbstractPlayerControls}.
*/
@ToString
@EqualsAndHashCode
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
@FieldDefaults(level = AccessLevel.PROTECTED, makeFinal = true)
public abstract class Session implements PlayerControls.Session {

/**
* Player whose session this one is
*/
@Getter Player player;

@Override
public void end() {
//noinspection unchecked
releaseControls((S) this);
sessions.remove(player);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package ru.progrm_jarvis.minecraft.commons.control;

import lombok.NonNull;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import ru.progrm_jarvis.minecraft.commons.player.collection.PlayerContainer;
import ru.progrm_jarvis.minecraft.commons.plugin.BukkitPluginContainer;

import javax.annotation.Nullable;
import java.util.Optional;
import java.util.function.Consumer;

/**
* An object which manages the player
*
* @param <P> type of plugin owning this player controls
* @param <S> type of session created for player whose controls are managed
* @param <E> tye of event to be called by the controls manager
*/
public interface PlayerControls<P extends Plugin, S extends PlayerControls.Session, E>
extends BukkitPluginContainer<P>, PlayerContainer {

/**
* Starts the new controls session for the player.
*
* @param player player for whom to start the controls session
* @return created controls session for the player (or the one he currently has in this player controls)
*
* @apiNote the session should be ended using {@link S#end()}
*/
@NonNull S startSession(@NonNull Player player);

@Override
default void addPlayer(@NonNull final Player player) {
startSession(player);
}

/**
* Gets the current controls session of the specified player.
*
* @param player player for whom to get the controls sessions
* @return optional of the player's current controls session or empty if it doesn't have one
*/
@NonNull Optional<S> getSession(@NonNull Player player);

@Override
default void removePlayer(@NonNull final Player player) {
getSession(player).ifPresent(Session::end);
}

@Override
default boolean containsPlayer(@NonNull final Player player) {
return getSession(player).isPresent();
}

/**
* Subscribes the event handler on this player controls' events.
*
* @param eventHandler event handler to be used whenever an event is fired
*/
void subscribe(@NonNull Consumer<E> eventHandler);

/**
* Unsubscribes the current event handler from this player controls' events.
*
* @return event handler which was unsubscribed or {@code null} if there was no subscribed event handler
*/
@Nullable Consumer<E> unsubscribe();

/**
* Session of player controls, responsible for handling the player's controls
*/
interface Session {

/**
* Gets the player manages by this controls session.
*
* @return MANAGED PLAYER
*/
Player getPlayer();

/**
* Ends this player controls session.
*/
void end();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package ru.progrm_jarvis.minecraft.commons.control.common;

import com.comphenix.packetwrapper.WrapperPlayClientHeldItemSlot;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketEvent;
import lombok.AccessLevel;
import lombok.NonNull;
import lombok.Value;
import lombok.experimental.FieldDefaults;
import lombok.val;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.Plugin;
import ru.progrm_jarvis.minecraft.commons.control.AbstractPlayerControls;

import javax.annotation.Nonnull;
import java.util.Map;
import java.util.Optional;

/**
* Player Controls using the player's hotbar scrolling
*/
public class ScrollPlayerControls<P extends Plugin>
extends AbstractPlayerControls<P, ScrollPlayerControls.Session, ScrollPlayerControls.Event> {

public ScrollPlayerControls(@NonNull final P plugin, final boolean global,
@NonNull final Map<@NonNull Player, @NonNull ScrollPlayerControls.Session> sessions) {
super(plugin, global, sessions);
}

@Override
@NonNull protected Session createSession(final Player player) {
return new Session(player);
}

/**
* Handles the slot-change packet-event sending the event if needed.
*
* @param player player for whom to handle slot change
* @param slot slot now selected by the player
*
* @implNote initializes the event object only if needed (event handler is not null)
*/
protected void handleSlotChanged(@NonNull final Player player, final int slot) {
val eventHandler = this.eventHandler.get();
if (eventHandler != null) eventHandler.accept(new Event(player, (byte) (slot - 4)));
}

/**
* Session of this scroll player controls
*/
public class Session extends AbstractPlayerControls.Session {

public Session(final Player player) {
super(player);
}
}

/**
* An event to be passed to event handler whenever a player scrolls.
*/
@Value
@FieldDefaults(level = AccessLevel.PROTECTED, makeFinal = true)
public static class Event {

/**
* Player whose event this one is
*/
Player player;

/**
* Positive value for scroll to right, negative value for scroll to left, {@code 0} for no update.
*/
byte delta;
}

/**
* Function used to fill the player's hotbar with items.
*/
@FunctionalInterface
public interface HotbarFiller {

/**
* Gets the item to be set in player's inventory at the specified slot.
*
* @param player player for whom to update the item in inventory
* @param slot value between {@code 0} and {@code 8} (inclusive), an index of hotbar slot (from left to right)
* @return non-empty optional containing a non-null item to be set in player's hotbar at the specified slot
* or empty optional if the slot should not be updated for the player
*/
Optional<@NonNull ItemStack> getItem(@Nonnull Player player, int slot);
}

/**
* Packet handler to be used to intercept incoming selected-slot-change packets.
*/
protected class SlotChangePacketHandler extends PacketAdapter {

public SlotChangePacketHandler() {
super(ScrollPlayerControls.this.plugin, PacketType.Play.Client.HELD_ITEM_SLOT);
}

@Override
public void onPacketReceiving(final PacketEvent event) {
val player = event.getPlayer();
if (sessions.containsKey(player)) {
handleSlotChanged(player, new WrapperPlayClientHeldItemSlot(event.getPacket()).getSlot());
event.setCancelled(true);
}
}
}
}