diff --git a/eternalcore-api/src/main/java/com/eternalcode/core/EternalCoreApi.java b/eternalcore-api/src/main/java/com/eternalcode/core/EternalCoreApi.java
index 9c56892ce..e97035080 100644
--- a/eternalcore-api/src/main/java/com/eternalcode/core/EternalCoreApi.java
+++ b/eternalcore-api/src/main/java/com/eternalcode/core/EternalCoreApi.java
@@ -2,6 +2,7 @@
import com.eternalcode.core.feature.afk.AfkService;
import com.eternalcode.core.feature.catboy.CatboyService;
+import com.eternalcode.core.feature.helpop.HelpOpService;
import com.eternalcode.core.feature.home.HomeService;
import com.eternalcode.core.feature.jail.JailService;
import com.eternalcode.core.feature.msg.MsgService;
@@ -19,6 +20,8 @@ public interface EternalCoreApi {
IgnoreService getIgnoreService();
+ HelpOpService getHelpOpService();
+
HomeService getHomeService();
JailService getJailService();
diff --git a/eternalcore-api/src/main/java/com/eternalcode/core/feature/helpop/HelpOpService.java b/eternalcore-api/src/main/java/com/eternalcode/core/feature/helpop/HelpOpService.java
new file mode 100644
index 000000000..6225c73c2
--- /dev/null
+++ b/eternalcore-api/src/main/java/com/eternalcode/core/feature/helpop/HelpOpService.java
@@ -0,0 +1,30 @@
+package com.eternalcode.core.feature.helpop;
+
+import java.util.UUID;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Service responsible for managing helpop functionality.
+ */
+public interface HelpOpService {
+
+ /**
+ * Marks a player as having sent a helpop request.
+ *
+ *
This allows administrators to reply to the player's request
+ * using the helpop reply command.
+ *
+ * @param playerUuid the UUID of the player who sent the helpop request
+ */
+ void markSender(@NotNull UUID playerUuid);
+
+ /**
+ * Checks if a player has sent a helpop request.
+ *
+ *
Helpop requests expire after a configured duration (default: 1 hour).
+ *
+ * @param playerUuid the UUID of the player to check
+ * @return {@code true} if the player has an active helpop request, {@code false} otherwise
+ */
+ boolean hasSentHelpOp(@NotNull UUID playerUuid);
+}
diff --git a/eternalcore-api/src/main/java/com/eternalcode/core/feature/helpop/event/HelpOpReplyEvent.java b/eternalcore-api/src/main/java/com/eternalcode/core/feature/helpop/event/HelpOpReplyEvent.java
new file mode 100644
index 000000000..3c0f7fbee
--- /dev/null
+++ b/eternalcore-api/src/main/java/com/eternalcode/core/feature/helpop/event/HelpOpReplyEvent.java
@@ -0,0 +1,92 @@
+package com.eternalcode.core.feature.helpop.event;
+
+import org.bukkit.entity.Player;
+import org.bukkit.event.Cancellable;
+import org.bukkit.event.Event;
+import org.bukkit.event.HandlerList;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Event called when an administrator replies to a player's helpop request.
+ *
+ *
This event is triggered before the reply message is actually sent to the player.
+ * Plugins can cancel this event to prevent the reply from being sent, or modify
+ * the reply content before it's delivered.
+ */
+public class HelpOpReplyEvent extends Event implements Cancellable {
+
+ private static final HandlerList HANDLER_LIST = new HandlerList();
+
+ private final Player admin;
+ private final Player target;
+ private String content;
+ private boolean cancelled;
+
+ public HelpOpReplyEvent(@NotNull Player admin, @NotNull Player target, @NotNull String content) {
+ super(false);
+
+ this.admin = admin;
+ this.target = target;
+ this.content = content;
+ }
+
+ /**
+ * Gets the administrator who is replying.
+ *
+ * @return the admin player
+ */
+ @NotNull
+ public Player getAdmin() {
+ return this.admin;
+ }
+
+ /**
+ * Gets the player who originally sent the helpop request.
+ *
+ * @return the target player
+ */
+ @NotNull
+ public Player getTarget() {
+ return this.target;
+ }
+
+ /**
+ * Gets the reply message content.
+ *
+ * @return the reply content
+ */
+ @NotNull
+ public String getContent() {
+ return this.content;
+ }
+
+ /**
+ * Sets the reply message content.
+ *
+ * @param content the new reply content
+ */
+ public void setContent(@NotNull String content) {
+ this.content = content;
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return this.cancelled;
+ }
+
+ @Override
+ public void setCancelled(boolean cancel) {
+ this.cancelled = cancel;
+ }
+
+ @Override
+ @NotNull
+ public HandlerList getHandlers() {
+ return HANDLER_LIST;
+ }
+
+ @NotNull
+ public static HandlerList getHandlerList() {
+ return HANDLER_LIST;
+ }
+}
diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/EternalCoreApiImpl.java b/eternalcore-core/src/main/java/com/eternalcode/core/EternalCoreApiImpl.java
index 4680437ba..4123fcaac 100644
--- a/eternalcore-core/src/main/java/com/eternalcode/core/EternalCoreApiImpl.java
+++ b/eternalcore-core/src/main/java/com/eternalcode/core/EternalCoreApiImpl.java
@@ -2,6 +2,7 @@
import com.eternalcode.core.feature.afk.AfkService;
import com.eternalcode.core.feature.catboy.CatboyService;
+import com.eternalcode.core.feature.helpop.HelpOpService;
import com.eternalcode.core.feature.home.HomeService;
import com.eternalcode.core.feature.ignore.IgnoreService;
import com.eternalcode.core.feature.jail.JailService;
@@ -35,6 +36,11 @@ public IgnoreService getIgnoreService() {
return this.dependencyProvider.getDependency(IgnoreService.class);
}
+ @Override
+ public HelpOpService getHelpOpService() {
+ return this.dependencyProvider.getDependency(HelpOpService.class);
+ }
+
@Override
public HomeService getHomeService() {
return this.dependencyProvider.getDependency(HomeService.class);
diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/helpop/HelpOpCommand.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/helpop/HelpOpCommand.java
index 5889582c5..02d429771 100644
--- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/helpop/HelpOpCommand.java
+++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/helpop/HelpOpCommand.java
@@ -33,14 +33,22 @@ class HelpOpCommand {
private final NoticeService noticeService;
private final HelpOpSettings helpOpSettings;
+ private final HelpOpService helpOpService;
private final EventCaller eventCaller;
private final Server server;
private final Delay delay;
@Inject
- HelpOpCommand(NoticeService noticeService, HelpOpSettings helpOpSettings, EventCaller eventCaller, Server server) {
+ HelpOpCommand(
+ NoticeService noticeService,
+ HelpOpSettings helpOpSettings,
+ HelpOpService helpOpService,
+ EventCaller eventCaller,
+ Server server
+ ) {
this.noticeService = noticeService;
this.helpOpSettings = helpOpSettings;
+ this.helpOpService = helpOpService;
this.eventCaller = eventCaller;
this.server = server;
this.delay = Delay.withDefault(() -> this.helpOpSettings.helpOpDelay());
@@ -49,7 +57,7 @@ class HelpOpCommand {
@Execute
@DescriptionDocs(description = "Send helpop message to all administrator with eternalcore.helpop.spy permission", arguments = "")
void execute(@Sender Player player, @Join String message) {
- UUID uuid = player.getUniqueId();
+ UUID sender = player.getUniqueId();
HelpOpEvent event = new HelpOpEvent(player, message);
this.eventCaller.callEvent(event);
@@ -58,33 +66,25 @@ void execute(@Sender Player player, @Join String message) {
return;
}
- if (this.delay.hasDelay(uuid)) {
- Duration time = this.delay.getRemaining(uuid);
+ if (this.delay.hasDelay(sender)) {
+ Duration time = this.delay.getRemaining(sender);
this.noticeService.create()
.notice(translation -> translation.helpOp().helpOpDelay())
.placeholder("{TIME}", DurationUtil.format(time, true))
- .player(uuid)
+ .player(sender)
.send();
return;
}
- NoticeBroadcast notice = this.noticeService.create()
+ this.noticeService.create()
.console()
.notice(translation -> translation.helpOp().format())
+ .onlinePlayers(HELPOP_SPY)
.placeholder("{PLAYER}", player.getName())
- .placeholder("{TEXT}", MiniMessage.miniMessage().escapeTags(message));
-
- for (Player admin : this.server.getOnlinePlayers()) {
- if (!admin.hasPermission(HELPOP_SPY)) {
- continue;
- }
-
- notice = notice.player(admin.getUniqueId());
- }
-
- notice.send();
+ .placeholder("{TEXT}", MiniMessage.miniMessage().escapeTags(message))
+ .send();
this.noticeService
.create()
@@ -92,6 +92,7 @@ void execute(@Sender Player player, @Join String message) {
.notice(translation -> translation.helpOp().send())
.send();
- this.delay.markDelay(uuid);
+ this.delay.markDelay(sender);
+ this.helpOpService.markSender(sender);
}
}
diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/helpop/HelpOpReplyCommand.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/helpop/HelpOpReplyCommand.java
new file mode 100644
index 000000000..d5e69e081
--- /dev/null
+++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/helpop/HelpOpReplyCommand.java
@@ -0,0 +1,76 @@
+package com.eternalcode.core.feature.helpop;
+
+import com.eternalcode.annotations.scan.command.DescriptionDocs;
+import com.eternalcode.annotations.scan.permission.PermissionDocs;
+import com.eternalcode.core.event.EventCaller;
+import com.eternalcode.core.feature.helpop.event.HelpOpReplyEvent;
+import com.eternalcode.core.injector.annotations.Inject;
+import com.eternalcode.core.notice.NoticeService;
+import dev.rollczi.litecommands.annotations.argument.Arg;
+import dev.rollczi.litecommands.annotations.command.Command;
+import dev.rollczi.litecommands.annotations.context.Sender;
+import dev.rollczi.litecommands.annotations.execute.Execute;
+import dev.rollczi.litecommands.annotations.join.Join;
+import dev.rollczi.litecommands.annotations.permission.Permission;
+import net.kyori.adventure.text.minimessage.MiniMessage;
+import org.bukkit.entity.Player;
+
+@Command(name = "helpopreply")
+@Permission("eternalcore.helpop.reply")
+@PermissionDocs(
+ name = "HelpOp Reply",
+ description = "Allows administrator to reply to a player's helpop request",
+ permission = "eternalcore.helpop.reply"
+)
+class HelpOpReplyCommand {
+
+ private final NoticeService noticeService;
+ private final HelpOpService helpOpService;
+ private final EventCaller eventCaller;
+
+ @Inject
+ HelpOpReplyCommand(NoticeService noticeService, HelpOpService helpOpService, EventCaller eventCaller) {
+ this.noticeService = noticeService;
+ this.helpOpService = helpOpService;
+ this.eventCaller = eventCaller;
+ }
+
+ @Execute
+ @DescriptionDocs(description = "Reply to a player's helpop request", arguments = " ")
+ void execute(@Sender Player admin, @Arg Player target, @Join String message) {
+ if (!this.helpOpService.hasSentHelpOp(target.getUniqueId())) {
+ this.noticeService.create()
+ .player(admin.getUniqueId())
+ .notice(translation -> translation.helpOp().playerNotSentHelpOp())
+ .placeholder("{PLAYER}", target.getName())
+ .send();
+
+ return;
+ }
+
+ HelpOpReplyEvent event = new HelpOpReplyEvent(admin, target, message);
+ this.eventCaller.callEvent(event);
+
+ if (event.isCancelled()) {
+ return;
+ }
+
+ String escapedMessage = MiniMessage.miniMessage().escapeTags(event.getContent());
+
+ this.noticeService.create()
+ .console()
+ .player(target.getUniqueId())
+ .onlinePlayers(HelpOpCommand.HELPOP_SPY)
+ .notice(translation -> translation.helpOp().adminReply())
+ .placeholder("{ADMIN}", admin.getName())
+ .placeholder("{PLAYER}", target.getName())
+ .placeholder("{TEXT}", escapedMessage)
+ .send();
+
+ this.noticeService.create()
+ .player(admin.getUniqueId())
+ .notice(translation -> translation.helpOp().adminReplySend())
+ .placeholder("{PLAYER}", target.getName())
+ .send();
+ }
+}
diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/helpop/HelpOpServiceImpl.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/helpop/HelpOpServiceImpl.java
new file mode 100644
index 000000000..7ac162841
--- /dev/null
+++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/helpop/HelpOpServiceImpl.java
@@ -0,0 +1,26 @@
+package com.eternalcode.core.feature.helpop;
+
+import com.eternalcode.core.injector.annotations.component.Service;
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import java.time.Duration;
+import java.util.UUID;
+import org.jspecify.annotations.NonNull;
+
+@Service
+class HelpOpServiceImpl implements HelpOpService {
+
+ private final Cache helpOpSenders = CacheBuilder.newBuilder()
+ .expireAfterWrite(Duration.ofHours(1))
+ .build();
+
+ @Override
+ public void markSender(@NonNull UUID playerUuid) {
+ this.helpOpSenders.put(playerUuid, true);
+ }
+
+ @Override
+ public boolean hasSentHelpOp(@NonNull UUID playerUuid) {
+ return this.helpOpSenders.getIfPresent(playerUuid) != null;
+ }
+}
diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/helpop/messages/ENHelpOpMessages.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/helpop/messages/ENHelpOpMessages.java
index aacc7e81e..b067a3af1 100644
--- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/helpop/messages/ENHelpOpMessages.java
+++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/helpop/messages/ENHelpOpMessages.java
@@ -1,18 +1,34 @@
package com.eternalcode.core.feature.helpop.messages;
+import com.eternalcode.multification.bukkit.notice.BukkitNotice;
import com.eternalcode.multification.notice.Notice;
import eu.okaeri.configs.OkaeriConfig;
import eu.okaeri.configs.annotation.Comment;
import lombok.Getter;
import lombok.experimental.Accessors;
+import org.bukkit.Sound;
@Getter
@Accessors(fluent = true)
public class ENHelpOpMessages extends OkaeriConfig implements HelpOpSection {
@Comment("# {PLAYER} - Player who send message on /helpop, {TEXT} - message")
Notice format = Notice.chat("[HelpOp] {PLAYER}: {TEXT}");
+
@Comment(" ")
Notice send = Notice.chat("► This message has been successfully sent to administration");
+
@Comment("# {TIME} - Time to next use (cooldown)")
Notice helpOpDelay = Notice.chat("✘ You can use this command for: {TIME}");
+
+ @Comment("# {ADMIN} - Admin who replied, {PLAYER} - Player who received the reply, {TEXT} - Reply message")
+ Notice adminReply = BukkitNotice.builder()
+ .chat("[HelpOp] {ADMIN} -> {PLAYER}: {TEXT}")
+ .sound(Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0f, 1.0f)
+ .build();
+
+ @Comment(" ")
+ Notice adminReplySend = Notice.chat("► Reply has been sent to player {PLAYER}");
+
+ @Comment("# {PLAYER} - Player name")
+ Notice playerNotSentHelpOp = Notice.chat("✘ Player {PLAYER} has not sent any helpop request");
}
diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/helpop/messages/HelpOpSection.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/helpop/messages/HelpOpSection.java
index 5c1c8c84f..f190b39f8 100644
--- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/helpop/messages/HelpOpSection.java
+++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/helpop/messages/HelpOpSection.java
@@ -6,4 +6,7 @@ public interface HelpOpSection {
Notice format();
Notice send();
Notice helpOpDelay();
+ Notice adminReply();
+ Notice adminReplySend();
+ Notice playerNotSentHelpOp();
}
diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/helpop/messages/PLHelpOpMessages.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/helpop/messages/PLHelpOpMessages.java
index 27b5e89ff..5b6f145f9 100644
--- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/helpop/messages/PLHelpOpMessages.java
+++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/helpop/messages/PLHelpOpMessages.java
@@ -1,10 +1,12 @@
package com.eternalcode.core.feature.helpop.messages;
+import com.eternalcode.multification.bukkit.notice.BukkitNotice;
import com.eternalcode.multification.notice.Notice;
import eu.okaeri.configs.OkaeriConfig;
import eu.okaeri.configs.annotation.Comment;
import lombok.Getter;
import lombok.experimental.Accessors;
+import org.bukkit.Sound;
@Getter
@Accessors(fluent = true)
@@ -12,8 +14,22 @@ public class PLHelpOpMessages extends OkaeriConfig implements HelpOpSection {
@Comment({ "# {PLAYER} - Gracz który wysłał wiadomość na helpop, {TEXT} - Treść wysłanej wiadomości" })
Notice format = Notice
.chat("[HelpOp] {PLAYER}: {TEXT}");
+
@Comment(" ")
Notice send = Notice.chat("► Wiadomość została wysłana do administracji");
+
@Comment("# {TIME} - Czas do końca blokady (cooldown)")
Notice helpOpDelay = Notice.chat("✘ Możesz użyć tej komendy dopiero za {TIME}!");
+
+ @Comment("# {ADMIN} - Admin który odpowiedział, {PLAYER} - Gracz który otrzymał odpowiedź, {TEXT} - Treść odpowiedzi")
+ Notice adminReply = BukkitNotice.builder()
+ .chat("[HelpOp] {ADMIN} -> {PLAYER}: {TEXT}")
+ .sound(Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0f, 1.0f)
+ .build();
+
+ @Comment(" ")
+ Notice adminReplySend = Notice.chat("► Odpowiedź została wysłana do gracza {PLAYER}");
+
+ @Comment("# {PLAYER} - Nazwa gracza")
+ Notice playerNotSentHelpOp = Notice.chat("✘ Gracz {PLAYER} nie wysłał żadnego zapytania na helpop");
}