/* * This file is a part of the SteamWar software. * * Copyright (C) 2025 SteamWar.de-Serverteam * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ package de.steamwar.messages; import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.event.ResultedEvent; import com.velocitypowered.api.event.connection.LoginEvent; import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.ServerConnection; import de.steamwar.persistent.Subserver; import de.steamwar.sql.SteamwarUser; import de.steamwar.sql.UserPerm; import de.steamwar.velocitycore.VelocityCore; import de.steamwar.velocitycore.commands.StreamingCommand; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.ClickEvent; import net.kyori.adventure.text.event.HoverEvent; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import java.text.DateFormat; import java.text.MessageFormat; import java.util.Date; import java.util.Locale; import java.util.ResourceBundle; import java.util.UUID; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Stream; public interface Chatter { LegacyComponentSerializer SERIALIZER = LegacyComponentSerializer.builder().extractUrls().build(); static Stream allPlayers() { return VelocityCore.getProxy().getAllPlayers().stream(); } static Stream allStream() { return Stream.concat(Stream.of(Chatter.console()), allPlayers().map(Chatter::of)); } static ChatterGroup broadcast() { return new ChatterGroup(allStream()); } static ChatterGroup globalChat() { return new ChatterGroup(Stream.concat(Stream.of(Chatter.console()), allPlayers().filter(player -> { Subserver subserver = Subserver.getSubserver(player); return !(Subserver.isArena(subserver) && subserver.getServer() == player.getCurrentServer().map(ServerConnection::getServerInfo).orElse(null)); }).map(Chatter::of))); } static ChatterGroup serverteam() { return new ChatterGroup(allStream() .filter(player -> player.user().hasPerm(UserPerm.TEAM)) .filter(StreamingCommand::isNotStreaming)); } SteamwarUser user(); Locale getLocale(); boolean chatShown(); void sendMessage(Component msg); Player getPlayer(); default T withPlayerOrOffline(Function withPlayer, Supplier withOffline) { Player player = getPlayer(); if(player == null) return withOffline.get(); else return withPlayer.apply(player); } default void withPlayerOrOffline(Consumer withPlayer, Runnable withOffline) { Player player = getPlayer(); if(player == null) { withOffline.run(); } else { withPlayer.accept(player); } } default void withPlayer(Consumer function) { withPlayerOrOffline(function, () -> {}); } default void system(String format, Object... params) { system(new Message(format, params)); } default void system(Message message) { send(true, null, null, message); } default void system(String format, Message onHover, ClickEvent onClick, Object... params) { send(true, onHover, onClick, new Message(format, params)); } default void prefixless(String format, Object... params) { prefixless(format, null, null, params); } default void prefixless(String format, Message onHover, ClickEvent onClick, Object... params) { send(false, onHover, onClick, new Message(format, params)); } default void send(boolean prefixed, Message onHover, ClickEvent onClick, Message message) { Component msg = parse(prefixed, message); if(onHover != null) msg = msg.hoverEvent(HoverEvent.showText(parse(false, onHover))); if(onClick != null) msg = msg.clickEvent(onClick); sendMessage(msg); } default String parseToPlain(String format, Object... params) { return parseToPlain(new Message(format, params)); } default String parseToPlain(Message message) { return PlainTextComponentSerializer.plainText().serialize(parse(message)); } default String parseToLegacy(String format, Object... params) { return parseToLegacy(new Message(format, params)); } default String parseToLegacy(Message message) { return SERIALIZER.serialize(parse(message)); } default Component parse(String format, Object... params) { return parse(false, new Message(format, params)); } default Component parse(Message message) { return parse(false, message); } default Component parse(boolean prefixed, String format, Object... params) { return parse(prefixed, new Message(format, params)); } default Component parse(boolean prefixed, Message message) { Locale locale = getLocale(); ResourceBundle resourceBundle = SteamwarResourceBundle.getResourceBundle(locale); String pattern = ""; if(prefixed) pattern = resourceBundle.getObject("PREFIX") + " "; pattern += (String)resourceBundle.getObject(message.format()); MessageFormat format = new MessageFormat(pattern, locale); Object[] params = message.params(); for (int i = 0; i < params.length; i++) { if(params[i] instanceof Message msg) { params[i] = parseToLegacy(msg); } else if(params[i] instanceof Date date) { params[i] = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, locale).format(date); } else if(params[i] instanceof SteamwarUser user) { params[i] = user.getUserName(); } else if(params[i] instanceof Player player) { params[i] = player.getUsername(); } else if(params[i] instanceof Chatter chatter) { params[i] = chatter.user().getUserName(); } else if(params[i] instanceof Function func) { params[i] = func.apply(this); } } return SERIALIZER.deserialize(format.format(params)); } static PlayerChatter of(Player player) { return new PlayerChatter(player, player::sendMessage); } static PlayerChatter disconnect(Player player) { return new PlayerChatter(player, player::disconnect); } static Chatter of(CommandSource sender) { if(sender instanceof Player player) return of(player); //Console return new PlayerlessChatter() { @Override public SteamwarUser user() { return SteamwarUser.get(-1); } @Override public boolean chatShown() { return true; } @Override public void sendMessage(Component msg) { sender.sendMessage(msg); } }; } static Chatter of(SteamwarUser user) { return of(user.getUUID(), false); } static Chatter of(UUID uuid) { return of(uuid, false); } static Chatter of(UUID uuid, boolean playerlessChatShown) { Player player = VelocityCore.getProxy().getPlayer(uuid).orElse(null); if(player != null) return Chatter.of(player); return new PlayerlessChatter() { @Override public SteamwarUser user() { return SteamwarUser.get(uuid); } @Override public boolean chatShown() { return playerlessChatShown; } @Override public void sendMessage(Component msg) { // Nowhere to send } }; } static Chatter console() { return of(VelocityCore.getProxy().getConsoleCommandSource()); } static Chatter of(LoginEvent event) { return new PlayerlessChatter() { @Override public SteamwarUser user() { return SteamwarUser.get(event.getPlayer().getUniqueId()); } @Override public boolean chatShown() { return false; } @Override public void sendMessage(Component msg) { event.setResult(ResultedEvent.ComponentResult.denied(msg)); } }; } abstract class PlayerlessChatter implements Chatter { @Override public Locale getLocale() { return user().getLocale(); } @Override public Player getPlayer() { return VelocityCore.getProxy().getPlayer(user().getUUID()).orElse(null); } } }