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:
Akka
2022-07-02 21:43:22 +08:00
committed by GitHub
parent a80302cdcd
commit 9bafc2c5d5
9 changed files with 335 additions and 1 deletions

View File

@@ -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));
}
}
}

View File

@@ -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
}
}

View File

@@ -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));
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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));
}
}