io.github.portlek.input.ChatInput Maven / Gradle / Ivy
/*
* MIT License
*
* Copyright (c) 2021 Hasan Demirtaş
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package io.github.portlek.input;
import io.github.portlek.input.event.ChatEvent;
import io.github.portlek.input.event.QuitEvent;
import java.util.Arrays;
import java.util.Collection;
import java.util.EnumMap;
import java.util.HashSet;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import java.util.function.Function;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* a class created to get inputs from players.
*
* @param the input type.
* @param the input sender type.
*/
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public final class ChatInput {
/**
* the builder.
*/
@NotNull
private final ChatInput.Builder builder;
/**
* the platform.
*/
@NotNull
private final ChatPlatform platform;
/**
* the started.
*/
private final AtomicBoolean started = new AtomicBoolean(false);
/**
* the expire task.
*/
@Nullable
private ChatTask expireTask;
/**
* creates a new builder instance.
*
* @param platform the platform to create.
* @param sender the sender to create.
* @param type of the value.
* @param type of the sender.
*
* @return a newly created builder instance.
*/
@NotNull
public static Builder builder(@NotNull final ChatPlatform platform,
@NotNull final ChatSender
sender) {
return new Builder<>(platform, sender);
}
/**
* stops the chat input sequence.
*
* @param sender the sender to end.
* @param reason the reason to end.
*/
public void end(@NotNull final P sender, @NotNull final EndReason reason) {
this.started.set(false);
this.platform.unregisterListeners();
Optional.ofNullable(this.expireTask).ifPresent(ChatTask::cancel);
this.builder.getRunAfter().forEach((r, j) -> {
if (r == reason) {
j.forEach(c -> c.accept(sender));
}
});
}
/**
* runs when the sender push an input.
*
* @param event the event to apply as a send message event.
*/
public void onChat(@NotNull final ChatEvent
event) {
if (!this.started.get()) {
return;
}
final ChatSender
sender = this.builder.getSender();
if (!sender.getUniqueId().equals(event.getSender().getUniqueId())) {
return;
}
event.cancel();
final String message = event.getMessage();
final P wrapped = sender.getWrapped();
if (message.equalsIgnoreCase(this.builder.getCancel())) {
this.builder.getOnCancel().accept(wrapped);
this.end(wrapped, EndReason.PLAYER_CANCELS);
return;
}
if (this.builder.getIsValidInput().test(wrapped, message)) {
final T value = this.builder.getSetValue().apply(wrapped, message);
this.builder.getOnFinish().accept(wrapped, value);
this.end(wrapped, EndReason.FINISH);
} else {
if (this.builder.getOnInvalidInput().test(wrapped, message)) {
Optional.ofNullable(this.builder.getInvalidInputMessage())
.ifPresent(sender::sendMessage);
Optional.ofNullable(this.builder.getSendValueMessage())
.filter(s -> this.builder.isRepeat())
.ifPresent(sender::sendMessage);
}
if (!this.builder.isRepeat()) {
this.end(wrapped, EndReason.INVALID_INPUT);
}
}
}
/**
* runs when the send quits from the game.
*
* @param event the event to apply as a quit event.
*/
public void onQuit(@NotNull final QuitEvent
event) {
if (!this.started.get()) {
return;
}
final ChatSender
sender = this.builder.getSender();
if (event.getSender().getUniqueId().equals(sender.getUniqueId())) {
this.builder.getOnDisconnect().accept(sender.getWrapped());
this.end(sender.getWrapped(), EndReason.PLAYER_DISCONNECTS);
}
}
/**
* starts the chat input sequence.
*/
public void start() {
this.platform.init(this);
final ChatSender
sender = this.builder.getSender();
if (this.builder.getExpire() != -1L) {
this.expireTask = this.platform.createRunTaskLater(() -> {
if (!this.started.get()) {
return;
}
Optional.ofNullable(this.expireTask)
.filter(task -> !task.isCancelled()).map(task -> sender.getWrapped())
.ifPresent(wrapped -> {
this.builder.getOnExpire().accept(wrapped);
this.end(wrapped, EndReason.EXPIRE);
});
}, this.builder.getExpire());
}
this.started.set(true);
Optional.ofNullable(this.builder.getSendValueMessage())
.ifPresent(sender::sendMessage);
}
/**
* a builder class to create {@link ChatInput} instance.
*
* @param the value type.
* @param the input sender type.
*/
@Getter
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public static final class Builder {
/**
* the platform.
*/
@NotNull
private final ChatPlatform platform;
/**
* the run after finish.
*/
@NotNull
private final EnumMap>> runAfter = new EnumMap<>(EndReason.class);
/**
* the sender.
*/
@NotNull
private final ChatSender sender;
/**
* the cancel.
*/
@NotNull
private String cancel = "cancel";
/**
* the expire.
*/
private long expire = -1L;
/**
* the expire message.
*/
@Nullable
private Function
expireMessage;
/**
* the invalid input message.
*/
@Nullable
private String invalidInputMessage = "That is not a valid input!";
/**
* the is valid input.
*/
@NotNull
private BiPredicate
isValidInput = (p, mes) -> true;
/**
* the on cancel.
*/
@NotNull
private Consumer
onCancel = p -> {
};
/**
* the on disconnect.
*/
@NotNull
private Consumer
onDisconnect = sender -> {
};
/**
* the on expire.
*/
@NotNull
private Consumer
onExpire = p -> {
};
/**
* the on finish.
*/
@NotNull
private BiConsumer
onFinish = (p, val) -> {
};
/**
* the on invalid input.
*/
@NotNull
private BiPredicate
onInvalidInput = (p, mes) -> true;
/**
* the repeat.
*/
private boolean repeat = true;
/**
* the send value message.
*/
@Nullable
private String sendValueMessage = "Send in the chat the value";
/**
* the value.
*/
@Nullable
private T value;
/**
* the set value.
*/
@NotNull
private BiFunction
setValue = (p, mes) -> this.value;
/**
* puts the given values into {@link #runAfter}.
*
* @param runAfter the run after to put.
* @param reasons the reasons to put.
*
* @return {@code this}.
*/
@NotNull
public Builder addRunAfter(@NotNull final Consumer runAfter, @NotNull final EndReason... reasons) {
Arrays.stream(reasons)
.forEach(reason -> {
final Collection> old = this.runAfter.getOrDefault(reason, new HashSet<>());
old.add(runAfter);
this.runAfter.put(reason, old);
});
return this;
}
/**
* builds the {@link ChatInput} instance.
*
* @return a {@link ChatInput} instance.
*/
@NotNull
public ChatInput build() {
return new ChatInput<>(this, this.platform);
}
/**
* puts the given values into {@link #runAfter}.
*
* opens the given builder after finishing the chat input.
*
* @param builder the run after to put.
* @param reasons the reasons to put.
*
* @return {@code this}.
*
* @see #chainAfter(ChatInput, EndReason...)
*/
@NotNull
public Builder chainAfter(@NotNull final Builder builder, @NotNull final EndReason... reasons) {
return this.chainAfter(builder.build(), reasons);
}
/**
* puts the given values into {@link #runAfter}.
*
* opens the given chat input after finishing the chat input.
*
* @param input the input to put.
* @param reasons the reasons to put.
*
* @return {@code this}.
*
* @see #addRunAfter(Consumer, EndReason...)
*/
@NotNull
public Builder chainAfter(@NotNull final ChatInput input, @NotNull final EndReason... reasons) {
return this.addRunAfter(sender -> input.start(), Arrays.stream(reasons)
.filter(reason -> reason != EndReason.PLAYER_DISCONNECTS)
.toArray(EndReason[]::new));
}
/**
* sets {@link #value} and return {@code this}.
*
* @param value the value to set.
*
* @return {@code this}.
*/
@NotNull
public ChatInput.Builder defaultValue(@Nullable final T value) {
this.value = value;
return this;
}
/**
* sets {@link #expire} and return {@code this}.
*
* @param expire the expire to set.
*
* @return {@code this}.
*/
@NotNull
public ChatInput.Builder expire(final long expire) {
this.expire = expire;
return this;
}
/**
* sets {@link #invalidInputMessage} and return {@code this}.
*
* @param invalidInputMessage the invalid input message to set.
*
* @return {@code this}.
*/
@NotNull
public ChatInput.Builder invalidInputMessage(@Nullable final String invalidInputMessage) {
this.invalidInputMessage = invalidInputMessage;
return this;
}
/**
* sets {@link #isValidInput} and return {@code this}.
*
* @param isValidInput the is valid input to set.
*
* @return {@code this}.
*/
@NotNull
public ChatInput.Builder isValidInput(@NotNull final BiPredicate isValidInput) {
this.isValidInput = isValidInput;
return this;
}
/**
* sets {@link #onCancel} and return {@code this}.
*
* @param onCancel the on cancel to set.
*
* @return {@code this}.
*/
@NotNull
public ChatInput.Builder onCancel(@NotNull final Consumer onCancel) {
this.onCancel = onCancel;
return this;
}
/**
* sets {@link #onDisconnect}.
*
* @param onDisconnect the on disconnect to set.
*
* @return {@code this}.
*/
@NotNull
public Builder onDisconnect(@NotNull final Consumer onDisconnect) {
this.onDisconnect = onDisconnect;
return this;
}
/**
* sets {@link #onExpire} and return {@code this}.
*
* @param onExpire the on expire to set.
*
* @return {@code this}.
*/
@NotNull
public ChatInput.Builder onExpire(@NotNull final Consumer onExpire) {
this.onExpire = onExpire;
return this;
}
/**
* sets {@link #onFinish} and return {@code this}.
*
* @param onFinish the on finish to set.
*
* @return {@code this}.
*/
@NotNull
public ChatInput.Builder onFinish(@NotNull final BiConsumer onFinish) {
this.onFinish = onFinish;
return this;
}
/**
* sets {@link #onInvalidInput} and return {@code this}.
*
* @param onInvalidInput the on invalid input to set.
*
* @return {@code this}.
*/
@NotNull
public ChatInput.Builder onInvalidInput(@NotNull final BiPredicate onInvalidInput) {
this.onInvalidInput = onInvalidInput;
return this;
}
/**
* sets {@link #repeat} and return {@code this}.
*
* @param repeat the repeat to set.
*
* @return {@code this}.
*/
@NotNull
public ChatInput.Builder repeat(final boolean repeat) {
this.repeat = repeat;
return this;
}
/**
* sets {@link #sendValueMessage} and return {@code this}.
*
* @param sendValueMessage the send value message to set.
*
* @return {@code this}.
*/
@NotNull
public ChatInput.Builder sendValueMessage(@Nullable final String sendValueMessage) {
this.sendValueMessage = sendValueMessage;
return this;
}
/**
* sets {@link #expireMessage}.
*
* @param expireMessage the expire message to set.
*
* @return {@code this}.
*/
@NotNull
public Builder setExpireMessage(@NotNull final Function expireMessage) {
this.expireMessage = expireMessage;
return this;
}
/**
* sets {@link #setValue} and return {@code this}.
*
* @param setValue the set value to set.
*
* @return {@code this}.
*/
@NotNull
public ChatInput.Builder setValue(@NotNull final BiFunction setValue) {
this.setValue = setValue;
return this;
}
/**
* sets {@link #cancel} and return {@code this}.
*
* @param cancel the cancel to set.
*
* @return {@code this}.
*/
@NotNull
public ChatInput.Builder toCancel(@NotNull final String cancel) {
this.cancel = cancel;
return this;
}
}
}