Add timed boss bars, /near command

This commit is contained in:
Alessandro Proto 2024-10-26 17:54:55 +02:00
parent f11036a844
commit 8e7244b0b8
8 changed files with 483 additions and 1 deletions

View file

@ -9,7 +9,7 @@ yarn_mappings=1.20.1+build.10
loader_version=0.16.7
# Mod Properties
mod_version=1.14.3
mod_version=1.15.0
maven_group=cc.reconnected
archives_base_name=rcc-server

View file

@ -99,6 +99,10 @@ public class RccServer implements ModInitializer {
WarpCommand.register(dispatcher, registryAccess, environment);
SetWarpCommand.register(dispatcher, registryAccess, environment);
DeleteWarpCommand.register(dispatcher, registryAccess, environment);
TimeBarCommand.register(dispatcher, registryAccess, environment);
NearCommand.register(dispatcher, registryAccess, environment);
});
AfkTracker.register();
@ -106,6 +110,7 @@ public class RccServer implements ModInitializer {
BackTracker.register();
TabList.register();
HttpApiServer.register();
BossBarManager.register();
ServerLifecycleEvents.SERVER_STARTED.register(server -> {
luckPerms = LuckPermsProvider.get();

View file

@ -33,4 +33,13 @@ public class RccServerConfigModel {
));
public String playerTabName = "%rcc-server:afk%%player:displayname_visual%";
public int nearCommandMaxRange = 48;
public int nearCommandDefaultRange = 32;
public boolean enableAutoRestart = true;
public ArrayList<String> restartAt = new ArrayList<>(List.of(
"06:00",
"18:00"
));
}

View file

@ -0,0 +1,56 @@
package cc.reconnected.server.api.events;
import cc.reconnected.server.core.BossBarManager;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
import net.minecraft.server.MinecraftServer;
public class BossBarEvents {
public static final Event<Start> START = EventFactory.createArrayBacked(Start.class, callbacks ->
(timeBar, server) -> {
for (Start callback : callbacks) {
callback.onStart(timeBar, server);
}
});
public static final Event<End> END = EventFactory.createArrayBacked(End.class, callbacks ->
(timeBar, server) -> {
for (End callback : callbacks) {
callback.onEnd(timeBar, server);
}
});
public static final Event<Cancel> CANCEL = EventFactory.createArrayBacked(Cancel.class, callbacks ->
(timeBar, server) -> {
for (Cancel callback : callbacks) {
callback.onCancel(timeBar, server);
}
});
public static final Event<Progress> PROGRESS = EventFactory.createArrayBacked(Progress.class, callbacks ->
(timeBar, server) -> {
for (Progress callback : callbacks) {
callback.onProgress(timeBar, server);
}
});
@FunctionalInterface
public interface Start {
void onStart(BossBarManager.TimeBar timeBar, MinecraftServer server);
}
@FunctionalInterface
public interface End {
void onEnd(BossBarManager.TimeBar timeBar, MinecraftServer server);
}
@FunctionalInterface
public interface Cancel {
void onCancel(BossBarManager.TimeBar timeBar, MinecraftServer server);
}
@FunctionalInterface
public interface Progress {
void onProgress(BossBarManager.TimeBar timeBar, MinecraftServer server);
}
}

View file

@ -0,0 +1,82 @@
package cc.reconnected.server.commands.misc;
import cc.reconnected.server.RccServer;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.context.CommandContext;
import me.lucko.fabric.api.permissions.v0.Permissions;
import net.minecraft.command.CommandRegistryAccess;
import net.minecraft.server.command.CommandManager;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import java.util.ArrayList;
import java.util.Comparator;
import static net.minecraft.server.command.CommandManager.*;
public class NearCommand {
public static void register(CommandDispatcher<ServerCommandSource> dispatcher, CommandRegistryAccess registryAccess, CommandManager.RegistrationEnvironment environment) {
var rootCommand = literal("near")
.requires(Permissions.require("rcc.command.near", 2))
.executes(context -> {
if (!context.getSource().isExecutedByPlayer()) {
context.getSource().sendFeedback(() -> Text.of("This command can only be executed by players!"), false);
return 1;
}
return execute(context, RccServer.CONFIG.nearCommandDefaultRange(), context.getSource().getPlayer());
})
.then(argument("radius", IntegerArgumentType.integer(0, RccServer.CONFIG.nearCommandMaxRange()))
.executes(context -> {
if (!context.getSource().isExecutedByPlayer()) {
context.getSource().sendFeedback(() -> Text.of("This command can only be executed by players!"), false);
return 1;
}
return execute(context, IntegerArgumentType.getInteger(context, "radius"), context.getSource().getPlayer());
}));
dispatcher.register(rootCommand);
}
private static int execute(CommandContext<ServerCommandSource> context, int range, ServerPlayerEntity sourcePlayer) {
var list = new ArrayList<ClosePlayers>();
var sourcePos = sourcePlayer.getPos();
sourcePlayer.getServerWorld().getPlayers().forEach(targetPlayer -> {
var targetPos = targetPlayer.getPos();
if (!sourcePlayer.getUuid().equals(targetPlayer.getUuid()) && sourcePos.isInRange(targetPos, range)) {
var distance = sourcePos.distanceTo(targetPos);
list.add(new ClosePlayers(targetPlayer.getDisplayName(), distance));
}
});
if(list.isEmpty()) {
context.getSource().sendFeedback(() -> Text.literal("There is no one near you.").formatted(Formatting.GOLD), false);
return 1;
}
list.sort(Comparator.comparingDouble(ClosePlayers::distance));
var text = Text.empty().append(Text.literal("Nearest players: ").formatted(Formatting.GOLD));
var comma = Text.literal(", ").formatted(Formatting.GOLD);
for (int i = 0; i < list.size(); i++) {
var player = list.get(i);
if (i > 0) {
text = text.append(comma);
}
text = text.append(player.displayName)
.append(" ")
.append(Text.literal(String.format("(%.1fm)", player.distance)).formatted(Formatting.GREEN));
}
final var finalText = text;
context.getSource().sendFeedback(() -> finalText, false);
return 1;
}
private record ClosePlayers(Text displayName, double distance) {
}
}

View file

@ -0,0 +1,122 @@
package cc.reconnected.server.commands.misc;
import cc.reconnected.server.api.events.BossBarEvents;
import cc.reconnected.server.core.BossBarManager;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.BoolArgumentType;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import me.lucko.fabric.api.permissions.v0.Permissions;
import net.minecraft.command.CommandRegistryAccess;
import net.minecraft.command.CommandSource;
import net.minecraft.command.argument.UuidArgumentType;
import net.minecraft.entity.boss.BossBar;
import net.minecraft.server.command.CommandManager;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.text.ClickEvent;
import net.minecraft.text.HoverEvent;
import net.minecraft.text.Style;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import static net.minecraft.server.command.CommandManager.*;
public class TimeBarCommand {
private static final ConcurrentHashMap<UUID, BarCommand> runningBars = new ConcurrentHashMap<>();
public static void register(CommandDispatcher<ServerCommandSource> dispatcher, CommandRegistryAccess registryAccess, CommandManager.RegistrationEnvironment environment) {
var rootCommand = literal("timebar")
.requires(Permissions.require("rcc.command.timebar", 3))
.then(literal("start")
.then(argument("seconds", IntegerArgumentType.integer(0))
.then(argument("color", StringArgumentType.word())
.suggests((context, builder) -> {
var colors = Arrays.stream(BossBar.Color.values()).map(Enum::toString).toList();
return CommandSource.suggestMatching(colors, builder);
})
.then(argument("style", StringArgumentType.word())
.suggests((context, builder) -> {
var styles = Arrays.stream(BossBar.Style.values()).map(Enum::toString).toList();
return CommandSource.suggestMatching(styles, builder);
})
.then(argument("countdown", BoolArgumentType.bool())
.then(argument("label", StringArgumentType.string())
.then(argument("command", StringArgumentType.greedyString())
.suggests((context, builder) -> dispatcher.getRoot().listSuggestions(context, builder))
.executes(TimeBarCommand::execute))
)
)
)
)
)
)
.then(literal("cancel")
.then(argument("uuid", UuidArgumentType.uuid())
.executes(TimeBarCommand::executeCancel)));
dispatcher.register(rootCommand);
BossBarEvents.END.register((timeBar, server) -> {
if (runningBars.containsKey(timeBar.getUuid())) {
var barCommand = runningBars.get(timeBar.getUuid());
try {
dispatcher.execute(barCommand.command(), barCommand.source);
} catch (CommandSyntaxException e) {
barCommand.source.sendFeedback(() -> Text.literal(e.toString()).formatted(Formatting.RED), false);
}
runningBars.remove(timeBar.getUuid());
}
});
}
private static int execute(CommandContext<ServerCommandSource> context) {
var seconds = IntegerArgumentType.getInteger(context, "seconds");
var colorName = StringArgumentType.getString(context, "color");
var styleName = StringArgumentType.getString(context, "style");
var countdown = BoolArgumentType.getBool(context, "countdown");
var label = StringArgumentType.getString(context, "label");
var command = StringArgumentType.getString(context, "command");
var color = BossBar.Color.valueOf(colorName);
var style = BossBar.Style.valueOf(styleName);
var bar = BossBarManager.getInstance().startTimeBar(label, seconds, color, style, countdown);
runningBars.put(bar.getUuid(), new BarCommand(context.getSource(), command));
context.getSource().sendFeedback(() -> Text
.literal("New time bar created with UUID ")
.append(Text.literal(bar.getUuid().toString()).setStyle(Style.EMPTY
.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Text.of("Click to copy")))
.withClickEvent(new ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, bar.getUuid().toString())))), true);
return 1;
}
private static int executeCancel(CommandContext<ServerCommandSource> context) {
var uuid = UuidArgumentType.getUuid(context, "uuid");
if (!runningBars.containsKey(uuid)) {
context.getSource().sendFeedback(() -> Text.literal("Time bar not found!").formatted(Formatting.RED), false);
return 1;
}
runningBars.remove(uuid);
BossBarManager.getInstance().cancelTimeBar(uuid);
context.getSource().sendFeedback(() -> Text.literal("Time bar canceled"), true);
return 1;
}
private record BarCommand(ServerCommandSource source, String command) {
}
}

