mirror of
https://github.com/Grasscutters/Grasscutter.git
synced 2025-03-11 09:37:19 +08:00
Implement server announcement (#1420)
* implement server announcement * Update src/main/java/emu/grasscutter/command/commands/AnnounceCommand.java Co-authored-by: Luke H-W <Birdulon@users.noreply.github.com> * Added arg numbers check Co-authored-by: Luke H-W <Birdulon@users.noreply.github.com>
This commit is contained in:
parent
a80302cdcd
commit
9bafc2c5d5
@ -0,0 +1,74 @@
|
||||
package emu.grasscutter.command.commands;
|
||||
|
||||
import emu.grasscutter.Grasscutter;
|
||||
import emu.grasscutter.command.Command;
|
||||
import emu.grasscutter.command.CommandHandler;
|
||||
import emu.grasscutter.game.player.Player;
|
||||
import emu.grasscutter.server.packet.send.PacketServerAnnounceNotify;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import static emu.grasscutter.utils.Language.translate;
|
||||
|
||||
@Command(label = "announce",
|
||||
usage = "a <tpl templateId|refresh|revoke templateId|content>",
|
||||
permission = "server.announce",
|
||||
aliases = {"a"},
|
||||
description = "commands.announce.description",
|
||||
targetRequirement = Command.TargetRequirement.NONE)
|
||||
public final class AnnounceCommand implements CommandHandler {
|
||||
|
||||
@Override
|
||||
public void execute(Player sender, Player targetPlayer, List<String> args) {
|
||||
var manager = Grasscutter.getGameServer().getAnnouncementManager();
|
||||
if (args.size() < 1) {
|
||||
CommandHandler.sendTranslatedMessage(sender, "commands.announce.command_usage");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (args.get(0)){
|
||||
case "tpl":
|
||||
if (args.size() < 2) {
|
||||
CommandHandler.sendTranslatedMessage(sender, "commands.announce.command_usage");
|
||||
return;
|
||||
}
|
||||
|
||||
var templateId = Integer.parseInt(args.get(1));
|
||||
var tpl = manager.getAnnounceConfigItemMap().get(templateId);
|
||||
if(tpl == null){
|
||||
CommandHandler.sendMessage(sender, translate(sender, "commands.announce.not_found", templateId));
|
||||
return;
|
||||
}
|
||||
|
||||
manager.broadcast(Collections.singletonList(tpl));
|
||||
CommandHandler.sendMessage(sender, translate(sender, "commands.announce.send_success", tpl.getTemplateId()));
|
||||
break;
|
||||
|
||||
case "refresh":
|
||||
var num = manager.refresh();
|
||||
CommandHandler.sendMessage(sender, translate(sender, "commands.announce.refresh_success", num));
|
||||
break;
|
||||
|
||||
case "revoke":
|
||||
if (args.size() < 2) {
|
||||
CommandHandler.sendTranslatedMessage(sender, "commands.announce.command_usage");
|
||||
return;
|
||||
}
|
||||
|
||||
var templateId1 = Integer.parseInt(args.get(1));
|
||||
manager.revoke(templateId1);
|
||||
CommandHandler.sendMessage(sender, translate(sender, "commands.announce.revoke_done", templateId1));
|
||||
break;
|
||||
|
||||
default:
|
||||
var id = new Random().nextInt(10000, 99999);
|
||||
var text = String.join(" ", args);
|
||||
manager.getOnlinePlayers().forEach(i -> i.sendPacket(new PacketServerAnnounceNotify(text, id)));
|
||||
|
||||
CommandHandler.sendMessage(sender, translate(sender, "commands.announce.send_success", id));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,112 @@
|
||||
package emu.grasscutter.game.managers;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import emu.grasscutter.Grasscutter;
|
||||
import emu.grasscutter.data.DataLoader;
|
||||
import emu.grasscutter.game.player.Player;
|
||||
import emu.grasscutter.game.world.World;
|
||||
import emu.grasscutter.net.proto.AnnounceDataOuterClass;
|
||||
import emu.grasscutter.server.game.GameServer;
|
||||
import emu.grasscutter.server.packet.send.PacketServerAnnounceNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketServerAnnounceRevokeNotify;
|
||||
import emu.grasscutter.utils.Utils;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
import lombok.experimental.FieldDefaults;
|
||||
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.*;
|
||||
|
||||
@Getter
|
||||
public class AnnouncementManager {
|
||||
|
||||
public final GameServer server;
|
||||
public AnnouncementManager(GameServer server){
|
||||
this.server = server;
|
||||
loadConfig();
|
||||
}
|
||||
Map<Integer, AnnounceConfigItem> announceConfigItemMap = new HashMap<>();
|
||||
|
||||
private int loadConfig() {
|
||||
try (var fileReader = new InputStreamReader(DataLoader.load("Announcement.json"))) {
|
||||
List<AnnounceConfigItem> announceConfigItems = Grasscutter.getGsonFactory().fromJson(fileReader,
|
||||
TypeToken.getParameterized(List.class, AnnounceConfigItem.class).getType());
|
||||
|
||||
announceConfigItemMap = new HashMap<>();
|
||||
announceConfigItems.forEach(i -> announceConfigItemMap.put(i.getTemplateId(), i));
|
||||
|
||||
|
||||
} catch (Exception e) {
|
||||
Grasscutter.getLogger().error("Unable to load server announce config.", e);
|
||||
}
|
||||
|
||||
return announceConfigItemMap.size();
|
||||
}
|
||||
|
||||
public List<Player> getOnlinePlayers() {
|
||||
return getServer().getWorlds().stream()
|
||||
.map(World::getPlayers)
|
||||
.flatMap(Collection::stream)
|
||||
.toList();
|
||||
}
|
||||
|
||||
public void broadcast(List<AnnounceConfigItem> tpl) {
|
||||
if(tpl == null || tpl.size() == 0){
|
||||
return;
|
||||
}
|
||||
|
||||
var list = tpl.stream()
|
||||
.map(AnnounceConfigItem::toProto)
|
||||
.map(AnnounceDataOuterClass.AnnounceData.Builder::build)
|
||||
.toList();
|
||||
|
||||
getOnlinePlayers().forEach(i -> i.sendPacket(new PacketServerAnnounceNotify(list)));
|
||||
}
|
||||
|
||||
public int refresh() {
|
||||
return loadConfig();
|
||||
}
|
||||
|
||||
public void revoke(int tplId) {
|
||||
getOnlinePlayers().forEach(i -> i.sendPacket(new PacketServerAnnounceRevokeNotify(tplId)));
|
||||
}
|
||||
|
||||
@Data
|
||||
@FieldDefaults(level = AccessLevel.PRIVATE)
|
||||
public class AnnounceConfigItem{
|
||||
int templateId;
|
||||
AnnounceType type;
|
||||
int frequency;
|
||||
String content;
|
||||
Date beginTime;
|
||||
Date endTime;
|
||||
boolean tick;
|
||||
int interval;
|
||||
|
||||
public AnnounceDataOuterClass.AnnounceData.Builder toProto(){
|
||||
var proto = AnnounceDataOuterClass.AnnounceData.newBuilder();
|
||||
|
||||
proto.setConfigId(templateId)
|
||||
// I found the time here is useless
|
||||
.setBeginTime(Utils.getCurrentSeconds() + 1)
|
||||
.setEndTime(Utils.getCurrentSeconds() + 10);
|
||||
|
||||
if(type == AnnounceType.CENTER){
|
||||
proto.setCenterSystemText(content)
|
||||
.setCenterSystemFrequency(frequency)
|
||||
;
|
||||
}else{
|
||||
proto.setCountDownText(content)
|
||||
.setCountDownFrequency(frequency)
|
||||
;
|
||||
}
|
||||
|
||||
return proto;
|
||||
}
|
||||
}
|
||||
|
||||
public enum AnnounceType{
|
||||
CENTER, COUNTDOWN
|
||||
}
|
||||
}
|
@ -12,6 +12,7 @@ import emu.grasscutter.game.dungeons.DungeonManager;
|
||||
import emu.grasscutter.game.dungeons.challenge.DungeonChallenge;
|
||||
import emu.grasscutter.game.expedition.ExpeditionManager;
|
||||
import emu.grasscutter.game.gacha.GachaManager;
|
||||
import emu.grasscutter.game.managers.AnnouncementManager;
|
||||
import emu.grasscutter.game.managers.CookingManager;
|
||||
import emu.grasscutter.game.managers.InventoryManager;
|
||||
import emu.grasscutter.game.managers.MultiplayerManager;
|
||||
@ -69,6 +70,7 @@ public final class GameServer extends KcpServer {
|
||||
@Getter private final BattlePassMissionManager battlePassMissionManager;
|
||||
@Getter private final CombineManger combineManger;
|
||||
@Getter private final TowerScheduleManager towerScheduleManager;
|
||||
@Getter private final AnnouncementManager announcementManager;
|
||||
|
||||
public GameServer() {
|
||||
this(getAdapterInetSocketAddress());
|
||||
@ -112,7 +114,7 @@ public final class GameServer extends KcpServer {
|
||||
this.towerScheduleManager = new TowerScheduleManager(this);
|
||||
this.worldDataManager = new WorldDataManager(this);
|
||||
this.battlePassMissionManager = new BattlePassMissionManager(this);
|
||||
|
||||
this.announcementManager = new AnnouncementManager(this);
|
||||
// Hook into shutdown event.
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(this::onServerShutdown));
|
||||
}
|
||||
|
@ -0,0 +1,38 @@
|
||||
package emu.grasscutter.server.packet.send;
|
||||
|
||||
import emu.grasscutter.net.packet.BasePacket;
|
||||
import emu.grasscutter.net.packet.PacketOpcodes;
|
||||
import emu.grasscutter.net.proto.AnnounceDataOuterClass;
|
||||
import emu.grasscutter.net.proto.ServerAnnounceNotifyOuterClass;
|
||||
import emu.grasscutter.utils.Utils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class PacketServerAnnounceNotify extends BasePacket {
|
||||
|
||||
public PacketServerAnnounceNotify(List<AnnounceDataOuterClass.AnnounceData> data) {
|
||||
super(PacketOpcodes.ServerAnnounceNotify);
|
||||
|
||||
var proto = ServerAnnounceNotifyOuterClass.ServerAnnounceNotify.newBuilder();
|
||||
|
||||
proto.addAllAnnounceDataList(data);
|
||||
|
||||
this.setData(proto);
|
||||
}
|
||||
|
||||
public PacketServerAnnounceNotify(String msg, int configId) {
|
||||
super(PacketOpcodes.ServerAnnounceNotify);
|
||||
|
||||
var proto = ServerAnnounceNotifyOuterClass.ServerAnnounceNotify.newBuilder();
|
||||
|
||||
proto.addAnnounceDataList(AnnounceDataOuterClass.AnnounceData.newBuilder()
|
||||
.setConfigId(configId)
|
||||
.setBeginTime(Utils.getCurrentSeconds() + 1)
|
||||
.setEndTime(Utils.getCurrentSeconds() + 2)
|
||||
.setCenterSystemText(msg)
|
||||
.setCenterSystemFrequency(1)
|
||||
.build());
|
||||
|
||||
this.setData(proto);
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package emu.grasscutter.server.packet.send;
|
||||
|
||||
import emu.grasscutter.net.packet.BasePacket;
|
||||
import emu.grasscutter.net.packet.PacketOpcodes;
|
||||
import emu.grasscutter.net.proto.ServerAnnounceRevokeNotifyOuterClass;
|
||||
|
||||
public class PacketServerAnnounceRevokeNotify extends BasePacket {
|
||||
|
||||
public PacketServerAnnounceRevokeNotify(int tplId) {
|
||||
super(PacketOpcodes.ServerAnnounceRevokeNotify);
|
||||
|
||||
var proto = ServerAnnounceRevokeNotifyOuterClass.ServerAnnounceRevokeNotify.newBuilder();
|
||||
|
||||
proto.addConfigIdList(tplId);
|
||||
|
||||
this.setData(proto);
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
package emu.grasscutter.task.tasks;
|
||||
|
||||
import emu.grasscutter.Grasscutter;
|
||||
import emu.grasscutter.game.managers.AnnouncementManager;
|
||||
import emu.grasscutter.task.Task;
|
||||
import emu.grasscutter.task.TaskHandler;
|
||||
import org.quartz.JobExecutionContext;
|
||||
import org.quartz.JobExecutionException;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Task(taskName = "Announcement", taskCronExpression = "0 * * * * ?", triggerName = "AnnouncementTrigger")
|
||||
public final class AnnouncementTask extends TaskHandler {
|
||||
|
||||
Map<Integer, Integer> intervalMap = new ConcurrentHashMap<>();
|
||||
@Override
|
||||
public void onEnable() {
|
||||
Grasscutter.getLogger().debug("[Task] Announcement task enabled.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
Grasscutter.getLogger().debug("[Task] Announcement task disabled.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void execute(JobExecutionContext context) throws JobExecutionException {
|
||||
var current = new Date();
|
||||
var announceConfigItems = Grasscutter.getGameServer().getAnnouncementManager().getAnnounceConfigItemMap().values().stream()
|
||||
.filter(AnnouncementManager.AnnounceConfigItem::isTick)
|
||||
.filter(i -> current.after(i.getBeginTime()))
|
||||
.filter(i -> current.before(i.getEndTime()))
|
||||
.collect(Collectors.toMap(AnnouncementManager.AnnounceConfigItem::getTemplateId, y -> y));
|
||||
|
||||
announceConfigItems.values().forEach(i -> intervalMap.compute(i.getTemplateId(), (k,v) -> v == null ? 1 : v + 1));
|
||||
|
||||
var toSend = intervalMap.entrySet().stream()
|
||||
.filter(i -> announceConfigItems.containsKey(i.getKey()))
|
||||
.filter(i -> announceConfigItems.get(i.getKey()).getInterval() >= i.getValue())
|
||||
.map(i -> announceConfigItems.get(i.getKey()))
|
||||
.toList();
|
||||
|
||||
Grasscutter.getGameServer().getAnnouncementManager().broadcast(toSend);
|
||||
Grasscutter.getLogger().debug("Broadcast {} announcement(s) to all online players", toSend.size());
|
||||
|
||||
// clear the interval count
|
||||
toSend.forEach(i -> intervalMap.put(i.getTemplateId(), 0));
|
||||
}
|
||||
}
|
22
src/main/resources/defaults/data/Announcement.json
Normal file
22
src/main/resources/defaults/data/Announcement.json
Normal file
@ -0,0 +1,22 @@
|
||||
[
|
||||
{
|
||||
"templateId" : 1,
|
||||
"type" : "CENTER",
|
||||
"frequency" : 1,
|
||||
"content": "Welcome to grasscutter PS!",
|
||||
"beginTime": "2022-06-01T00:00:00+08:00",
|
||||
"endTime": "2022-06-01T00:08:00+08:00",
|
||||
"tick" : false,
|
||||
"interval": 1
|
||||
},
|
||||
{
|
||||
"templateId" : 2,
|
||||
"type" : "COUNTDOWN",
|
||||
"frequency" : 1,
|
||||
"content": "Welcome to grasscutter PS!",
|
||||
"beginTime": "2022-06-01T00:00:00+08:00",
|
||||
"endTime": "2022-06-01T00:08:00+08:00",
|
||||
"tick" : false,
|
||||
"interval": 1
|
||||
}
|
||||
]
|
@ -117,6 +117,14 @@
|
||||
"no_account": "Account not found.",
|
||||
"description": "Modify user accounts"
|
||||
},
|
||||
"announce": {
|
||||
"command_usage": "Usage: a <tpl templateId|refresh|revoke templateId|content>",
|
||||
"send_success": "Send an announcement successfully, you can revoke it by /a revoke %s.",
|
||||
"refresh_success": "Refresh announcement config file successfully. (Total %s)",
|
||||
"revoke_done": "Try to revoke announcement %s.",
|
||||
"description": "Send announcement to all online players, or manage server's announcement.",
|
||||
"not_found": "Could not found announcement %s."
|
||||
},
|
||||
"clear": {
|
||||
"command_usage": "Usage: clear <all|wp|art|mat>",
|
||||
"weapons": "Cleared weapons for %s.",
|
||||
|
@ -117,6 +117,14 @@
|
||||
"no_account": "账号不存在。",
|
||||
"description": "创建或删除账号"
|
||||
},
|
||||
"announce": {
|
||||
"command_usage": "用法:a <tpl templateId|refresh|revoke templateId|content>",
|
||||
"send_success": "成功地发送了一则公告,你可以通过/a revoke %s来撤销。",
|
||||
"refresh_success": "成功地刷新了公告配置。(共%s个)",
|
||||
"revoke_done": "尝试撤回公告 %s。",
|
||||
"description": "向所有在线玩家发送公告,或者管理服务器的公告。",
|
||||
"not_found": "找不到公告 %s。"
|
||||
},
|
||||
"clear": {
|
||||
"command_usage": "用法:clear <all|wp|art|mat>\nall: 所有, wp: 武器, art: 圣遗物, mat: 材料",
|
||||
"weapons": "已清除 %s 的武器。",
|
||||
|
Loading…
x
Reference in New Issue
Block a user