forked from SteamWar/SteamWar
Merge pull request 'Chat channels' (#506) from feature/chat-channels into main
Reviewed-on: SteamWar/SteamWar#506
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2026 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
package de.steamwar.network.packets.server;
|
||||
|
||||
import de.steamwar.network.packets.NetworkPacket;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* A chat message written on an arena. The arena only records it for the replay and never displays it.
|
||||
*/
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Getter
|
||||
public class ChatRecordPacket extends NetworkPacket {
|
||||
private static final long serialVersionUID = -4718360932583127458L;
|
||||
private int playerId;
|
||||
private String message;
|
||||
}
|
||||
@@ -109,7 +109,6 @@ TEAM_READY = §aTeam ready
|
||||
TEAM_NOT_READY = §c§mTeam ready
|
||||
SKIP_READY = §aSkipping to next event
|
||||
SKIP_NOT_READY = §c§mSkipping to next event
|
||||
TEAM_CHAT = {0}{1}§8» {0}{2}
|
||||
CHOOSE_KIT = §eChoose kit
|
||||
RESPAWN = §eRespawn
|
||||
MANAGE_PLAYERS = §cManage players
|
||||
|
||||
@@ -256,10 +256,6 @@ public class FightTeam {
|
||||
broadcast(player -> FightSystem.getMessage().send(message, player, params));
|
||||
}
|
||||
|
||||
public void broadcastChat(Player sender, String message) {
|
||||
broadcast(player -> FightSystem.getMessage().sendPrefixless("TEAM_CHAT", player, ChatMessageType.CHAT, prefix, sender.getName(), message));
|
||||
}
|
||||
|
||||
private void broadcast(Consumer<Player> f) {
|
||||
players.forEach((uuid, fightPlayer) -> {
|
||||
Player player = Bukkit.getPlayer(uuid);
|
||||
|
||||
@@ -19,50 +19,41 @@
|
||||
|
||||
package de.steamwar.fightsystem.listener;
|
||||
|
||||
import de.steamwar.fightsystem.ArenaMode;
|
||||
import de.steamwar.fightsystem.Config;
|
||||
import de.steamwar.fightsystem.FightSystem;
|
||||
import de.steamwar.fightsystem.fight.Fight;
|
||||
import de.steamwar.fightsystem.fight.FightTeam;
|
||||
import de.steamwar.fightsystem.record.GlobalRecorder;
|
||||
import de.steamwar.fightsystem.states.FightState;
|
||||
import de.steamwar.fightsystem.states.StateDependentListener;
|
||||
import de.steamwar.linkage.Linked;
|
||||
import de.steamwar.network.packets.PacketHandler;
|
||||
import de.steamwar.network.packets.server.ChatRecordPacket;
|
||||
import de.steamwar.sql.SteamwarUser;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.AsyncPlayerChatEvent;
|
||||
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* Chat is handled by Velocity. Messages written on this server are only sent here to be recorded for the replay.
|
||||
*/
|
||||
@Linked
|
||||
public class Chat implements Listener {
|
||||
public class Chat extends PacketHandler {
|
||||
|
||||
public Chat() {
|
||||
new StateDependentListener(ArenaMode.All, FightState.All, this);
|
||||
}
|
||||
@Handler
|
||||
public void handleChatRecord(ChatRecordPacket packet) {
|
||||
SteamwarUser user = SteamwarUser.byId(packet.getPlayerId());
|
||||
String message = packet.getMessage();
|
||||
FightSystem.getPlugin().getLogger().log(Level.INFO, user.getUserName() + "» " + message);
|
||||
|
||||
@EventHandler
|
||||
public void handlePlayerChat(AsyncPlayerChatEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
String message = event.getMessage();
|
||||
|
||||
FightSystem.getPlugin().getLogger().log(Level.INFO, player.getName() + "» " + message);
|
||||
FightTeam fightTeam = Fight.getPlayerTeam(player);
|
||||
Player player = Bukkit.getPlayer(user.getUUID());
|
||||
FightTeam fightTeam = player != null ? Fight.getPlayerTeam(player) : null;
|
||||
if (fightTeam != null) {
|
||||
String teamName = fightTeam.getColoredName();
|
||||
if (message.startsWith(Config.GameModeConfig.TeamChatPrefix)) {
|
||||
fightTeam.broadcastChat(player, message.substring(1));
|
||||
} else {
|
||||
broadcastChat("PARTICIPANT_CHAT", teamName, player.getName(), message);
|
||||
}
|
||||
} else if (Config.isReferee(player)) {
|
||||
broadcastChat("FIGHTLEADER_CHAT", player.getName(), message);
|
||||
GlobalRecorder.getInstance().chat("PARTICIPANT_CHAT", fightTeam.getColoredName(), user.getUserName(), message);
|
||||
} else if (player != null && Config.isReferee(player)) {
|
||||
GlobalRecorder.getInstance().chat("FIGHTLEADER_CHAT", user.getUserName(), message);
|
||||
} else {
|
||||
broadcastChat("SPECTATOR_CHAT", player.getName(), message);
|
||||
GlobalRecorder.getInstance().chat("SPECTATOR_CHAT", user.getUserName(), message);
|
||||
}
|
||||
|
||||
event.setCancelled(true);
|
||||
}
|
||||
|
||||
public static void broadcastChat(String message, Object... params) {
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.misslewars.listener;
|
||||
|
||||
import de.steamwar.linkage.Linked;
|
||||
import de.steamwar.misslewars.FightState;
|
||||
import de.steamwar.misslewars.MWTeam;
|
||||
import de.steamwar.misslewars.MissileWars;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.player.AsyncPlayerChatEvent;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
@Linked
|
||||
public class ChatListener extends BasicListener {
|
||||
|
||||
public ChatListener() {
|
||||
super(EnumSet.allOf(FightState.class));
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onChat(AsyncPlayerChatEvent e) {
|
||||
e.setCancelled(true);
|
||||
|
||||
Player p = e.getPlayer();
|
||||
MWTeam team = MissileWars.getTeam(p);
|
||||
String message = e.getPlayer().getDisplayName() + "§8» §7" + e.getMessage();
|
||||
if (team == null) {
|
||||
Bukkit.broadcastMessage(message);
|
||||
} else {
|
||||
if (e.getMessage().startsWith("+")) {
|
||||
String teamMessage = "§8[" + team.getColorCode() + "TEAM§8] " + e.getPlayer().getDisplayName() + "§8» §7" + e.getMessage().substring(1);
|
||||
team.getPlayers().forEach(player -> player.sendMessage(teamMessage));
|
||||
} else {
|
||||
Bukkit.broadcastMessage(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,6 @@
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
LOCAL_CHAT = §eLocal §r{0}§8»§7 {1}
|
||||
|
||||
COMMAND_SYSTEM_ERROR = §cError executing the command!
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
LOCAL_CHAT = §eLokal §r{0}§8»§7 {1}
|
||||
|
||||
COMMAND_SYSTEM_ERROR = §cFehler beim Ausführen des Befehls!
|
||||
|
||||
|
||||
@@ -53,6 +53,3 @@ DEALER_REDSTONE = §eRedstone
|
||||
DEALER_BLOCKS = §eBlocks
|
||||
DEALER_TOOLS = §eTools
|
||||
DEALER_ANGLES = §eAngle Blocks
|
||||
|
||||
PARTICIPANT_CHAT = {0} {1}§8» §7{2}
|
||||
SPECTATOR_CHAT = §7{0}§8» §7{1}
|
||||
@@ -22,7 +22,6 @@ package de.steamwar.tntleague.events
|
||||
import de.steamwar.tntleague.config.TNTLeagueWorldConfig
|
||||
import de.steamwar.tntleague.game.TNTLeagueGame
|
||||
import de.steamwar.tntleague.game.TNTLeagueTeam
|
||||
import de.steamwar.tntleague.message
|
||||
import de.steamwar.tntleague.plugin
|
||||
import org.bukkit.GameMode
|
||||
import org.bukkit.Material
|
||||
@@ -90,20 +89,4 @@ object GlobalListener : Listener {
|
||||
null -> e.respawnLocation = TNTLeagueWorldConfig.lobby
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@EventHandler
|
||||
fun handlePlayerChat(event: AsyncPlayerChatEvent) {
|
||||
val player = event.player
|
||||
val msg = event.message
|
||||
|
||||
val fightTeam = TNTLeagueGame.getTeam(player)
|
||||
if (fightTeam != null) {
|
||||
message.broadcastPrefixless("PARTICIPANT_CHAT", fightTeam.name, player.name, msg)
|
||||
} else {
|
||||
message.broadcastPrefixless("SPECTATOR_CHAT", player.name, msg)
|
||||
}
|
||||
|
||||
event.isCancelled = true
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,3 @@ GAME_TIME = §a{0}:{1}
|
||||
CATCH_UP_WARNING = §4!! §cYou should catch up §4!!
|
||||
|
||||
COMMAND_START = §aThe game will start soon§8!
|
||||
|
||||
PARTICIPANT_CHAT = {0} {1}§8» §7{2}
|
||||
SPECTATOR_CHAT = §7{0}§8» §7{1}
|
||||
@@ -20,19 +20,15 @@
|
||||
package de.steamwar.towerrun.listener;
|
||||
|
||||
import de.steamwar.linkage.Linked;
|
||||
import de.steamwar.towerrun.TowerRun;
|
||||
import de.steamwar.towerrun.config.WorldConfig;
|
||||
import de.steamwar.towerrun.game.TowerRunGame;
|
||||
import de.steamwar.towerrun.game.TowerRunPlayer;
|
||||
import de.steamwar.towerrun.state.GameState;
|
||||
import de.steamwar.towerrun.state.GameStateBukkitListener;
|
||||
import de.steamwar.towerrun.state.GameStates;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.AsyncPlayerChatEvent;
|
||||
import org.bukkit.event.player.PlayerMoveEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import org.bukkit.event.player.PlayerRespawnEvent;
|
||||
@@ -71,25 +67,4 @@ public class GlobalListener extends GameStateBukkitListener implements Listener
|
||||
public void onPlayerRespawn(PlayerRespawnEvent event) {
|
||||
event.setRespawnLocation(WorldConfig.SPAWN);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onAsyncPlayerChat(AsyncPlayerChatEvent event) {
|
||||
TowerRunPlayer player = TowerRunPlayer.get(event.getPlayer());
|
||||
if (GameState.getCurrentState() == GameStates.RUNNING && (TowerRunGame.isAlive(player) || TowerRunGame.isEscaped(player))) {
|
||||
String prefix;
|
||||
if (TowerRunGame.isAlive(player)) {
|
||||
if (event.getPlayer().getGameMode() == GameMode.SPECTATOR) {
|
||||
prefix = "§c☠";
|
||||
} else {
|
||||
prefix = "§6❤";
|
||||
}
|
||||
} else {
|
||||
prefix = "§a✔";
|
||||
}
|
||||
TowerRun.getMessage().broadcastPrefixless("PARTICIPANT_CHAT", prefix, event.getPlayer().getName(), event.getMessage());
|
||||
} else {
|
||||
TowerRun.getMessage().broadcastPrefixless("SPECTATOR_CHAT", event.getPlayer().getName(), event.getMessage());
|
||||
}
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -573,12 +573,51 @@ CHAT_BC_USAGE = §8/§7bc §8[§emessage§8]
|
||||
CHAT_NO_RECEIVER = §cNobody receives your message
|
||||
CHAT_EMPTY = §cDon\'t write meaningless empty messages.
|
||||
|
||||
CHAT_SERVERTEAM = §8STC §e{0}§8» §f{2}
|
||||
CHAT_DISCORD_SERVERTEAM = §8STC §e{0}§8» §f{2}
|
||||
CHAT_GLOBAL = {3}{4}{5}{6}{0}§8» {7}{2}
|
||||
CHAT_DISCORD_GLOBAL = {3}{4}{5}{6}{0}§8» {7}{2}
|
||||
CHAT_TEAM = §8TC §e{0}§8» §f{2}
|
||||
CHAT_MSG = §e{0}§8»§e{1} §7{2}
|
||||
CHAT_SERVERTEAM_SENDER = §e{0}
|
||||
CHAT_SERVERTEAM = §8» §f{2}
|
||||
CHAT_GLOBAL_SENDER = {3}{4}{5}{6}{0}
|
||||
CHAT_GLOBAL = §8» {7}{2}
|
||||
CHAT_TEAM_SENDER = §e{0}
|
||||
CHAT_TEAM = §8» §f{2}
|
||||
CHAT_MSG_SENDER = §e{0}
|
||||
CHAT_MSG = §8»§e{1} §7{2}
|
||||
CHAT_SPECIAL_ALERT = §e{0}§8»§e{1} §7{2}
|
||||
CHAT_FIGHT_PARTICIPANT_SENDER = {0}{1}
|
||||
CHAT_FIGHT_PARTICIPANT = §8» §7{0}
|
||||
CHAT_FIGHT_SPECTATOR_SENDER = §7{1}
|
||||
CHAT_FIGHT_SPECTATOR = §8» §7{0}
|
||||
CHAT_FIGHT_REFEREE_SENDER = §e{1}
|
||||
CHAT_FIGHT_REFEREE = §8» §e{0}
|
||||
CHAT_FIGHT_TEAM = §8» §f{0}
|
||||
CHAT_ICON = {0}{1}
|
||||
CHAT_HOVER_TEAM = §7Team: §e{0}\n§7Prefix: §r{1}{2}
|
||||
CHAT_ICON_GLOBAL = 🌍
|
||||
CHAT_ICON_LOCAL = ⌂
|
||||
CHAT_ICON_FIGHT = ⚔
|
||||
CHAT_ICON_FIGHT_FOREIGN = ⚔ §8{0}
|
||||
CHAT_ICON_FIGHT_TEAM = {0}⚑
|
||||
CHAT_ICON_FIGHT_TEAM_FOREIGN = {0}⚑ §8{1}
|
||||
CHAT_ICON_TEAM = 👥
|
||||
CHAT_ICON_SERVERTEAM = ⚙
|
||||
CHAT_ICON_MSG = ✉
|
||||
CHAT_CHANNEL_GLOBAL = §eGlobal
|
||||
CHAT_CHANNEL_ARENA = §e{0}
|
||||
CHAT_CHANNEL_FIGHT_TEAM = §e{0} {1}
|
||||
CHAT_CHANNEL_TEAM = §eTC
|
||||
CHAT_CHANNEL_SERVERTEAM = §eSTC
|
||||
CHAT_CHANNEL_MSG = §eMSG §7{0}
|
||||
CHAT_CHANNEL_ALERT = §cAlert
|
||||
CHAT_CHANNEL_LOCAL = §eLocal
|
||||
CHAT_CHANNEL_DEFAULT = §ais your default channel
|
||||
CHAT_CHANNEL_NOT_SWITCHABLE = §cYou cannot write into this channel by default.
|
||||
CHAT_NO_WRITE = §cYou cannot write into this channel.
|
||||
CHAT_FIGHT_NO_TEAM = §cYou are not in a fight team.
|
||||
GLOBAL_USAGE = §8/§7global §8[§emessage§8]
|
||||
CHAT_USAGE = §8/§7chat §8[§echannel§8] [§ejoin§8|§eleave§8|§emsg §8<§etext§8>]
|
||||
CHAT_CHANNEL_UNKNOWN = §cUnknown chat channel.
|
||||
CHAT_CHANNEL_JOINED = §7You are reading §e{0} §7again.
|
||||
CHAT_CHANNEL_LEFT = §7You no longer read §e{0}§7.
|
||||
CHAT_CHANNEL_LEAVE_DEFAULT = §cYou cannot leave the channel you are writing into. Switch to another channel first.
|
||||
|
||||
#CheckListner
|
||||
CHECK_UNCHECKED = §7You still have §e{0} §7unchecked schematic§8(§7s§8)!
|
||||
|
||||
@@ -538,12 +538,51 @@ CHAT_BC_USAGE = §8/§7bc §8[§eNachricht§8]
|
||||
CHAT_NO_RECEIVER = §cNiemand empfängt deine Nachricht
|
||||
CHAT_EMPTY = §cSchreibe keine inhaltslosen Nachrichten.
|
||||
|
||||
CHAT_SERVERTEAM = §8STC §e{0}§8» §f{2}
|
||||
CHAT_DISCORD_SERVERTEAM = §8STC §e{0}§8» §f{2}
|
||||
CHAT_GLOBAL = {3}{4}{5}{6}{0}§8» {7}{2}
|
||||
CHAT_DISCORD_GLOBAL = {3}{4}{5}{6}{0}§8» {7}{2}
|
||||
CHAT_TEAM = §8TC §e{0}§8» §f{2}
|
||||
CHAT_MSG = §e{0}§8»§e{1} §7{2}
|
||||
CHAT_SERVERTEAM_SENDER = §e{0}
|
||||
CHAT_SERVERTEAM = §8» §f{2}
|
||||
CHAT_GLOBAL_SENDER = {3}{4}{5}{6}{0}
|
||||
CHAT_GLOBAL = §8» {7}{2}
|
||||
CHAT_TEAM_SENDER = §e{0}
|
||||
CHAT_TEAM = §8» §f{2}
|
||||
CHAT_MSG_SENDER = §e{0}
|
||||
CHAT_MSG = §8»§e{1} §7{2}
|
||||
CHAT_SPECIAL_ALERT = §e{0}§8»§e{1} §7{2}
|
||||
CHAT_FIGHT_PARTICIPANT_SENDER = {0}{1}
|
||||
CHAT_FIGHT_PARTICIPANT = §8» §7{0}
|
||||
CHAT_FIGHT_SPECTATOR_SENDER = §7{1}
|
||||
CHAT_FIGHT_SPECTATOR = §8» §7{0}
|
||||
CHAT_FIGHT_REFEREE_SENDER = §e{1}
|
||||
CHAT_FIGHT_REFEREE = §8» §e{0}
|
||||
CHAT_FIGHT_TEAM = §8» §f{0}
|
||||
CHAT_ICON = {0}{1}
|
||||
CHAT_HOVER_TEAM = §7Team: §e{0}\n§7Kürzel: §r{1}{2}
|
||||
CHAT_ICON_GLOBAL = 🌍
|
||||
CHAT_ICON_LOCAL = ⌂
|
||||
CHAT_ICON_FIGHT = ⚔
|
||||
CHAT_ICON_FIGHT_FOREIGN = ⚔ §8{0}
|
||||
CHAT_ICON_FIGHT_TEAM = {0}⚑
|
||||
CHAT_ICON_FIGHT_TEAM_FOREIGN = {0}⚑ §8{1}
|
||||
CHAT_ICON_TEAM = 👥
|
||||
CHAT_ICON_SERVERTEAM = ⚙
|
||||
CHAT_ICON_MSG = ✉
|
||||
CHAT_CHANNEL_GLOBAL = §eGlobal
|
||||
CHAT_CHANNEL_ARENA = §e{0}
|
||||
CHAT_CHANNEL_FIGHT_TEAM = §e{0} {1}
|
||||
CHAT_CHANNEL_TEAM = §eTC
|
||||
CHAT_CHANNEL_SERVERTEAM = §eSTC
|
||||
CHAT_CHANNEL_MSG = §eMSG §7{0}
|
||||
CHAT_CHANNEL_ALERT = §cAlert
|
||||
CHAT_CHANNEL_LOCAL = §eLokal
|
||||
CHAT_CHANNEL_DEFAULT = §aist dein Standardkanal
|
||||
CHAT_CHANNEL_NOT_SWITCHABLE = §cIn diesen Kanal kannst du nicht standardmäßig schreiben.
|
||||
CHAT_NO_WRITE = §cDu kannst nicht in diesen Kanal schreiben.
|
||||
CHAT_FIGHT_NO_TEAM = §cDu bist in keinem Kampfteam.
|
||||
GLOBAL_USAGE = §8/§7global §8[§eNachricht§8]
|
||||
CHAT_USAGE = §8/§7chat §8[§eKanal§8] [§ejoin§8|§eleave§8|§emsg §8<§eText§8>]
|
||||
CHAT_CHANNEL_UNKNOWN = §cUnbekannter Chatkanal.
|
||||
CHAT_CHANNEL_JOINED = §7Du liest §e{0} §7wieder mit.
|
||||
CHAT_CHANNEL_LEFT = §7Du liest §e{0} §7nicht mehr mit.
|
||||
CHAT_CHANNEL_LEAVE_DEFAULT = §cDu kannst den Kanal, in den du schreibst, nicht verlassen. Wechsle zuerst in einen anderen Kanal.
|
||||
|
||||
#CheckListner
|
||||
CHECK_UNCHECKED = §7Du hast noch §e{0} §7ungeprüfte Schematic§8(§7s§8)!
|
||||
|
||||
@@ -23,8 +23,6 @@ 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;
|
||||
@@ -61,13 +59,6 @@ public interface Chatter {
|
||||
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))
|
||||
|
||||
@@ -31,8 +31,10 @@ import net.kyori.adventure.text.event.ClickEvent;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static de.steamwar.persistent.Storage.eventServer;
|
||||
@@ -43,8 +45,15 @@ public class EventStarter {
|
||||
@Getter
|
||||
private static final Map<Integer, String> spectatePorts = new HashMap<>();
|
||||
|
||||
public static void addSpectateServer(int port, String command) {
|
||||
private static final Set<String> spectateServers = new HashSet<>();
|
||||
|
||||
public static void addSpectateServer(int port, String command, String serverName) {
|
||||
spectatePorts.put(port, command);
|
||||
spectateServers.add(serverName);
|
||||
}
|
||||
|
||||
public static boolean isSpectateServer(String serverName) {
|
||||
return spectateServers.contains(serverName);
|
||||
}
|
||||
|
||||
public EventStarter() {
|
||||
|
||||
@@ -236,7 +236,7 @@ public class VelocityCore implements ReloadablePlugin {
|
||||
String cmd = cmds.remove(0);
|
||||
|
||||
if (server.getSpectatePort() != 0) {
|
||||
EventStarter.addSpectateServer(server.getSpectatePort(), cmd);
|
||||
EventStarter.addSpectateServer(server.getSpectatePort(), cmd, entry.getKey());
|
||||
}
|
||||
|
||||
new ServerSwitchCommand(cmd, entry.getKey(), server.getSpectatePort() != 0, cmds.toArray(new String[0]));
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2026 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
package de.steamwar.velocitycore.chat;
|
||||
|
||||
import de.steamwar.messages.Chatter;
|
||||
import de.steamwar.messages.Message;
|
||||
import de.steamwar.sql.UserPerm;
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class AlertChannel extends ChatChannel {
|
||||
|
||||
@Override
|
||||
public boolean switchable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message tag(Chatter receiver) {
|
||||
return new Message("CHAT_CHANNEL_ALERT");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<Chatter> readers() {
|
||||
return Arrays.stream(Chatter.broadcast().getChatters());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canWrite(Chatter sender) {
|
||||
if (!sender.user().hasPerm(UserPerm.MODERATION)) {
|
||||
sender.system("CHAT_NO_WRITE");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Component format(Chatter receiver, Chatter sender, String message) {
|
||||
return receiver.parse(true, new Message("ALERT", message.replace('&', '§')));
|
||||
}
|
||||
|
||||
/**
|
||||
* Alerts are system messages: they are shown even with hidden chat and bypass the chat filter.
|
||||
*/
|
||||
@Override
|
||||
public void send(Chatter sender, String message, boolean mirror) {
|
||||
if (!canWrite(sender)) return;
|
||||
|
||||
readers().forEach(reader -> reader.sendMessage(format(reader, sender, message)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2026 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.velocitycore.chat;
|
||||
|
||||
import de.steamwar.messages.Chatter;
|
||||
import de.steamwar.messages.Message;
|
||||
import de.steamwar.sql.AuditLog;
|
||||
import de.steamwar.sql.SteamwarUser;
|
||||
import de.steamwar.sql.UserPerm;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.event.HoverEvent;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public abstract class ChatChannel {
|
||||
|
||||
/**
|
||||
* @return the id under which this channel is stored as default channel of a player, null if it cannot be stored
|
||||
*/
|
||||
public String persistId() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean switchable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the channel name shown in channel related system messages
|
||||
*/
|
||||
public abstract Message tag(Chatter receiver);
|
||||
|
||||
/**
|
||||
* @return the icon always shown in front of messages of this channel, null if the message format already shows the channel.
|
||||
* The icon is highlighted if this is the default channel of the receiver.
|
||||
*/
|
||||
protected Message icon(Chatter receiver) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public abstract Stream<Chatter> readers();
|
||||
|
||||
/**
|
||||
* Checks if the sender is allowed to write into this channel and informs the sender otherwise.
|
||||
*/
|
||||
public abstract boolean canWrite(Chatter sender);
|
||||
|
||||
protected abstract Component format(Chatter receiver, Chatter sender, String message);
|
||||
|
||||
/**
|
||||
* Called after a message was delivered to all readers.
|
||||
*
|
||||
* @param mirror false if the message originates from the mirror of this channel and should not be sent back
|
||||
* @return true if the message was delivered to a receiver outside the game
|
||||
*/
|
||||
protected boolean delivered(Chatter sender, String message, boolean mirror) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void audit(SteamwarUser sender, String message) {
|
||||
AuditLog.createChat(AuditLog.SERVER_NAME_VELOCITY, null, sender, message);
|
||||
}
|
||||
|
||||
public void send(Chatter sender, String message) {
|
||||
send(sender, message, true);
|
||||
}
|
||||
|
||||
public void send(Chatter sender, String message, boolean mirror) {
|
||||
if (!canWrite(sender)) return;
|
||||
|
||||
SteamwarUser user = sender.user();
|
||||
String coloredMessage = user.hasPerm(UserPerm.COLOR_CHAT) ? message.replace('&', '§') : message;
|
||||
if (ChatFilter.filtered(sender, coloredMessage)) return;
|
||||
|
||||
Map<Integer, Chatter> receivers = new LinkedHashMap<>();
|
||||
readers().forEach(reader -> receivers.putIfAbsent(reader.user().getId(), reader));
|
||||
receivers.putIfAbsent(user.getId(), sender);
|
||||
|
||||
boolean noReceiver = true;
|
||||
for (Chatter receiver : receivers.values()) {
|
||||
if (!receiver.chatShown()) continue;
|
||||
|
||||
Component msg = format(receiver, sender, coloredMessage);
|
||||
Message icon = icon(receiver);
|
||||
if (icon != null) {
|
||||
Component iconComponent = receiver.parse(new Message("CHAT_ICON", ChatManager.getDefault(receiver) == this ? "§e" : "§7", icon))
|
||||
.hoverEvent(HoverEvent.showText(receiver.parse(tag(receiver))));
|
||||
msg = Component.text().append(iconComponent).append(Component.text(" ")).append(msg).build();
|
||||
}
|
||||
receiver.sendMessage(msg);
|
||||
|
||||
if (receiver.user().getId() != user.getId()) noReceiver = false;
|
||||
}
|
||||
|
||||
if (!delivered(sender, coloredMessage, mirror) && noReceiver) {
|
||||
sender.system("CHAT_NO_RECEIVER");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2026 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
package de.steamwar.velocitycore.chat;
|
||||
|
||||
import de.steamwar.messages.Chatter;
|
||||
import de.steamwar.messages.Message;
|
||||
import de.steamwar.sql.Punishment;
|
||||
import de.steamwar.sql.SteamwarUser;
|
||||
import de.steamwar.sql.UserPerm;
|
||||
import de.steamwar.velocitycore.VelocityCore;
|
||||
import de.steamwar.velocitycore.commands.PunishmentCommand;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class ChatFilter {
|
||||
|
||||
private ChatFilter() {}
|
||||
|
||||
public static boolean filtered(Chatter sender, String message) {
|
||||
if (!sender.chatShown()) {
|
||||
sender.system("CHAT_RECEIVE");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (message.replace("§[a-f0-9klmno]", "").trim().isEmpty()) {
|
||||
sender.system("CHAT_EMPTY");
|
||||
return true;
|
||||
}
|
||||
|
||||
SteamwarUser user = sender.user();
|
||||
if (!user.hasPerm(UserPerm.TEAM) && (message.contains("http:") || message.contains("https:") || message.contains("www."))) {
|
||||
sender.system("CHAT_NO_LINKS");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (PunishmentCommand.isPunishedWithMessage(sender, Punishment.PunishmentType.Mute)) return true;
|
||||
|
||||
if (message.contains("LIXFEL")) {
|
||||
specialAlert(sender, "Lixfel", "CHAT_LIXFEL_", 3, 6, 11, 12, 15);
|
||||
}
|
||||
if (message.contains("YOYONOW")) {
|
||||
specialAlert(sender, "YoyoNow", "CHAT_YOYONOW_", 3, 6, 11, 12);
|
||||
}
|
||||
if (message.contains("CHAOSCAOT")) {
|
||||
specialAlert(sender, "Chaoscaot", "CHAT_CHAOSCAOT_", 3, 6, 11, 12, 15, 17);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void specialAlert(Chatter sender, String name, String baseMessage, int... delay) {
|
||||
sender.system("CHAT_LIXFEL_ACTION_BAR");
|
||||
for (int i = 0; i < delay.length; i++) {
|
||||
int finalI = i;
|
||||
VelocityCore.schedule(() -> sender.prefixless("CHAT_SPECIAL_ALERT", name, sender.user(), new Message(baseMessage + (finalI + 1)))).delay(delay[i], TimeUnit.SECONDS).schedule();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2026 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
package de.steamwar.velocitycore.chat;
|
||||
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import de.steamwar.messages.Chatter;
|
||||
import de.steamwar.messages.Message;
|
||||
import de.steamwar.network.packets.server.PingPacket;
|
||||
import de.steamwar.sql.SteamwarUser;
|
||||
import de.steamwar.sql.Team;
|
||||
import de.steamwar.sql.UserPerm;
|
||||
import de.steamwar.velocitycore.network.NetworkSender;
|
||||
import de.steamwar.velocitycore.util.SteamwarPrefix;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.event.HoverEvent;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ChatFormat {
|
||||
|
||||
private ChatFormat() {}
|
||||
|
||||
/**
|
||||
* Formats a message with the rank and team prefixes of the sender. The sender part ({@code <format>_SENDER})
|
||||
* shows the team of the sender on hover.
|
||||
*/
|
||||
public static Component player(String format, Chatter receiver, Chatter sender, Chatter msgReceiver, String message) {
|
||||
SteamwarUser user = sender.user();
|
||||
UserPerm.Prefix prefix = user.prefix();
|
||||
|
||||
String teamPrefix = "";
|
||||
if (user.hasPerm(UserPerm.TEAM) && useSwPrefix(user)) {
|
||||
teamPrefix = "§eS§8W ";
|
||||
} else if (user.getTeam() != 0) {
|
||||
Team team = Team.byId(user.getTeam());
|
||||
teamPrefix = "§" + team.getTeamColor() + team.getTeamKuerzel() + " ";
|
||||
}
|
||||
|
||||
String chatColorCode = user.hasPerm(UserPerm.TEAM) ? "§f" : "§7";
|
||||
Object[] params = {
|
||||
user,
|
||||
msgReceiver == null ? receiver : msgReceiver,
|
||||
highlightMentions(message, chatColorCode, receiver),
|
||||
teamPrefix,
|
||||
"",
|
||||
prefix == UserPerm.emptyPrefix ? "§f" : prefix.getColorCode(),
|
||||
prefix.getChatPrefix().length() == 0 ? "§f" : prefix.getChatPrefix() + " ",
|
||||
chatColorCode};
|
||||
|
||||
Component senderPart = withTeamHover(receiver, user, receiver.parse(new Message(format + "_SENDER", params)));
|
||||
return Component.text().append(senderPart).append(receiver.parse(new Message(format, params))).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a fight chat message. The fight team name is only hoverable in event fights, where it is the actual team.
|
||||
*
|
||||
* @param coloredTeamName the fight team of the sender, null if the sender is not in a fight team
|
||||
*/
|
||||
public static Component fight(Chatter receiver, RegisteredServer server, Chatter sender, String coloredTeamName, String senderFormat, String format, String message) {
|
||||
TextComponent.Builder line = Component.text();
|
||||
String nameColor = "";
|
||||
if (coloredTeamName != null) {
|
||||
Component team = Chatter.SERIALIZER.deserialize(coloredTeamName);
|
||||
Team eventTeam = ChatManager.eventTeam(server, coloredTeamName);
|
||||
if (eventTeam != null) {
|
||||
team = team.hoverEvent(HoverEvent.showText(teamHover(receiver, eventTeam)));
|
||||
}
|
||||
line.append(team).append(Component.text(" "));
|
||||
nameColor = coloredTeamName.length() >= 2 && coloredTeamName.charAt(0) == '§' ? coloredTeamName.substring(0, 2) : "";
|
||||
}
|
||||
|
||||
line.append(withTeamHover(receiver, sender.user(), receiver.parse(new Message(senderFormat, nameColor, sender.user()))));
|
||||
line.append(receiver.parse(new Message(format, message)));
|
||||
return line.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the team of the user when hovering the component, the SteamWar team for server team members using the SW prefix.
|
||||
*/
|
||||
public static Component withTeamHover(Chatter receiver, SteamwarUser user, Component component) {
|
||||
if (useSwPrefix(user)) {
|
||||
return component.hoverEvent(HoverEvent.showText(receiver.parse(new Message("CHAT_HOVER_TEAM", "SteamWar", "§e", "S§8W"))));
|
||||
}
|
||||
if (user.getTeam() == 0) return component;
|
||||
|
||||
return component.hoverEvent(HoverEvent.showText(teamHover(receiver, Team.byId(user.getTeam()))));
|
||||
}
|
||||
|
||||
private static Component teamHover(Chatter receiver, Team team) {
|
||||
return receiver.parse(new Message("CHAT_HOVER_TEAM", team.getTeamName(), "§" + team.getTeamColor(), team.getTeamKuerzel()));
|
||||
}
|
||||
|
||||
private static boolean useSwPrefix(SteamwarUser user) {
|
||||
if (!user.hasPerm(UserPerm.TEAM)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
UserPerm.Prefix prefix = user.prefix();
|
||||
|
||||
boolean hasTeamPrefix = user.getTeam() != 0;
|
||||
boolean hasSwPrefix = prefix != UserPerm.emptyPrefix;
|
||||
|
||||
if (!hasSwPrefix) return false;
|
||||
if (!hasTeamPrefix) return true;
|
||||
|
||||
return SteamwarPrefix.usesSWPrefix(user);
|
||||
}
|
||||
|
||||
public static String highlightMentions(String message, String returnColor, Chatter player) {
|
||||
if (!message.contains("@")) return message;
|
||||
|
||||
String mark = "@" + player.user().getUserName();
|
||||
return Arrays.stream(message.split(" ")).map(cur -> {
|
||||
if (cur.equalsIgnoreCase(mark) && player.getPlayer() != null) {
|
||||
NetworkSender.send(player.getPlayer(), new PingPacket(player.user().getId()));
|
||||
return "§e" + cur + returnColor;
|
||||
}
|
||||
return cur;
|
||||
}).collect(Collectors.joining(" "));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,455 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2026 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
package de.steamwar.velocitycore.chat;
|
||||
|
||||
import com.velocitypowered.api.event.Subscribe;
|
||||
import com.velocitypowered.api.event.connection.DisconnectEvent;
|
||||
import com.velocitypowered.api.event.player.ServerPostConnectEvent;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.ServerConnection;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import de.steamwar.linkage.Linked;
|
||||
import de.steamwar.messages.Chatter;
|
||||
import de.steamwar.messages.Message;
|
||||
import de.steamwar.messages.PlayerChatter;
|
||||
import de.steamwar.network.packets.common.FightInfoPacket;
|
||||
import de.steamwar.persistent.Subserver;
|
||||
import de.steamwar.sql.Event;
|
||||
import de.steamwar.sql.Referee;
|
||||
import de.steamwar.sql.Team;
|
||||
import de.steamwar.sql.SteamwarUser;
|
||||
import de.steamwar.sql.UserConfig;
|
||||
import de.steamwar.sql.UserPerm;
|
||||
import de.steamwar.velocitycore.EventStarter;
|
||||
import de.steamwar.velocitycore.VelocityCore;
|
||||
import de.steamwar.velocitycore.listeners.BasicListener;
|
||||
import de.steamwar.velocitycore.tablist.TablistManager;
|
||||
import lombok.Getter;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.event.HoverEvent;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Keeps track of all chat channels and the default channel every player writes into.
|
||||
*/
|
||||
@Linked
|
||||
public class ChatManager extends BasicListener {
|
||||
|
||||
private static final String CONFIG_KEY = "chat.channel";
|
||||
private static final String HIDDEN_KEY = "chat.hidden";
|
||||
private static final long REFEREE_CACHE_MILLIS = 60_000;
|
||||
|
||||
@Getter
|
||||
private static final GlobalChannel global = new GlobalChannel();
|
||||
@Getter
|
||||
private static final ServerTeamChannel serverTeam = new ServerTeamChannel();
|
||||
@Getter
|
||||
private static final AlertChannel alert = new AlertChannel();
|
||||
|
||||
private static final Map<RegisteredServer, LocalChannel> locals = new ConcurrentHashMap<>();
|
||||
private static final Map<RegisteredServer, FightTeamChannel[]> fightTeams = new ConcurrentHashMap<>();
|
||||
private static final Map<Integer, TeamChannel> teams = new ConcurrentHashMap<>();
|
||||
private static final Set<DirectChannel> directs = ConcurrentHashMap.newKeySet();
|
||||
|
||||
private static final Map<UUID, ChatChannel> defaults = new ConcurrentHashMap<>();
|
||||
private static final Map<UUID, String> persisted = new ConcurrentHashMap<>();
|
||||
private static final Set<UUID> globalOnArena = ConcurrentHashMap.newKeySet();
|
||||
private static final Set<UUID> announced = ConcurrentHashMap.newKeySet();
|
||||
private static final Map<UUID, Set<RegisteredServer>> spies = new ConcurrentHashMap<>();
|
||||
private static final Map<UUID, Set<String>> hidden = new ConcurrentHashMap<>();
|
||||
|
||||
private static int refereeEvent = -1;
|
||||
private static long refereesLoaded = 0;
|
||||
private static Set<Integer> referees = Collections.emptySet();
|
||||
|
||||
public static LocalChannel local(RegisteredServer server) {
|
||||
locals.keySet().removeIf(ChatManager::unregistered);
|
||||
return locals.computeIfAbsent(server, LocalChannel::new);
|
||||
}
|
||||
|
||||
public static FightTeamChannel fightTeam(RegisteredServer server, boolean blue) {
|
||||
fightTeams.keySet().removeIf(ChatManager::unregistered);
|
||||
return fightTeams.computeIfAbsent(server, s -> new FightTeamChannel[]{new FightTeamChannel(s, true), new FightTeamChannel(s, false)})[blue ? 0 : 1];
|
||||
}
|
||||
|
||||
public static TeamChannel team(int team) {
|
||||
return teams.computeIfAbsent(team, TeamChannel::new);
|
||||
}
|
||||
|
||||
public static DirectChannel direct(UUID first, UUID second) {
|
||||
for (DirectChannel channel : directs) {
|
||||
if (channel.contains(first) && channel.partner(first).equals(second)) {
|
||||
return channel;
|
||||
}
|
||||
}
|
||||
|
||||
DirectChannel channel = new DirectChannel(first, second);
|
||||
directs.add(channel);
|
||||
return channel;
|
||||
}
|
||||
|
||||
private static boolean unregistered(RegisteredServer server) {
|
||||
return VelocityCore.getProxy().getServer(server.getServerInfo().getName()).orElse(null) != server;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true for arenas and event spectate servers, which use a fight chat instead of the global chat
|
||||
*/
|
||||
public static boolean isArena(RegisteredServer server) {
|
||||
return Subserver.isArena(Subserver.getSubserver(server.getServerInfo())) || EventStarter.isSpectateServer(server.getServerInfo().getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return all servers using a fight chat (arenas and event spectate servers)
|
||||
*/
|
||||
public static Stream<RegisteredServer> fightServers() {
|
||||
return VelocityCore.getProxy().getAllServers().stream().filter(ChatManager::isArena);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the local channel of the server the player is on, null while the player is on no server
|
||||
*/
|
||||
public static LocalChannel currentLocal(Player player) {
|
||||
return currentServer(player).map(ChatManager::local).orElse(null);
|
||||
}
|
||||
|
||||
static boolean onServer(Chatter chatter, RegisteredServer server) {
|
||||
Player player = chatter.getPlayer();
|
||||
return player != null && server.getPlayersConnected().contains(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the message into the channel or makes it the default channel of the player if there is no message.
|
||||
*/
|
||||
public static void sendOrSwitch(Chatter sender, ChatChannel channel, String[] message, String usage) {
|
||||
if (message.length > 0) {
|
||||
channel.send(sender, String.join(" ", message));
|
||||
} else if (sender instanceof PlayerChatter player) {
|
||||
switchTo(player, channel);
|
||||
} else {
|
||||
sender.system(usage);
|
||||
}
|
||||
}
|
||||
|
||||
private static Optional<RegisteredServer> currentServer(Player player) {
|
||||
return player.getCurrentServer().map(ServerConnection::getServer);
|
||||
}
|
||||
|
||||
static boolean readsGlobal(Player player) {
|
||||
return !currentServer(player).map(ChatManager::isArena).orElse(false) || globalOnArena.contains(player.getUniqueId());
|
||||
}
|
||||
|
||||
static Stream<Chatter> spies(RegisteredServer server) {
|
||||
return spies.entrySet().stream()
|
||||
.filter(entry -> entry.getValue().contains(server))
|
||||
.map(entry -> VelocityCore.getProxy().getPlayer(entry.getKey()).orElse(null))
|
||||
.filter(Objects::nonNull)
|
||||
.map(Chatter::of)
|
||||
.filter(chatter -> chatter.user().hasPerm(UserPerm.MODERATION))
|
||||
.map(Chatter.class::cast);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the user is a referee of the event fight running on the server
|
||||
*/
|
||||
static boolean isReferee(RegisteredServer server, int userId) {
|
||||
Subserver subserver = Subserver.getSubserver(server.getServerInfo());
|
||||
if (subserver == null || !EventStarter.getEventServer().containsValue(subserver)) return false;
|
||||
|
||||
Event event = Event.get();
|
||||
return event != null && referees(event.getEventID()).contains(userId);
|
||||
}
|
||||
|
||||
private static synchronized Set<Integer> referees(int eventId) {
|
||||
if (eventId != refereeEvent || System.currentTimeMillis() - refereesLoaded > REFEREE_CACHE_MILLIS) {
|
||||
referees = Referee.get(eventId);
|
||||
refereeEvent = eventId;
|
||||
refereesLoaded = System.currentTimeMillis();
|
||||
}
|
||||
return referees;
|
||||
}
|
||||
|
||||
private static Set<String> hidden(UUID uuid) {
|
||||
return hidden.computeIfAbsent(uuid, u -> {
|
||||
Set<String> channels = ConcurrentHashMap.newKeySet();
|
||||
String config = UserConfig.getConfig(u, HIDDEN_KEY);
|
||||
if (config != null && !config.isEmpty()) {
|
||||
channels.addAll(Arrays.asList(config.split(",")));
|
||||
}
|
||||
return channels;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return false if the player left the channel with the given persist id as reader
|
||||
*/
|
||||
static boolean reads(Chatter chatter, String persistId) {
|
||||
Player player = chatter.getPlayer();
|
||||
return player == null || !hidden(player.getUniqueId()).contains(persistId);
|
||||
}
|
||||
|
||||
private static void saveHidden(UUID uuid) {
|
||||
Set<String> channels = hidden(uuid);
|
||||
UserConfig.updatePlayerConfig(uuid, HIDDEN_KEY, channels.isEmpty() ? null : String.join(",", channels));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the channel is the fight chat of an arena the player is not on, which moderators can read without joining the arena
|
||||
*/
|
||||
private static boolean isForeignArena(PlayerChatter player, ChatChannel channel) {
|
||||
return channel instanceof LocalChannel local && local.isArena() && !onServer(player, local.getServer());
|
||||
}
|
||||
|
||||
public static void join(PlayerChatter player, ChatChannel channel) {
|
||||
UUID uuid = player.user().getUUID();
|
||||
if (isForeignArena(player, channel)) {
|
||||
spies.computeIfAbsent(uuid, u -> ConcurrentHashMap.newKeySet()).add(((LocalChannel) channel).getServer());
|
||||
player.system("CHAT_CHANNEL_JOINED", channel.tag(player));
|
||||
return;
|
||||
}
|
||||
|
||||
if (hidden(uuid).remove(channel.persistId())) {
|
||||
saveHidden(uuid);
|
||||
}
|
||||
player.system("CHAT_CHANNEL_JOINED", channel.tag(player));
|
||||
}
|
||||
|
||||
public static void leave(PlayerChatter player, ChatChannel channel) {
|
||||
UUID uuid = player.user().getUUID();
|
||||
if (isForeignArena(player, channel)) {
|
||||
Set<RegisteredServer> servers = spies.get(uuid);
|
||||
if (servers != null) servers.remove(((LocalChannel) channel).getServer());
|
||||
player.system("CHAT_CHANNEL_LEFT", channel.tag(player));
|
||||
return;
|
||||
}
|
||||
|
||||
String id = channel.persistId();
|
||||
if (id.equals(getDefault(player).persistId())) {
|
||||
player.system("CHAT_CHANNEL_LEAVE_DEFAULT");
|
||||
return;
|
||||
}
|
||||
|
||||
if (hidden(uuid).add(id)) {
|
||||
saveHidden(uuid);
|
||||
}
|
||||
player.system("CHAT_CHANNEL_LEFT", channel.tag(player));
|
||||
}
|
||||
|
||||
public static FightInfoPacket getFightInfo(RegisteredServer server) {
|
||||
return TablistManager.getFightInfo(server);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the team of an event fight on the server whose colored name is shown, null if the server runs no event fight
|
||||
*/
|
||||
static Team eventTeam(RegisteredServer server, String coloredTeamName) {
|
||||
Subserver subserver = Subserver.getSubserver(server.getServerInfo());
|
||||
if (subserver == null) return null;
|
||||
|
||||
String kuerzel = coloredTeamName.replaceAll("§.", "");
|
||||
for (Map.Entry<Integer, Subserver> entry : new ArrayList<>(EventStarter.getEventServer().entrySet())) {
|
||||
if (entry.getValue() != subserver) continue;
|
||||
|
||||
Team team = Team.byId(entry.getKey());
|
||||
if (team != null && team.getTeamKuerzel().equals(kuerzel)) return team;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the fight teams of an arena. Players who just joined a fight team write into the fight chat by default.
|
||||
*/
|
||||
public static void updateFightInfo(RegisteredServer server, FightInfoPacket previous, FightInfoPacket packet) {
|
||||
|
||||
Set<Integer> joined = new HashSet<>(packet.getBluePlayers());
|
||||
joined.addAll(packet.getRedPlayers());
|
||||
if (previous != null) {
|
||||
previous.getBluePlayers().forEach(joined::remove);
|
||||
previous.getRedPlayers().forEach(joined::remove);
|
||||
}
|
||||
|
||||
for (Player player : server.getPlayersConnected()) {
|
||||
if (joined.contains(SteamwarUser.get(player.getUniqueId()).getId())) {
|
||||
setDefault(Chatter.of(player), local(server));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the channel the receiver writes into by default; non-player chatters (console, Discord) use the global chat
|
||||
*/
|
||||
public static ChatChannel getDefault(Chatter chatter) {
|
||||
Player player = chatter.getPlayer();
|
||||
if (player == null) return global;
|
||||
|
||||
return defaults.computeIfAbsent(player.getUniqueId(), uuid -> initialDefault(player, currentServer(player).orElse(null)));
|
||||
}
|
||||
|
||||
private static ChatChannel initialDefault(Player player, RegisteredServer server) {
|
||||
if (server != null && isArena(server)) return local(server);
|
||||
return resolvePersisted(player, server);
|
||||
}
|
||||
|
||||
private static ChatChannel resolvePersisted(Player player, RegisteredServer server) {
|
||||
String id = persisted.computeIfAbsent(player.getUniqueId(), uuid -> Optional.ofNullable(UserConfig.getConfig(uuid, CONFIG_KEY)).orElse(global.persistId()));
|
||||
SteamwarUser user = SteamwarUser.get(player.getUniqueId());
|
||||
return switch (id) {
|
||||
case "local" -> server != null && !isArena(server) ? local(server) : global;
|
||||
case "team" -> user.getTeam() != 0 ? team(user.getTeam()) : global;
|
||||
case "serverteam" -> user.hasPerm(UserPerm.TEAM) ? serverTeam : global;
|
||||
default -> global;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the channel the default channel of the player because the player chose it.
|
||||
*/
|
||||
public static void switchTo(PlayerChatter player, ChatChannel channel) {
|
||||
if (!channel.switchable() || isForeignArena(player, channel)) {
|
||||
player.system("CHAT_CHANNEL_NOT_SWITCHABLE");
|
||||
return;
|
||||
}
|
||||
if (!channel.canWrite(player)) return;
|
||||
|
||||
UUID uuid = player.user().getUUID();
|
||||
if (channel == global && currentServer(player.getPlayer()).map(ChatManager::isArena).orElse(false)) {
|
||||
globalOnArena.add(uuid);
|
||||
} else if (channel != global) {
|
||||
globalOnArena.remove(uuid);
|
||||
}
|
||||
|
||||
String id = channel.persistId();
|
||||
if (id != null && !id.equals(persisted.get(uuid))) {
|
||||
persisted.put(uuid, id);
|
||||
UserConfig.updatePlayerConfig(uuid, CONFIG_KEY, id);
|
||||
}
|
||||
if (id != null && hidden(uuid).remove(id)) {
|
||||
saveHidden(uuid);
|
||||
}
|
||||
|
||||
defaults.put(uuid, channel);
|
||||
notifyDefault(player, channel);
|
||||
}
|
||||
|
||||
private static void setDefault(PlayerChatter player, ChatChannel channel) {
|
||||
UUID uuid = player.user().getUUID();
|
||||
if (channel != global) globalOnArena.remove(uuid);
|
||||
|
||||
ChatChannel previous = defaults.put(uuid, channel);
|
||||
if (announced.add(uuid)) {
|
||||
notifyDefault(player, channel);
|
||||
} else if (previous != channel && !player.parseToPlain(previous.tag(player)).equals(player.parseToPlain(channel.tag(player)))) {
|
||||
notifyDefault(player, channel);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells the player which channel they write into, showing the channel icon with its name on hover.
|
||||
*/
|
||||
public static void notifyDefault(PlayerChatter player, ChatChannel channel) {
|
||||
Message icon = channel.icon(player);
|
||||
Component iconComponent = icon == null ? player.parse(channel.tag(player)) : player.parse(new Message("CHAT_ICON", "§a", icon));
|
||||
player.sendMessage(Component.text()
|
||||
.append(iconComponent.hoverEvent(HoverEvent.showText(player.parse(channel.tag(player)))))
|
||||
.append(Component.text(" "))
|
||||
.append(player.parse(new Message("CHAT_CHANNEL_DEFAULT")))
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a chat message the player typed without a command.
|
||||
*/
|
||||
public static void chat(PlayerChatter sender, String message) {
|
||||
ChatChannel channel = getDefault(sender);
|
||||
if (channel instanceof TeamChannel team && team.getTeam() != sender.user().getTeam()) {
|
||||
channel = resolvePersisted(sender.getPlayer(), currentServer(sender.getPlayer()).orElse(null));
|
||||
setDefault(sender, channel);
|
||||
}
|
||||
|
||||
if (message.startsWith("+")) {
|
||||
channel = quickChannel(sender, channel);
|
||||
message = message.substring(1);
|
||||
}
|
||||
|
||||
channel.send(sender, message);
|
||||
channel.audit(sender.user(), message);
|
||||
}
|
||||
|
||||
/**
|
||||
* The channel a message prefixed with '+' is sent to: the local chat when writing into the global chat,
|
||||
* the fight team chat when writing into the local/fight chat.
|
||||
*/
|
||||
private static ChatChannel quickChannel(PlayerChatter sender, ChatChannel current) {
|
||||
RegisteredServer server = currentServer(sender.getPlayer()).orElse(null);
|
||||
if (server == null) return current;
|
||||
|
||||
if (current instanceof LocalChannel local) {
|
||||
FightTeamChannel blue = fightTeam(server, true);
|
||||
if (blue.isMember(sender)) return blue;
|
||||
|
||||
FightTeamChannel red = fightTeam(server, false);
|
||||
if (red.isMember(sender)) return red;
|
||||
|
||||
// Spectators on fight servers stay in the fight chat
|
||||
return local.isArena() ? local : global;
|
||||
}
|
||||
|
||||
return local(server);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onServerPostConnect(ServerPostConnectEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
RegisteredServer server = currentServer(player).orElse(null);
|
||||
if (server == null) return;
|
||||
|
||||
UUID uuid = player.getUniqueId();
|
||||
globalOnArena.remove(uuid);
|
||||
|
||||
ChatChannel current = defaults.get(uuid);
|
||||
ChatChannel next;
|
||||
if (isArena(server)) {
|
||||
next = local(server);
|
||||
} else if (!announced.contains(uuid) || current instanceof LocalChannel || (event.getPreviousServer() != null && isArena(event.getPreviousServer()))) {
|
||||
next = resolvePersisted(player, server);
|
||||
} else {
|
||||
next = current;
|
||||
}
|
||||
|
||||
setDefault(Chatter.of(player), next);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onDisconnect(DisconnectEvent event) {
|
||||
UUID uuid = event.getPlayer().getUniqueId();
|
||||
defaults.remove(uuid);
|
||||
persisted.remove(uuid);
|
||||
globalOnArena.remove(uuid);
|
||||
announced.remove(uuid);
|
||||
spies.remove(uuid);
|
||||
hidden.remove(uuid);
|
||||
|
||||
directs.removeIf(channel -> channel.contains(uuid));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2026 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
package de.steamwar.velocitycore.chat;
|
||||
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import de.steamwar.messages.Chatter;
|
||||
import de.steamwar.messages.Message;
|
||||
import de.steamwar.sql.AuditLog;
|
||||
import de.steamwar.sql.IgnoreSystem;
|
||||
import de.steamwar.sql.SteamwarUser;
|
||||
import de.steamwar.velocitycore.VelocityCore;
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static de.steamwar.persistent.Storage.lastChats;
|
||||
|
||||
/**
|
||||
* A temporary channel between two players, closed as soon as one of them goes offline.
|
||||
*/
|
||||
public class DirectChannel extends ChatChannel {
|
||||
|
||||
private final UUID first;
|
||||
private final UUID second;
|
||||
|
||||
DirectChannel(UUID first, UUID second) {
|
||||
this.first = first;
|
||||
this.second = second;
|
||||
}
|
||||
|
||||
public boolean contains(UUID uuid) {
|
||||
return first.equals(uuid) || second.equals(uuid);
|
||||
}
|
||||
|
||||
public UUID partner(UUID uuid) {
|
||||
return first.equals(uuid) ? second : first;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message tag(Chatter receiver) {
|
||||
return new Message("CHAT_CHANNEL_MSG", SteamwarUser.get(partner(receiver.user().getUUID())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean switchable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Message icon(Chatter receiver) {
|
||||
return new Message("CHAT_ICON_MSG");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<Chatter> readers() {
|
||||
return Stream.of(first, second)
|
||||
.map(uuid -> VelocityCore.getProxy().getPlayer(uuid).orElse(null))
|
||||
.filter(player -> player != null)
|
||||
.map(Chatter::of);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canWrite(Chatter sender) {
|
||||
UUID partner = partner(sender.user().getUUID());
|
||||
if (VelocityCore.getProxy().getPlayer(partner).isEmpty()) {
|
||||
sender.system("MSG_OFFLINE");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IgnoreSystem.isIgnored(partner, sender.user().getUUID())) {
|
||||
sender.system("MSG_IGNORED");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Component format(Chatter receiver, Chatter sender, String message) {
|
||||
return ChatFormat.player("CHAT_MSG", receiver, sender, Chatter.of(partner(sender.user().getUUID())), message);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean delivered(Chatter sender, String message, boolean mirror) {
|
||||
Player senderPlayer = VelocityCore.getProxy().getPlayer(sender.user().getUUID()).orElse(null);
|
||||
Player partnerPlayer = VelocityCore.getProxy().getPlayer(partner(sender.user().getUUID())).orElse(null);
|
||||
if (senderPlayer != null && partnerPlayer != null) {
|
||||
lastChats.put(senderPlayer, partnerPlayer);
|
||||
lastChats.put(partnerPlayer, senderPlayer);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void audit(SteamwarUser sender, String message) {
|
||||
AuditLog.createSensitiveCommand(AuditLog.SERVER_NAME_VELOCITY, null, sender, "/msg " + SteamwarUser.get(partner(sender.getUUID())).getUserName() + " " + message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2026 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
package de.steamwar.velocitycore.chat;
|
||||
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import de.steamwar.messages.Chatter;
|
||||
import de.steamwar.messages.Message;
|
||||
import de.steamwar.network.packets.common.FightInfoPacket;
|
||||
import de.steamwar.sql.AuditLog;
|
||||
import de.steamwar.sql.SteamwarUser;
|
||||
import lombok.Getter;
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* The team chat of the blue or red team of a fight.
|
||||
*/
|
||||
public class FightTeamChannel extends ChatChannel {
|
||||
|
||||
@Getter
|
||||
private final RegisteredServer server;
|
||||
private final boolean blue;
|
||||
|
||||
FightTeamChannel(RegisteredServer server, boolean blue) {
|
||||
this.server = server;
|
||||
this.blue = blue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean switchable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
private List<Integer> members() {
|
||||
FightInfoPacket info = ChatManager.getFightInfo(server);
|
||||
if (info == null) return Collections.emptyList();
|
||||
return blue ? info.getBluePlayers() : info.getRedPlayers();
|
||||
}
|
||||
|
||||
private String teamName() {
|
||||
FightInfoPacket info = ChatManager.getFightInfo(server);
|
||||
if (info == null) return "";
|
||||
return blue ? info.getBlueName() : info.getRedName();
|
||||
}
|
||||
|
||||
public boolean isMember(Chatter chatter) {
|
||||
return members().contains(chatter.user().getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message tag(Chatter receiver) {
|
||||
return new Message("CHAT_CHANNEL_FIGHT_TEAM", server.getServerInfo().getName(), teamName());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Message icon(Chatter receiver) {
|
||||
String teamName = teamName();
|
||||
String color = teamName.length() >= 2 && teamName.charAt(0) == '§' ? teamName.substring(0, 2) : "§7";
|
||||
if (ChatManager.onServer(receiver, server)) {
|
||||
return new Message("CHAT_ICON_FIGHT_TEAM", color);
|
||||
}
|
||||
return new Message("CHAT_ICON_FIGHT_TEAM_FOREIGN", color, server.getServerInfo().getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<Chatter> readers() {
|
||||
List<Integer> members = members();
|
||||
return Stream.concat(
|
||||
server.getPlayersConnected().stream().map(Chatter::of).filter(chatter -> members.contains(chatter.user().getId())),
|
||||
ChatManager.spies(server));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canWrite(Chatter sender) {
|
||||
if (!isMember(sender) || !ChatManager.onServer(sender, server)) {
|
||||
sender.system("CHAT_FIGHT_NO_TEAM");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Component format(Chatter receiver, Chatter sender, String message) {
|
||||
return ChatFormat.fight(receiver, server, sender, teamName(), "CHAT_FIGHT_PARTICIPANT_SENDER", "CHAT_FIGHT_TEAM", ChatFormat.highlightMentions(message, "§f", receiver));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void audit(SteamwarUser sender, String message) {
|
||||
AuditLog.createChat(server.getServerInfo().getName(), null, sender, "+" + message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2026 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
package de.steamwar.velocitycore.chat;
|
||||
|
||||
import de.steamwar.messages.Chatter;
|
||||
import de.steamwar.messages.Message;
|
||||
import de.steamwar.velocitycore.discord.DiscordBot;
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class GlobalChannel extends ChatChannel {
|
||||
|
||||
@Override
|
||||
public String persistId() {
|
||||
return "global";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message tag(Chatter receiver) {
|
||||
return new Message("CHAT_CHANNEL_GLOBAL");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Message icon(Chatter receiver) {
|
||||
return new Message("CHAT_ICON_GLOBAL");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<Chatter> readers() {
|
||||
return Stream.concat(Stream.of(Chatter.console()), Chatter.allPlayers().filter(ChatManager::readsGlobal).map(Chatter::of).filter(chatter -> ChatManager.reads(chatter, persistId())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canWrite(Chatter sender) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Component format(Chatter receiver, Chatter sender, String message) {
|
||||
return ChatFormat.player("CHAT_GLOBAL", receiver, sender, null, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean delivered(Chatter sender, String message, boolean mirror) {
|
||||
if (mirror) {
|
||||
DiscordBot.withBot(bot -> bot.getIngameChat().sendMessage(format(bot.getIngameChat(), sender, message)));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2026 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
package de.steamwar.velocitycore.chat;
|
||||
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import de.steamwar.messages.Chatter;
|
||||
import de.steamwar.messages.Message;
|
||||
import de.steamwar.network.packets.common.FightInfoPacket;
|
||||
import de.steamwar.network.packets.server.ChatRecordPacket;
|
||||
import de.steamwar.sql.AuditLog;
|
||||
import de.steamwar.sql.SteamwarUser;
|
||||
import de.steamwar.sql.UserPerm;
|
||||
import de.steamwar.velocitycore.network.NetworkSender;
|
||||
import lombok.Getter;
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* The chat of all players on one server. On arenas this is the fight chat.
|
||||
*/
|
||||
public class LocalChannel extends ChatChannel {
|
||||
|
||||
@Getter
|
||||
private final RegisteredServer server;
|
||||
|
||||
LocalChannel(RegisteredServer server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
public boolean isArena() {
|
||||
return ChatManager.isArena(server);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String persistId() {
|
||||
return "local";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message tag(Chatter receiver) {
|
||||
if (isArena()) {
|
||||
return new Message("CHAT_CHANNEL_ARENA", server.getServerInfo().getName());
|
||||
}
|
||||
return new Message("CHAT_CHANNEL_LOCAL");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Message icon(Chatter receiver) {
|
||||
if (!isArena()) return new Message("CHAT_ICON_LOCAL");
|
||||
|
||||
// Moderators reading an arena they are not on need to know which arena it is
|
||||
if (ChatManager.onServer(receiver, server)) {
|
||||
return new Message("CHAT_ICON_FIGHT");
|
||||
}
|
||||
return new Message("CHAT_ICON_FIGHT_FOREIGN", server.getServerInfo().getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<Chatter> readers() {
|
||||
Stream<Chatter> players = server.getPlayersConnected().stream().map(Chatter::of).filter(chatter -> ChatManager.reads(chatter, persistId())).map(Chatter.class::cast);
|
||||
if (isArena()) {
|
||||
players = Stream.concat(players, ChatManager.spies(server));
|
||||
}
|
||||
return players;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canWrite(Chatter sender) {
|
||||
// Moderators can write into fight chats they are not on (/chat <arena> msg)
|
||||
if (!ChatManager.onServer(sender, server) && !(isArena() && sender.user().hasPerm(UserPerm.MODERATION))) {
|
||||
sender.system("CHAT_NO_WRITE");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Component format(Chatter receiver, Chatter sender, String message) {
|
||||
if (!isArena()) {
|
||||
return ChatFormat.player("CHAT_GLOBAL", receiver, sender, null, message);
|
||||
}
|
||||
|
||||
String highlighted = ChatFormat.highlightMentions(message, "§7", receiver);
|
||||
FightInfoPacket info = ChatManager.getFightInfo(server);
|
||||
int id = sender.user().getId();
|
||||
if (info != null && info.getBluePlayers().contains(id)) {
|
||||
return ChatFormat.fight(receiver, server, sender, info.getBlueName(), "CHAT_FIGHT_PARTICIPANT_SENDER", "CHAT_FIGHT_PARTICIPANT", highlighted);
|
||||
} else if (info != null && info.getRedPlayers().contains(id)) {
|
||||
return ChatFormat.fight(receiver, server, sender, info.getRedName(), "CHAT_FIGHT_PARTICIPANT_SENDER", "CHAT_FIGHT_PARTICIPANT", highlighted);
|
||||
} else if (ChatManager.isReferee(server, id)) {
|
||||
return ChatFormat.fight(receiver, server, sender, null, "CHAT_FIGHT_REFEREE_SENDER", "CHAT_FIGHT_REFEREE", highlighted);
|
||||
}
|
||||
return ChatFormat.fight(receiver, server, sender, null, "CHAT_FIGHT_SPECTATOR_SENDER", "CHAT_FIGHT_SPECTATOR", highlighted);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean delivered(Chatter sender, String message, boolean mirror) {
|
||||
if (isArena()) {
|
||||
// Arenas never display chat from Velocity, they only record it for replays
|
||||
NetworkSender.send(server, new ChatRecordPacket(sender.user().getId(), message));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void audit(SteamwarUser sender, String message) {
|
||||
AuditLog.createChat(server.getServerInfo().getName(), null, sender, message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2026 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
package de.steamwar.velocitycore.chat;
|
||||
|
||||
import de.steamwar.messages.Chatter;
|
||||
import de.steamwar.messages.Message;
|
||||
import de.steamwar.sql.AuditLog;
|
||||
import de.steamwar.sql.SteamwarUser;
|
||||
import de.steamwar.sql.UserPerm;
|
||||
import de.steamwar.velocitycore.discord.DiscordBot;
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class ServerTeamChannel extends ChatChannel {
|
||||
|
||||
@Override
|
||||
public String persistId() {
|
||||
return "serverteam";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message tag(Chatter receiver) {
|
||||
return new Message("CHAT_CHANNEL_SERVERTEAM");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Message icon(Chatter receiver) {
|
||||
return new Message("CHAT_ICON_SERVERTEAM");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<Chatter> readers() {
|
||||
return Arrays.stream(Chatter.serverteam().getChatters()).filter(chatter -> ChatManager.reads(chatter, persistId()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canWrite(Chatter sender) {
|
||||
if (!sender.user().hasPerm(UserPerm.TEAM)) {
|
||||
sender.system("CHAT_NO_WRITE");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Component format(Chatter receiver, Chatter sender, String message) {
|
||||
return ChatFormat.player("CHAT_SERVERTEAM", receiver, sender, null, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean delivered(Chatter sender, String message, boolean mirror) {
|
||||
if (mirror) {
|
||||
DiscordBot.withBot(bot -> bot.getServerTeamChat().sendMessage(ChatFormat.player("CHAT_GLOBAL", bot.getServerTeamChat(), sender, null, message)));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void audit(SteamwarUser sender, String message) {
|
||||
AuditLog.createCommand(AuditLog.SERVER_NAME_VELOCITY, null, sender, "/stc " + message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2026 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
package de.steamwar.velocitycore.chat;
|
||||
|
||||
import de.steamwar.messages.Chatter;
|
||||
import de.steamwar.messages.Message;
|
||||
import de.steamwar.sql.AuditLog;
|
||||
import de.steamwar.sql.SteamwarUser;
|
||||
import de.steamwar.velocitycore.commands.StreamingCommand;
|
||||
import lombok.Getter;
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class TeamChannel extends ChatChannel {
|
||||
|
||||
@Getter
|
||||
private final int team;
|
||||
|
||||
TeamChannel(int team) {
|
||||
this.team = team;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String persistId() {
|
||||
return "team";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message tag(Chatter receiver) {
|
||||
return new Message("CHAT_CHANNEL_TEAM");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Message icon(Chatter receiver) {
|
||||
return new Message("CHAT_ICON_TEAM");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<Chatter> readers() {
|
||||
return Chatter.allPlayers()
|
||||
.map(Chatter::of)
|
||||
.filter(chatter -> chatter.user().getTeam() == team)
|
||||
.filter(StreamingCommand::isNotStreaming)
|
||||
.filter(chatter -> ChatManager.reads(chatter, persistId()))
|
||||
.map(Chatter.class::cast);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canWrite(Chatter sender) {
|
||||
if (sender.user().getTeam() != team) {
|
||||
sender.system("TC_NO_TEAM");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Component format(Chatter receiver, Chatter sender, String message) {
|
||||
return ChatFormat.player("CHAT_TEAM", receiver, sender, null, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void audit(SteamwarUser sender, String message) {
|
||||
AuditLog.createSensitiveCommand(AuditLog.SERVER_NAME_VELOCITY, null, sender, "/tc " + message);
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import de.steamwar.command.SWCommand;
|
||||
import de.steamwar.linkage.Linked;
|
||||
import de.steamwar.messages.Chatter;
|
||||
import de.steamwar.sql.UserPerm;
|
||||
import de.steamwar.velocitycore.chat.ChatManager;
|
||||
import de.steamwar.velocitycore.discord.DiscordBot;
|
||||
|
||||
@Linked
|
||||
@@ -40,7 +41,7 @@ public class AlertCommand extends SWCommand {
|
||||
}
|
||||
|
||||
String s = String.join(" ", message);
|
||||
Chatter.broadcast().system("ALERT", s.replace('&', '§'));
|
||||
ChatManager.getAlert().send(sender, s);
|
||||
|
||||
if ("-discord".equals(sendToDiscord)) {
|
||||
DiscordBot.withBot(bot -> bot.getAnnouncementChannel().send(s));
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2026 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
package de.steamwar.velocitycore.commands;
|
||||
|
||||
import de.steamwar.command.PreviousArguments;
|
||||
import de.steamwar.command.SWCommand;
|
||||
import de.steamwar.command.TypeMapper;
|
||||
import de.steamwar.linkage.Linked;
|
||||
import de.steamwar.messages.Chatter;
|
||||
import de.steamwar.messages.PlayerChatter;
|
||||
import de.steamwar.sql.UserPerm;
|
||||
import de.steamwar.velocitycore.chat.ChatChannel;
|
||||
import de.steamwar.velocitycore.chat.ChatManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
@Linked
|
||||
public class ChatCommand extends SWCommand {
|
||||
|
||||
public ChatCommand() {
|
||||
super("chat");
|
||||
}
|
||||
|
||||
@Register(description = "CHAT_USAGE")
|
||||
public void current(PlayerChatter sender) {
|
||||
ChatManager.notifyDefault(sender, ChatManager.getDefault(sender));
|
||||
}
|
||||
|
||||
@Register
|
||||
public void switchTo(PlayerChatter sender, ChatChannel channel) {
|
||||
ChatManager.switchTo(sender, channel);
|
||||
}
|
||||
|
||||
@Register
|
||||
public void joinOrLeave(PlayerChatter sender, ChatChannel channel, @StaticValue(value = {"leave", "join"}, allowISE = true) boolean join) {
|
||||
if (join) {
|
||||
ChatManager.join(sender, channel);
|
||||
} else {
|
||||
ChatManager.leave(sender, channel);
|
||||
}
|
||||
}
|
||||
|
||||
@Register
|
||||
public void msg(PlayerChatter sender, ChatChannel channel, @StaticValue("msg") String msg, @ErrorMessage(value = "CHAT_USAGE", allowEAs = false) String... message) {
|
||||
if (!sender.user().hasPerm(UserPerm.MODERATION)) {
|
||||
sender.system("CHAT_NO_WRITE");
|
||||
return;
|
||||
}
|
||||
|
||||
channel.send(sender, String.join(" ", message));
|
||||
}
|
||||
|
||||
@ClassMapper(value = ChatChannel.class, local = true)
|
||||
public TypeMapper<ChatChannel> channelTypeMapper() {
|
||||
return new TypeMapper<>() {
|
||||
@Override
|
||||
public ChatChannel map(Chatter sender, PreviousArguments previousArguments, String s) {
|
||||
return switch (s.toLowerCase()) {
|
||||
case "global" -> ChatManager.getGlobal();
|
||||
case "local" -> sender.getPlayer() == null ? null : ChatManager.currentLocal(sender.getPlayer());
|
||||
case "team", "tc" -> sender.user().getTeam() != 0 ? ChatManager.team(sender.user().getTeam()) : null;
|
||||
case "serverteam", "stc" -> sender.user().hasPerm(UserPerm.TEAM) ? ChatManager.getServerTeam() : null;
|
||||
default -> arena(sender, s);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validate(Chatter sender, ChatChannel value, MessageSender messageSender) {
|
||||
return !messageSender.send(value == null, "CHAT_CHANNEL_UNKNOWN");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> tabCompletes(Chatter sender, PreviousArguments previousArguments, String s) {
|
||||
List<String> channels = new ArrayList<>(List.of("global", "local"));
|
||||
if (sender.user().getTeam() != 0) channels.add("team");
|
||||
if (sender.user().hasPerm(UserPerm.TEAM)) channels.add("serverteam");
|
||||
if (sender.user().hasPerm(UserPerm.MODERATION)) {
|
||||
ChatManager.fightServers().map(server -> server.getServerInfo().getName()).forEach(channels::add);
|
||||
}
|
||||
return channels;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Moderators can address the fight chat of every fight server by its server name.
|
||||
*/
|
||||
private static ChatChannel arena(Chatter sender, String name) {
|
||||
if (!sender.user().hasPerm(UserPerm.MODERATION)) return null;
|
||||
|
||||
return ChatManager.fightServers()
|
||||
.filter(server -> server.getServerInfo().getName().equalsIgnoreCase(name))
|
||||
.findFirst()
|
||||
.map(ChatManager::local)
|
||||
.orElse(null);
|
||||
}
|
||||
}
|
||||
+14
-12
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2025 SteamWar.de-Serverteam
|
||||
* Copyright (C) 2026 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
|
||||
@@ -17,21 +17,23 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.core.events;
|
||||
|
||||
import de.steamwar.core.Core;
|
||||
package de.steamwar.velocitycore.commands;
|
||||
|
||||
import de.steamwar.command.SWCommand;
|
||||
import de.steamwar.linkage.Linked;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.AsyncPlayerChatEvent;
|
||||
import de.steamwar.messages.Chatter;
|
||||
import de.steamwar.velocitycore.chat.ChatManager;
|
||||
|
||||
@Linked
|
||||
public class ChattingEvent implements Listener {
|
||||
public class GlobalCommand extends SWCommand {
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
private void onChat(AsyncPlayerChatEvent event) {
|
||||
event.setCancelled(true);
|
||||
Core.MESSAGE.broadcastPrefixless("LOCAL_CHAT", event.getPlayer().getDisplayName(), event.getMessage());
|
||||
public GlobalCommand() {
|
||||
super("global");
|
||||
}
|
||||
|
||||
@Register(description = "GLOBAL_USAGE")
|
||||
public void genericCommand(Chatter sender, String... message) {
|
||||
ChatManager.sendOrSwitch(sender, ChatManager.getGlobal(), message, "GLOBAL_USAGE");
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,8 @@ package de.steamwar.velocitycore.commands;
|
||||
import de.steamwar.command.SWCommand;
|
||||
import de.steamwar.linkage.Linked;
|
||||
import de.steamwar.messages.PlayerChatter;
|
||||
import de.steamwar.velocitycore.listeners.ChatListener;
|
||||
import de.steamwar.velocitycore.chat.ChatChannel;
|
||||
import de.steamwar.velocitycore.chat.ChatManager;
|
||||
|
||||
@Linked
|
||||
public class LocalCommand extends SWCommand {
|
||||
@@ -31,8 +32,11 @@ public class LocalCommand extends SWCommand {
|
||||
super("local", "bc", "bauchat");
|
||||
}
|
||||
|
||||
@Register
|
||||
public void genericCommand(PlayerChatter player, String... message) {
|
||||
ChatListener.localChat(player, String.join(" ", message));
|
||||
@Register(description = "CHAT_BC_USAGE")
|
||||
public void genericCommand(PlayerChatter sender, String... message) {
|
||||
ChatChannel channel = ChatManager.currentLocal(sender.getPlayer());
|
||||
if (channel == null) return;
|
||||
|
||||
ChatManager.sendOrSwitch(sender, channel, message, "CHAT_BC_USAGE");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,12 +22,9 @@ package de.steamwar.velocitycore.commands;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import de.steamwar.command.SWCommand;
|
||||
import de.steamwar.linkage.Linked;
|
||||
import de.steamwar.messages.Chatter;
|
||||
import de.steamwar.messages.ChatterGroup;
|
||||
import de.steamwar.messages.PlayerChatter;
|
||||
import de.steamwar.sql.IgnoreSystem;
|
||||
import de.steamwar.velocitycore.VelocityCore;
|
||||
import de.steamwar.velocitycore.listeners.ChatListener;
|
||||
import de.steamwar.velocitycore.chat.ChatManager;
|
||||
|
||||
import static de.steamwar.persistent.Storage.lastChats;
|
||||
|
||||
@@ -49,15 +46,7 @@ public class MsgCommand extends SWCommand {
|
||||
return;
|
||||
}
|
||||
|
||||
if (IgnoreSystem.isIgnored(target.getUniqueId(), sender.user().getUUID())) {
|
||||
sender.system("MSG_IGNORED");
|
||||
return;
|
||||
}
|
||||
|
||||
Chatter receiver = Chatter.of(target);
|
||||
ChatListener.sendChat(sender, new ChatterGroup(sender, receiver), "CHAT_MSG", receiver, String.join(" ", args));
|
||||
lastChats.put(sender.getPlayer(), target);
|
||||
lastChats.put(target, sender.getPlayer());
|
||||
ChatManager.direct(sender.user().getUUID(), target.getUniqueId()).send(sender, String.join(" ", args));
|
||||
}
|
||||
|
||||
public static void remove(Player player) {
|
||||
|
||||
@@ -23,7 +23,7 @@ import de.steamwar.command.SWCommand;
|
||||
import de.steamwar.linkage.Linked;
|
||||
import de.steamwar.messages.Chatter;
|
||||
import de.steamwar.sql.UserPerm;
|
||||
import de.steamwar.velocitycore.listeners.ChatListener;
|
||||
import de.steamwar.velocitycore.chat.ChatManager;
|
||||
|
||||
@Linked
|
||||
public class ServerTeamchatCommand extends SWCommand {
|
||||
@@ -33,7 +33,7 @@ public class ServerTeamchatCommand extends SWCommand {
|
||||
}
|
||||
|
||||
@Register(description = "STC_USAGE")
|
||||
public void genericCommand(Chatter sender, @ErrorMessage(value = "STC_USAGE", allowEAs = false) String... message) {
|
||||
ChatListener.sendChat(sender, Chatter.serverteam(), "CHAT_SERVERTEAM", null, String.join(" ", message));
|
||||
public void genericCommand(Chatter sender, String... message) {
|
||||
ChatManager.sendOrSwitch(sender, ChatManager.getServerTeam(), message, "STC_USAGE");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,9 +22,7 @@ package de.steamwar.velocitycore.commands;
|
||||
import de.steamwar.command.SWCommand;
|
||||
import de.steamwar.linkage.Linked;
|
||||
import de.steamwar.messages.Chatter;
|
||||
import de.steamwar.messages.ChatterGroup;
|
||||
import de.steamwar.sql.SteamwarUser;
|
||||
import de.steamwar.velocitycore.listeners.ChatListener;
|
||||
import de.steamwar.velocitycore.chat.ChatManager;
|
||||
|
||||
@Linked
|
||||
public class TeamchatCommand extends SWCommand {
|
||||
@@ -34,14 +32,13 @@ public class TeamchatCommand extends SWCommand {
|
||||
}
|
||||
|
||||
@Register(description = "TC_USAGE")
|
||||
public void genericCommand(Chatter sender, @ErrorMessage(value = "TC_USAGE", allowEAs = false) String... args) {
|
||||
SteamwarUser user = sender.user();
|
||||
|
||||
if (user.getTeam() == 0) {
|
||||
public void genericCommand(Chatter sender, String... args) {
|
||||
int team = sender.user().getTeam();
|
||||
if (team == 0) {
|
||||
sender.system("TC_NO_TEAM");
|
||||
return;
|
||||
}
|
||||
|
||||
ChatListener.sendChat(sender, new ChatterGroup(Chatter.allStream().filter(p -> p.user().getTeam() == user.getTeam()).filter(StreamingCommand::isNotStreaming)), "CHAT_TEAM", null, String.join(" ", args));
|
||||
ChatManager.sendOrSwitch(sender, ChatManager.team(team), args, "TC_USAGE");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ package de.steamwar.velocitycore.discord;
|
||||
|
||||
import de.steamwar.command.SWCommand;
|
||||
import de.steamwar.messages.Chatter;
|
||||
import de.steamwar.velocitycore.chat.ChatManager;
|
||||
import de.steamwar.sql.Event;
|
||||
import de.steamwar.velocitycore.VelocityCore;
|
||||
import de.steamwar.velocitycore.discord.channels.*;
|
||||
@@ -181,9 +182,9 @@ public class DiscordBot {
|
||||
// There is a hard limit of 30 messages per minute to send as a webhook, thus with 8 webhooks we can send
|
||||
// 240 messages per minute. Which means 4 every second. I looked at the WGS fights and there were around
|
||||
// ~70 in a short burst and then rather long no new message.
|
||||
ingameChat = new DiscordChatRoom(config.channel("ingame"), "CHAT_DISCORD_GLOBAL", Chatter::globalChat, 8);
|
||||
ingameChat = new DiscordChatRoom(config.channel("ingame"), ChatManager.getGlobal(), 8);
|
||||
// 60 messages per minute should be enough for the server team!
|
||||
serverTeamChat = new DiscordChatRoom(config.channel("serverteam"), "CHAT_DISCORD_SERVERTEAM", Chatter::serverteam, 2);
|
||||
serverTeamChat = new DiscordChatRoom(config.channel("serverteam"), ChatManager.getServerTeam(), 2);
|
||||
|
||||
VelocityCore.schedule(() -> {
|
||||
try {
|
||||
|
||||
@@ -20,22 +20,17 @@
|
||||
package de.steamwar.velocitycore.discord.channels;
|
||||
|
||||
import de.steamwar.messages.Chatter;
|
||||
import de.steamwar.messages.ChatterGroup;
|
||||
import de.steamwar.sql.Punishment;
|
||||
import de.steamwar.sql.SteamwarUser;
|
||||
import de.steamwar.velocitycore.listeners.ChatListener;
|
||||
import de.steamwar.velocitycore.chat.ChatChannel;
|
||||
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class DiscordChatRoom extends DiscordChannel {
|
||||
|
||||
private final String format;
|
||||
private final Supplier<ChatterGroup> target;
|
||||
private final ChatChannel target;
|
||||
|
||||
public DiscordChatRoom(String channel, String format, Supplier<ChatterGroup> target, int maxNumberOfWebhooks) {
|
||||
public DiscordChatRoom(String channel, ChatChannel target, int maxNumberOfWebhooks) {
|
||||
super(channel, maxNumberOfWebhooks);
|
||||
this.format = format;
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
@@ -47,6 +42,6 @@ public class DiscordChatRoom extends DiscordChannel {
|
||||
return;
|
||||
}
|
||||
|
||||
ChatListener.sendChat(Chatter.of(user.getUUID(), true), target.get(), format, null, event.getMessage().getContentDisplay().replace('§', '&').replace('\n', ' '));
|
||||
target.send(Chatter.of(user.getUUID(), true), event.getMessage().getContentDisplay().replace('§', '&').replace('\n', ' '), false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,30 +27,18 @@ import com.velocitypowered.api.event.player.PlayerChatEvent;
|
||||
import com.velocitypowered.api.event.player.TabCompleteEvent;
|
||||
import com.velocitypowered.api.proxy.ConsoleCommandSource;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.ServerConnection;
|
||||
import com.velocitypowered.api.proxy.server.ServerInfo;
|
||||
import de.steamwar.linkage.Linked;
|
||||
import de.steamwar.messages.Chatter;
|
||||
import de.steamwar.messages.ChatterGroup;
|
||||
import de.steamwar.messages.Message;
|
||||
import de.steamwar.messages.PlayerChatter;
|
||||
import de.steamwar.network.packets.server.PingPacket;
|
||||
import de.steamwar.persistent.Subserver;
|
||||
import de.steamwar.sql.*;
|
||||
import de.steamwar.velocitycore.ArenaMode;
|
||||
import de.steamwar.velocitycore.VelocityCore;
|
||||
import de.steamwar.velocitycore.chat.ChatManager;
|
||||
import de.steamwar.velocitycore.commands.PunishmentCommand;
|
||||
import de.steamwar.velocitycore.discord.DiscordBot;
|
||||
import de.steamwar.velocitycore.network.NetworkSender;
|
||||
import de.steamwar.velocitycore.util.SteamwarPrefix;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Linked
|
||||
public class ChatListener extends BasicListener {
|
||||
@@ -138,21 +126,7 @@ public class ChatListener extends BasicListener {
|
||||
return;
|
||||
}
|
||||
|
||||
Subserver subserver = Subserver.getSubserver(player);
|
||||
String serverName = AuditLog.SERVER_NAME_VELOCITY;
|
||||
if (Subserver.isArena(subserver) && subserver.getServer() == player.getCurrentServer().orElseThrow().getServerInfo()) {
|
||||
serverName = subserver.getServer().getName();
|
||||
localChat(Chatter.of(player), message);
|
||||
} else if (message.startsWith("+")) {
|
||||
serverName = player.getCurrentServer()
|
||||
.map(ServerConnection::getServerInfo)
|
||||
.map(ServerInfo::getName)
|
||||
.orElse(serverName);
|
||||
localChat(Chatter.of(player), message.substring(1));
|
||||
} else {
|
||||
sendChat(Chatter.of(player), Chatter.globalChat(), "CHAT_GLOBAL", null, message);
|
||||
}
|
||||
AuditLog.createChat(serverName, null, user, message);
|
||||
ChatManager.chat(Chatter.of(player), message);
|
||||
}
|
||||
|
||||
private static boolean isMistypedCommand(Player player, String message) {
|
||||
@@ -172,115 +146,6 @@ public class ChatListener extends BasicListener {
|
||||
return isCommand;
|
||||
}
|
||||
|
||||
public static void sendChat(Chatter sender, ChatterGroup receivers, String format, Chatter msgReceiver, String message) {
|
||||
SteamwarUser user = sender.user();
|
||||
final String coloredMessage = user.hasPerm(UserPerm.COLOR_CHAT) ? message.replace('&', '§') : message;
|
||||
if (chatFilter(sender, coloredMessage)) return;
|
||||
|
||||
boolean useSwPrefix = useSwPrefix(user);
|
||||
|
||||
boolean noReceiver = true;
|
||||
for (Chatter player : receivers.getChatters()) {
|
||||
if (player.chatShown()) {
|
||||
chatToReciever(player, msgReceiver, user, format, coloredMessage, useSwPrefix);
|
||||
if (sender.user().getId() != player.user().getId()) noReceiver = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (format.equals("CHAT_GLOBAL")) {
|
||||
DiscordBot.withBot(bot -> chatToReciever(bot.getIngameChat(), msgReceiver, user, format, coloredMessage, useSwPrefix));
|
||||
} else if (format.equals("CHAT_SERVERTEAM")) {
|
||||
DiscordBot.withBot(bot -> chatToReciever(bot.getServerTeamChat(), msgReceiver, user, "CHAT_GLOBAL", coloredMessage, useSwPrefix));
|
||||
} else if (noReceiver) {
|
||||
sender.system("CHAT_NO_RECEIVER");
|
||||
}
|
||||
}
|
||||
|
||||
public static void localChat(PlayerChatter sender, String message) {
|
||||
if (message.isEmpty()) {
|
||||
sender.system("CHAT_BC_USAGE");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ChatListener.filteredCommand(sender, message)) return;
|
||||
|
||||
if (!message.startsWith("/") && chatFilter(sender, message)) return;
|
||||
|
||||
sender.getPlayer().spoofChatInput(message);
|
||||
}
|
||||
|
||||
private static boolean chatFilter(Chatter sender, String message) {
|
||||
if (!sender.chatShown()) {
|
||||
sender.system("CHAT_RECEIVE");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (message.replace("§[a-f0-9klmno]", "").trim().isEmpty()) {
|
||||
sender.system("CHAT_EMPTY");
|
||||
return true;
|
||||
}
|
||||
|
||||
SteamwarUser user = sender.user();
|
||||
if (!user.hasPerm(UserPerm.TEAM) && (message.contains("http:") || message.contains("https:") || message.contains("www."))) {
|
||||
sender.system("CHAT_NO_LINKS");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (PunishmentCommand.isPunishedWithMessage(sender, Punishment.PunishmentType.Mute)) return true;
|
||||
|
||||
if (message.contains("LIXFEL")) {
|
||||
specialAlert(sender, "Lixfel", "CHAT_LIXFEL_", 3, 6, 11, 12, 15);
|
||||
}
|
||||
if (message.contains("YOYONOW")) {
|
||||
specialAlert(sender, "YoyoNow", "CHAT_YOYONOW_", 3, 6, 11, 12);
|
||||
}
|
||||
if (message.contains("CHAOSCAOT")) {
|
||||
specialAlert(sender, "Chaoscaot", "CHAT_CHAOSCAOT_", 3, 6, 11, 12, 15, 17);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void chatToReciever(Chatter receiver, Chatter msgReceiver, SteamwarUser sender, String format, String message, boolean useSwPrefix) {
|
||||
UserPerm.Prefix prefix = sender.prefix();
|
||||
|
||||
String teamPrefix = "";
|
||||
if (sender.hasPerm(UserPerm.TEAM) && useSwPrefix) {
|
||||
teamPrefix = "§eS§8W ";
|
||||
} else if (sender.getTeam() != 0) {
|
||||
Team team = Team.byId(sender.getTeam());
|
||||
teamPrefix = "§" + team.getTeamColor() + team.getTeamKuerzel() + " ";
|
||||
}
|
||||
|
||||
String chatColorCode = sender.hasPerm(UserPerm.TEAM) ? "§f" : "§7";
|
||||
receiver.prefixless(format,
|
||||
sender,
|
||||
msgReceiver == null ? receiver : msgReceiver,
|
||||
highlightMentions(message, chatColorCode, receiver),
|
||||
teamPrefix,
|
||||
"",
|
||||
prefix == UserPerm.emptyPrefix ? "§f" : prefix.getColorCode(),
|
||||
prefix.getChatPrefix().length() == 0 ? "§f" : prefix.getChatPrefix() + " ",
|
||||
chatColorCode);
|
||||
}
|
||||
|
||||
private static boolean useSwPrefix(SteamwarUser user) {
|
||||
if (!user.hasPerm(UserPerm.TEAM)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
UserPerm.Prefix prefix = user.prefix();
|
||||
|
||||
boolean hasTeamPrefix = user.getTeam() != 0;
|
||||
boolean hasSwPrefix = prefix != UserPerm.emptyPrefix;
|
||||
|
||||
if (!hasSwPrefix) return false;
|
||||
if (!hasTeamPrefix) return true;
|
||||
|
||||
return SteamwarPrefix.usesSWPrefix(user);
|
||||
}
|
||||
|
||||
private static boolean filteredCommand(Chatter sender, String message) {
|
||||
String command = message.split(" ", 2)[0];
|
||||
if (command.startsWith("/") && command.contains(":")) {
|
||||
@@ -290,27 +155,6 @@ public class ChatListener extends BasicListener {
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void specialAlert(Chatter sender, String name, String baseMessage, int... delay) {
|
||||
sender.system("CHAT_LIXFEL_ACTION_BAR");
|
||||
for (int i = 0; i < delay.length; i++) {
|
||||
int finalI = i;
|
||||
VelocityCore.schedule(() -> sender.prefixless("CHAT_MSG", name, sender.user(), new Message(baseMessage + (finalI + 1)))).delay(delay[i], TimeUnit.SECONDS).schedule();
|
||||
}
|
||||
}
|
||||
|
||||
private static String highlightMentions(String message, String returnColor, Chatter player) {
|
||||
if (!message.contains("@")) return message;
|
||||
|
||||
String mark = "@" + player.user().getUserName();
|
||||
return Arrays.stream(message.split(" ")).map(cur -> {
|
||||
if (cur.equalsIgnoreCase(mark) && player.getPlayer() != null) {
|
||||
NetworkSender.send(player.getPlayer(), new PingPacket(player.user().getId()));
|
||||
return "§e" + cur + returnColor;
|
||||
}
|
||||
return cur;
|
||||
}).collect(Collectors.joining(" "));
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onTabCompleteResponseEvent(TabCompleteEvent e) {
|
||||
List<String> suggestions = e.getSuggestions();
|
||||
|
||||
@@ -24,6 +24,7 @@ import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import de.steamwar.linkage.Linked;
|
||||
import de.steamwar.network.packets.PacketHandler;
|
||||
import de.steamwar.network.packets.common.FightInfoPacket;
|
||||
import de.steamwar.velocitycore.chat.ChatManager;
|
||||
import de.steamwar.velocitycore.network.NetworkSender;
|
||||
import de.steamwar.velocitycore.network.ServerMetaInfo;
|
||||
import de.steamwar.velocitycore.tablist.TablistManager;
|
||||
@@ -50,7 +51,8 @@ public class FightInfoHandler extends PacketHandler {
|
||||
|
||||
FightInfoPacket lobbyPacket = packet.withServerName(server.getServerInfo().getName());
|
||||
|
||||
TablistManager.newFightInfo(server, packet);
|
||||
FightInfoPacket previous = TablistManager.newFightInfo(server, packet);
|
||||
ChatManager.updateFightInfo(server, previous, packet);
|
||||
|
||||
lobbys.removeIf(s -> s.getPlayersConnected().isEmpty());
|
||||
lobbys.forEach(lobby -> NetworkSender.send(lobby, lobbyPacket));
|
||||
|
||||
@@ -45,9 +45,17 @@ public class TablistManager extends BasicListener implements Disable {
|
||||
|
||||
private static final Map<RegisteredServer, FightInfoPacket> fightInfos = new HashMap<>();
|
||||
|
||||
public static synchronized void newFightInfo(RegisteredServer info, FightInfoPacket packet) {
|
||||
fightInfos.put(info, packet);
|
||||
/**
|
||||
* @return the previous fight info of the server, null if there was none
|
||||
*/
|
||||
public static synchronized FightInfoPacket newFightInfo(RegisteredServer info, FightInfoPacket packet) {
|
||||
FightInfoPacket previous = fightInfos.put(info, packet);
|
||||
fightInfos.keySet().removeIf(serverInfo -> serverInfo.getPlayersConnected().isEmpty());
|
||||
return previous;
|
||||
}
|
||||
|
||||
public static synchronized FightInfoPacket getFightInfo(RegisteredServer server) {
|
||||
return fightInfos.get(server);
|
||||
}
|
||||
|
||||
private final Map<Player, Tablist> tablists = new HashMap<>();
|
||||
|
||||
Reference in New Issue
Block a user