View file

@ -0,0 +1,17 @@
package cc.reconnected.server.core;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
import net.minecraft.server.MinecraftServer;
public class AutoRestart {
private static MinecraftServer server;
public static void register() {
ServerLifecycleEvents.SERVER_STARTING.register(s -> server = s);
}
private static void schedule() {
}
}

View file

@ -0,0 +1,191 @@
package cc.reconnected.server.core;
import cc.reconnected.server.RccServer;
import cc.reconnected.server.api.events.BossBarEvents;
import cc.reconnected.server.util.Components;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import net.minecraft.entity.boss.BossBar;
import net.minecraft.entity.boss.CommandBossBar;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import java.util.Collection;
import java.util.UUID;
import java.util.concurrent.ConcurrentLinkedDeque;
public class BossBarManager {
private static BossBarManager instance;
public static BossBarManager getInstance() {
return instance;
}
private static MinecraftServer server;
private static ConcurrentLinkedDeque<TimeBar> timeBars = new ConcurrentLinkedDeque<>();
private static final MiniMessage miniMessage = MiniMessage.miniMessage();
public static void register() {
instance = new BossBarManager();
ServerLifecycleEvents.SERVER_STARTING.register(s -> server = s);
ServerTickEvents.END_SERVER_TICK.register(server -> {
// every second
if (server.getTicks() % 20 == 0) {
for (var timeBar : timeBars) {
var remove = timeBar.elapse();
BossBarEvents.PROGRESS.invoker().onProgress(timeBar, server);
var players = server.getPlayerManager().getPlayerList();
showBar(players, timeBar);
if (remove) {
timeBars.remove(timeBar);
BossBarEvents.END.invoker().onEnd(timeBar, server);
hideBar(players, timeBar);
}
}
}
});
}
private static void showBar(Collection<ServerPlayerEntity> players, TimeBar timeBar) {
timeBar.getBossBar().addPlayers(players);
}
private static void hideBar(Collection<ServerPlayerEntity> players, TimeBar timeBar) {
players.forEach(player -> {
timeBar.getBossBar().removePlayer(player);
});
}
public TimeBar startTimeBar(String label, int seconds, BossBar.Color color, BossBar.Style style, boolean countdown) {
var countdownBar = new TimeBar(label, seconds, countdown, color, style);
timeBars.add(countdownBar);
var players = server.getPlayerManager().getPlayerList();
showBar(players, countdownBar);
BossBarEvents.START.invoker().onStart(countdownBar, server);
return countdownBar;
}
public boolean cancelTimeBar(TimeBar timeBar) {
var success = timeBars.remove(timeBar);
if (success) {
var players = server.getPlayerManager().getPlayerList();
hideBar(players, timeBar);
BossBarEvents.CANCEL.invoker().onCancel(timeBar, server);
}
return success;
}
public boolean cancelTimeBar(UUID uuid) {
var progressBar = timeBars.stream().filter(p -> p.uuid.equals(uuid)).findFirst().orElse(null);
if (progressBar == null) {
return false;
}
return cancelTimeBar(progressBar);
}
public static class TimeBar {
private final UUID uuid = UUID.randomUUID();
private final CommandBossBar bossBar;
private final String label;
private final int time;
private int elapsedSeconds = 0;
private final boolean countdown;
public TimeBar(String label, int time, boolean countdown, BossBar.Color color, BossBar.Style style) {
this.bossBar = new CommandBossBar(Identifier.of(RccServer.MOD_ID, uuid.toString()), Text.of(label));
this.bossBar.setColor(color);
this.bossBar.setStyle(style);
this.label = label;
this.time = time;
this.countdown = countdown;
updateName();
updateProgress();
}
public static String formatTime(int totalSeconds) {
var hours = totalSeconds / 3600;
var minutes = (totalSeconds / 60) % 60;
var seconds = totalSeconds % 60;
if(totalSeconds >= 3600) {
return String.format("%dh%dm%ds", hours, minutes, seconds);
}
return String.format("%dm%ds", minutes, seconds);
}
public void updateName() {
var totalTime = formatTime(this.time);
var elapsedTime = formatTime(this.elapsedSeconds);
var remaining = time - elapsedSeconds;
var remainingTime = formatTime(remaining);
var text = miniMessage.deserialize(label, TagResolver.resolver(
Placeholder.parsed("total_time", totalTime),
Placeholder.parsed("elapsed_time", elapsedTime),
Placeholder.parsed("remaining_time", remainingTime)
));
bossBar.setName(Components.toText(text));
}
public UUID getUuid() {
return uuid;
}
public CommandBossBar getBossBar() {
return bossBar;
}
public String getLabel() {
return label;
}
public int getTime() {
return time;
}
public int getElapsedSeconds() {
return elapsedSeconds;
}
public boolean isCountdown() {
return countdown;
}
public boolean elapse() {
this.elapsedSeconds++;
updateProgress();
updateName();
return this.elapsedSeconds >= this.time;
}
private void updateProgress() {
float progress = (float) elapsedSeconds / (float) time;
if (countdown) {
progress = 1f - progress;
}
bossBar.setPercent(Math.min(
Math.max(
progress,
0f),
1f));
}
}
}